updating code already written, I need help for triple loops ForEach-Object to varie 3 variables

83 Views Asked by At

PowerShell

Link to original code text

here my code :

FUNCTION NEW-ANSIBAR-16M
{
    [cmdletbinding()]
    PARAM (
            [PARAMETER(Mandatory, HelpMessage = "Enter a range of 16M color values, e.g. (1..255)")]
            [VALIDATENOTNULLOREMPTY()]
            [int[]]
            $RANGE_R,
            [int[]]
            $RANGE_G,
            [int[]]
            $RANGE_B,
            [Parameter(HelpMessage = "How many spaces do you want in the bar? This will increase the length of the bar.")]
            [int]
            $Spacing = 1
    )
    $ESC = "$([CHAR]0X1b)"
    $OUT = @()
    $BLANK = " " * $Spacing
    $out += $RANGE_R | ForEach-Object {         # ◄--- I need to include $Range_G and $Range_B.
        "$esc[48;2;$($_);$($_);$($_);1m$($blank)$esc[0m" 

        # now it's in shades of gray because it's still the same variable for the other 2 values ?

    }
    $OUT -JOIN ""
    
}

**NEW-ANSIBAR-16M -RANGE_R (1..255) -RANGE_G (100..150) -RANGE_B (100..150) -Spacing 1** 

Should I try iteratively ? Or is there another way to do it ?

I tried with others ForEach-Object but it gave errors.

Thank you very much ! I need this result bot brown degraded and return to normal.

I need this result bot brown degraded

2

There are 2 best solutions below

5
zdan On BEST ANSWER

You can avoid using 3 loops if you use the zip function from .NET LINQ

Calling from powershell is a bit tricky, but is not so bad if you're using powershell 7.3+

Here's how you could do it:

$r_R = [Linq.Enumerable]::OfType[int]($RANGE_R)
$r_G = [Linq.Enumerable]::OfType[int]($RANGE_G)
$r_B = [Linq.Enumerable]::OfType[int]($RANGE_B)

$out += [Linq.Enumerable]::Zip[int,int,int]($r_R,$r_G, $r_B) | foreach { 
    "$esc[48;2;$($_.Item1);$($_.Item2);$($_.Item3);1m$($blank)$esc[0m"
}

You can adapt this to use StringBuilder as per the other answer.

5
Santiago Squarzon On

You need 3 loops indeed, but I definitely recommend to use foreach instead of ForEach-Object in this case, and a StringBuilder instead of recreating new arrays with $OUT += ... (this is very inefficient).

FUNCTION NEW-ANSIBAR-16M {
    [cmdletbinding()]
    PARAM (
        # param definition still the same here
    )

    $ESC = "$([CHAR]0X1b)"
    $BLANK = ' ' * $Spacing
    $sb = [System.Text.StringBuilder]::new()
    foreach ($r in $RANGE_R) {
        foreach ($g in $RANGE_G) {
            foreach ($b in $RANGE_B) {
                $sb = $sb.Append("$esc[48;2;$r;$g;$b;1m${blank}${esc}[0m")
            }
        }
    }
    $sb.ToString()
}

$range = 50, 100, 200, 255
$nEWANSIBAR16MSplat = @{
    RANGE_R = $range
    RANGE_G = $range
    RANGE_B = $range
    Spacing = 10
}

NEW-ANSIBAR-16M @nEWANSIBAR16MSplat

You could also use .AppendFormat to interpolate the variables in your string, if you like:

foreach ($r in $RANGE_R) {
    foreach ($g in $RANGE_G) {
        foreach ($b in $RANGE_B) {
            $sb = $sb.AppendFormat(
                "{0}[48;2;{1};{2};{3};1m{4}{0}[0m",
                $esc, $r, $g, $b, $blank)
        }
    }
}

Looking at the update and based on comments, it seems that you're not actually looking for all permutations of the 3 arrays but instead to join the elements by index going to top and back, for that you can use a for loop:

FUNCTION NEW-ANSIBAR-16M {
    [cmdletbinding()]
    PARAM (
        # same block..
    )

    $ESC = "$([CHAR]0X1b)"
    $BLANK = ' ' * $Spacing
    $max = [System.Linq.Enumerable]::Max([int[]] ($RANGE_R.Count, $RANGE_G.Count, $RANGE_B.Count))
    $result = for ($i = 0; $i -lt $max; $i++) {
        "{0}[48;2;{1};{2};{3};1m{4}{0}[0m" -f $esc, $RANGE_R[$i], $RANGE_G[$i], $RANGE_B[$i], $blank
    }
    $reverse = $result | Select-Object -Skip 1
    [array]::Reverse($reverse)
    -join ($result + $reverse)
}

enter image description here