4

I have two lists stored in variables: $list1 and $list2, for example:

$list1:

a
b
c
d

$list2:

1
2
3
4

How do I merge them together line by line so that I end up with:

a1
b2
c3
d4

I have tried using array (@) but it just combines them one after the other, not line by line, example:

$list1 = @(command)
$list1 += @($list2)
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
Rakha
  • 1,874
  • 3
  • 26
  • 60

3 Answers3

3

If you prefer pipelining, you can also do it in one line:

0 .. ($list1.count -1) | ForEach-Object { $list1[$_]+$list2[$_] }
Martin Brandl
  • 56,134
  • 13
  • 133
  • 172
  • Good one liner! However i only get a1 output (first line of each list) when running it? For example : $list1="a,b,c,d" $list2="1,2,3,4" $result=0 .. ($list1.count -1) | ForEach-Object { $list1[$_]+$list2[$_] } echo $result. This shows : a1 – Rakha Apr 28 '17 at 13:47
  • You are initializing the lists wrong. your `$list1` is just a single string. Correct way is: `$list1 = 'a', 'b', 'c', 'd'` – Martin Brandl Apr 28 '17 at 13:52
2

You could do this with a For loop that uses iterates through the index of each object until it reaches the total (.count) of the first object:

$list1 = 'a','b','c','d'
$list2 = 1,2,3,4

For ($i=0; $i -lt $list1.count; $i++) {
    $list1[$i]+$list2[$i]
}

Output:

a1
b2
c3
d4

If you want the results to go to a variable, you could put (for example) $list = before the For.

Mark Wragg
  • 22,105
  • 7
  • 39
  • 68
1

To complement Mark Wragg's helpful for-based answer and Martin Brandl's helpful pipeline-based answer:

Combining foreach with .., the range operator allows for a concise solution that also performs well:

foreach ($i in 0..($list1.count-1)) { "$($list1[$i])$($list2[$i])" }

Even though an entire array of indices is constructed first - 0..($list1.count-1) - this slightly outperforms the for solution with large input lists, and both foreach and for will be noticeably faster than the pipeline-based solution - see below.

Also note how string interpolation (variable references and subexpressions inside a single "..." string) are used to ensure that the result is always a string.

By contrast, if you use +, it is the type of the LHS that determines the output type, which can result in errors or unwanted output; e.g., 1 + 'a' causes an error, because 1 is an integer and 'a' cannot be converted to an integer.


Optional reading: performance considerations

  • Generally, foreach and for solutions are noticeably faster than pipeline-based (ForEach-Object cmdlet-based) solutions.

  • Pipelines are elegant and concise, but they are comparatively slow.

    • That shouldn't stop you from using them, but it's important to be aware that they can be a performance bottleneck.

    • Pipelines are memory-efficient, and for processing large collections that don't fit into memory as a whole they are always the right tool to use.

  • PSv4 introduced the little-known .ForEach() collection operator (method), whose performance is in between that of for / foreach and the ForEach-Object cmdlet.

The following compares the relative performance with large lists (100,000 items); the absolute timing numbers will vary based on many factors, but they should give you a general sense:

# Define two large lists.
$list1 = 1..100000
$list2 = 1..100000

# Define the commands as script blocks:
$cmds = { foreach ($i in 0..($list1.count-1))  { "$($list1[$i])$($list2[$i])" } },
        { for ($i=0; $i -lt $list1.count; $i++) { "$($list1[$i])$($list2[$i])" } },
        { 0..($list1.count -1) | ForEach-Object { "$($list1[$_])$($list2[$_])" } },
        {        (0..($list1.count-1)).ForEach({ "$($list1[$_])$($list2[$_])" }) }


# Time each command.
$cmds | ForEach-Object { '{0:0.0}' -f (Measure-Command $_).TotalSeconds }

In a 2-core Windows 10 VM running PSv5.1 I get the following results after running the tests several times:

0.5  # foreach
0.7  # for
1.8  # ForEach-Object (pipeline)
1.2  # .ForEach() operator
mklement0
  • 382,024
  • 64
  • 607
  • 775