Concatenate / concatenate two lists line by line?
I have two lists stored in variables: $list1
and $list2
, for example:
$list1
:
a b c d
$list2
:
1 2 3 4
How to combine them together in turn so that I end up with:
a1 b2 c3 d4
I tried using array (@), but it just concatenates them one by one and not line by line, e.g .:
$list1 = @(command) $list1 += @($list2)
source to share
To supplement useful answer for
Mark Wragg and Martin Brundle, a useful answer on the basis of the conveyor
Combining foreach
with ..
, the range operator allows for a concise solution that also works well:
foreach ($i in 0..$($list1.count-1)) { "$($list1[$i])$($list2[$i])" }
Despite the fact that you first create a whole array indices - 0..$($list1.count-1)
- is slightly larger than the solution for
with high input lists, and how foreach
, and for
will be much faster than a decision based on the assembly line - see below..
Also notice how string interpolation (variable references and subexpressions within the same string "..."
) are used to ensure that the result is always a string.
In contrast, if you use +
, the LHS type determines the type of output, which can lead to errors or unwanted results; for example, 1 + 'a'
raises an error because it 1
is an integer and 'a'
cannot be converted to an integer.
Additional Reading: Performance Considerations
-
Generally solutions are
foreach
and arefor
noticeably faster than pipeline based solutions (ForEach-Object
cmdlet based). -
Piping is elegant and concise, but relatively slow.
-
This shouldn't stop you from using them, but it's important to know that they can be a performance bottleneck.
-
Piping is memory efficient and for handling large collections that don't fit into memory in general, they are always the right tool to use.
-
-
PSv4 introduces a little-known collection operator (method)
.ForEach()
whose performance ranges fromfor
/foreach
and cmdletForEach-Object
.
Next, the relative performance is compared with large lists (100,000 items); absolute time numbers will vary based on many factors, but they should give you a general idea:
# 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 }
On a dual core Windows 10 VM running PSv5.1, I get the following results after several tests:
0.5 # foreach
0.7 # for
1.8 # ForEach-Object (pipeline)
1.2 # .ForEach() operator
source to share
You can do this with a loop For
that iterates over the index of each object until it reaches the common ( .count
) 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 wanted the results to go to a variable, you could put (for example) $list =
before For
.
source to share