Slice a PowerShell array into groups of smaller arrays
I would like to convert a single array into a group of smaller arrays, based on a variable. So, 0,1,2,3,4,5,6,7,8,9
would become 0,1,2
,3,4,5
,6,7,8
,9
when the size is 3.
My current approach:
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
0..[math]::Round($ids.count/$size) | % {
# slice first elements
$x = $ids[0..($size-1)]
# redefine array w/ remaining values
$ids = $ids[$size..$ids.Length]
# return elements (as an array, which isn't happening)
$x
} | % { "IDS: $($_ -Join ",")" }
Produces:
IDS: 0
IDS: 1
IDS: 2
IDS: 3
IDS: 4
IDS: 5
IDS: 6
IDS: 7
IDS: 8
IDS: 9
I would like it to be:
IDS: 0,1,2
IDS: 3,4,5
IDS: 6,7,8
IDS: 9
What am I missing?
arrays powershell data-partitioning
add a comment |
I would like to convert a single array into a group of smaller arrays, based on a variable. So, 0,1,2,3,4,5,6,7,8,9
would become 0,1,2
,3,4,5
,6,7,8
,9
when the size is 3.
My current approach:
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
0..[math]::Round($ids.count/$size) | % {
# slice first elements
$x = $ids[0..($size-1)]
# redefine array w/ remaining values
$ids = $ids[$size..$ids.Length]
# return elements (as an array, which isn't happening)
$x
} | % { "IDS: $($_ -Join ",")" }
Produces:
IDS: 0
IDS: 1
IDS: 2
IDS: 3
IDS: 4
IDS: 5
IDS: 6
IDS: 7
IDS: 8
IDS: 9
I would like it to be:
IDS: 0,1,2
IDS: 3,4,5
IDS: 6,7,8
IDS: 9
What am I missing?
arrays powershell data-partitioning
You're just assigning$ids
to$x
and sending it down the stream to be iterated by| % {
.
– TheIncorrigible1
Aug 29 '17 at 21:35
3
Use,$x
instead of just$x
.
– Bill_Stewart
Aug 29 '17 at 21:39
add a comment |
I would like to convert a single array into a group of smaller arrays, based on a variable. So, 0,1,2,3,4,5,6,7,8,9
would become 0,1,2
,3,4,5
,6,7,8
,9
when the size is 3.
My current approach:
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
0..[math]::Round($ids.count/$size) | % {
# slice first elements
$x = $ids[0..($size-1)]
# redefine array w/ remaining values
$ids = $ids[$size..$ids.Length]
# return elements (as an array, which isn't happening)
$x
} | % { "IDS: $($_ -Join ",")" }
Produces:
IDS: 0
IDS: 1
IDS: 2
IDS: 3
IDS: 4
IDS: 5
IDS: 6
IDS: 7
IDS: 8
IDS: 9
I would like it to be:
IDS: 0,1,2
IDS: 3,4,5
IDS: 6,7,8
IDS: 9
What am I missing?
arrays powershell data-partitioning
I would like to convert a single array into a group of smaller arrays, based on a variable. So, 0,1,2,3,4,5,6,7,8,9
would become 0,1,2
,3,4,5
,6,7,8
,9
when the size is 3.
My current approach:
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
0..[math]::Round($ids.count/$size) | % {
# slice first elements
$x = $ids[0..($size-1)]
# redefine array w/ remaining values
$ids = $ids[$size..$ids.Length]
# return elements (as an array, which isn't happening)
$x
} | % { "IDS: $($_ -Join ",")" }
Produces:
IDS: 0
IDS: 1
IDS: 2
IDS: 3
IDS: 4
IDS: 5
IDS: 6
IDS: 7
IDS: 8
IDS: 9
I would like it to be:
IDS: 0,1,2
IDS: 3,4,5
IDS: 6,7,8
IDS: 9
What am I missing?
arrays powershell data-partitioning
arrays powershell data-partitioning
edited Nov 26 '18 at 3:45
mklement0
132k21246284
132k21246284
asked Aug 29 '17 at 21:28
craigcraig
17.1k1778147
17.1k1778147
You're just assigning$ids
to$x
and sending it down the stream to be iterated by| % {
.
– TheIncorrigible1
Aug 29 '17 at 21:35
3
Use,$x
instead of just$x
.
– Bill_Stewart
Aug 29 '17 at 21:39
add a comment |
You're just assigning$ids
to$x
and sending it down the stream to be iterated by| % {
.
– TheIncorrigible1
Aug 29 '17 at 21:35
3
Use,$x
instead of just$x
.
– Bill_Stewart
Aug 29 '17 at 21:39
You're just assigning
$ids
to $x
and sending it down the stream to be iterated by | % {
.– TheIncorrigible1
Aug 29 '17 at 21:35
You're just assigning
$ids
to $x
and sending it down the stream to be iterated by | % {
.– TheIncorrigible1
Aug 29 '17 at 21:35
3
3
Use
,$x
instead of just $x
.– Bill_Stewart
Aug 29 '17 at 21:39
Use
,$x
instead of just $x
.– Bill_Stewart
Aug 29 '17 at 21:39
add a comment |
5 Answers
5
active
oldest
votes
You can use ,$x
instead of just $x
.
The about_Operators
section in the documentation has this:
, Comma operator
As a binary operator, the comma creates an array. As a unary
operator, the comma creates an array with one member. Place the
comma before the member.
add a comment |
For the sake of completeness:
function Slice-Array
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$True)]
[String]$Item,
[int]$Size=10
)
BEGIN { $Items=@()}
PROCESS {
foreach ($i in $Item ) { $Items += $i }
}
END {
0..[math]::Floor($Items.count/$Size) | ForEach-Object {
$x, $Items = $Items[0..($Size-1)], $Items[$Size..$Items.Length]; ,$x
}
}
}
Usage:
@(0,1,2,3,4,5,6,7,8,9) | Slice-Array -Size 3 | ForEach-Object { "IDs: $($_ -Join ",")" }
add a comment |
cls
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
<#
Manual Selection:
$ids | Select-Object -First 3 -Skip 0
$ids | Select-Object -First 3 -Skip 3
$ids | Select-Object -First 3 -Skip 6
$ids | Select-Object -First 3 -Skip 9
#>
# Select via looping
$idx = 0
while ($($size * $idx) -lt $ids.Length){
$group = $ids | Select-Object -First $size -skip ($size * $idx)
$group -join ","
$idx ++
}
add a comment |
To add an explanation to Bill Stewart's effective solution:
Outputting a collection such as an array[1] either implicitly or using return
sends its elements individually through the pipeline; that is, the collection is enumerated (unrolled):
# Count objects received.
PS> (1..3 | Measure-Object).Count
3 # Array elements were sent *individually* through the pipeline.
Using the unary form of ,
(comma; the array-construction operator) to prevent enumeration is a conveniently concise, though somewhat obscure workaround:
PS> (, (1..3) | Measure-Object).Count
1 # By wrapping the array in a helper array, the original array was preserved.
That is, , <collection>
creates a transient single-element helper array around the original collection so that the enumeration is only applied to the helper array, outputting the enclosed original collection as-is, as a single object.
A conceptually clearer, but more verbose and slower approach is to use Write-Output -NoEnumerate
, which clearly signals the intent to output a collection as a single object.
PS> (Write-Output -NoEnumerate (1..3) | Measure-Object).Count
1 # Write-Output -NoEnumerate prevented enumeration.
Pitfall with respect to visual inspection:
On outputting for display, the boundaries between multiple arrays are seemingly erased again:
PS> (1..2), (3..4) # Output two arrays without enumeration
1
2
3
4
That is, even though two 2-element arrays were each sent as a single object each, the output, through showing elements each on their own line, makes it look like a flat 4-element array was received.
A simple way around that is to stringify each array, which turns each array into a string containing a space-separated list of its elements.
PS> (1..2), (3..4) | ForEach-Object { "$_" }
1 2
3 4
Now it is obvious that two separate arrays were received.
[1] What data types are enumerated:
Instances of data types that implement the IEnumerable
interface are automatically enumerated, but there are exceptions:
Types that also implement IDictionary
, such as hashtables, are not enumerated, and neither are XmlNode
instances.
Conversely, instances of DataTable
(which doesn't implement IEnumerable
) are enumerated (as the elements of their .Rows
collection) - see the source code
Additionally, note that stdout output from external program is enumerated line by line.
add a comment |
Craig himself has conveniently wrapped the splitting (partitioning) functionality in a robust function:
Let me offer a better-performing evolution of it (PSv3+ syntax, renamed to Split-Array
), which:
more efficiently collects the input objects using an extensible
System.Collections.Generic.List[object]]
collection.doesn't modify the collection during splitting, and instead extracts ranges of elements from it.
function Split-Array {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[String] $InputObject
,
[ValidateRange(1, [int]::MaxValue)]
[int] $Size = 10
)
begin { $items = New-Object System.Collections.Generic.List[object] }
process { $items.AddRange($InputObject) }
end {
$chunkCount = [Math]::Floor($items.Count / $Size)
foreach ($chunkNdx in 0..($chunkCount-1)) {
, $items.GetRange($chunkNdx * $Size, $Size).ToArray()
}
if ($chunkCount * $Size -lt $items.Count) {
, $items.GetRange($chunkCount * $Size, $items.Count - $chunkCount * $Size).ToArray()
}
}
}
With small input collections, the optimization won't matter much, but once you get into the thousands of elements, the speed-up can be dramatic:
To give a rough sense of the performance improvement, using Time-Command
:
$ids = 0..1e4 # 10,000 numbers
$size = 3 # chunk size
Time-Command { $ids | Split-Array -size $size }, # optimized
{ $ids | Slice-Array -size $size } # original
Sample result from a single-core Windows 10 VM with Windows 5.1 (the absolute times aren't important, but the factors are):
Command Secs (10-run avg.) TimeSpan Factor
------- ------------------ -------- ------
$ids | Split-Array -size $size 0.150 00:00:00.1498207 1.00
$ids | Slice-Array -size $size 10.382 00:00:10.3820590 69.30
Note how the unoptimized function was almost 70 times slower.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f45948580%2fslice-a-powershell-array-into-groups-of-smaller-arrays%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
You can use ,$x
instead of just $x
.
The about_Operators
section in the documentation has this:
, Comma operator
As a binary operator, the comma creates an array. As a unary
operator, the comma creates an array with one member. Place the
comma before the member.
add a comment |
You can use ,$x
instead of just $x
.
The about_Operators
section in the documentation has this:
, Comma operator
As a binary operator, the comma creates an array. As a unary
operator, the comma creates an array with one member. Place the
comma before the member.
add a comment |
You can use ,$x
instead of just $x
.
The about_Operators
section in the documentation has this:
, Comma operator
As a binary operator, the comma creates an array. As a unary
operator, the comma creates an array with one member. Place the
comma before the member.
You can use ,$x
instead of just $x
.
The about_Operators
section in the documentation has this:
, Comma operator
As a binary operator, the comma creates an array. As a unary
operator, the comma creates an array with one member. Place the
comma before the member.
answered Aug 29 '17 at 21:58
Bill_StewartBill_Stewart
13k32435
13k32435
add a comment |
add a comment |
For the sake of completeness:
function Slice-Array
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$True)]
[String]$Item,
[int]$Size=10
)
BEGIN { $Items=@()}
PROCESS {
foreach ($i in $Item ) { $Items += $i }
}
END {
0..[math]::Floor($Items.count/$Size) | ForEach-Object {
$x, $Items = $Items[0..($Size-1)], $Items[$Size..$Items.Length]; ,$x
}
}
}
Usage:
@(0,1,2,3,4,5,6,7,8,9) | Slice-Array -Size 3 | ForEach-Object { "IDs: $($_ -Join ",")" }
add a comment |
For the sake of completeness:
function Slice-Array
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$True)]
[String]$Item,
[int]$Size=10
)
BEGIN { $Items=@()}
PROCESS {
foreach ($i in $Item ) { $Items += $i }
}
END {
0..[math]::Floor($Items.count/$Size) | ForEach-Object {
$x, $Items = $Items[0..($Size-1)], $Items[$Size..$Items.Length]; ,$x
}
}
}
Usage:
@(0,1,2,3,4,5,6,7,8,9) | Slice-Array -Size 3 | ForEach-Object { "IDs: $($_ -Join ",")" }
add a comment |
For the sake of completeness:
function Slice-Array
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$True)]
[String]$Item,
[int]$Size=10
)
BEGIN { $Items=@()}
PROCESS {
foreach ($i in $Item ) { $Items += $i }
}
END {
0..[math]::Floor($Items.count/$Size) | ForEach-Object {
$x, $Items = $Items[0..($Size-1)], $Items[$Size..$Items.Length]; ,$x
}
}
}
Usage:
@(0,1,2,3,4,5,6,7,8,9) | Slice-Array -Size 3 | ForEach-Object { "IDs: $($_ -Join ",")" }
For the sake of completeness:
function Slice-Array
{
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$True)]
[String]$Item,
[int]$Size=10
)
BEGIN { $Items=@()}
PROCESS {
foreach ($i in $Item ) { $Items += $i }
}
END {
0..[math]::Floor($Items.count/$Size) | ForEach-Object {
$x, $Items = $Items[0..($Size-1)], $Items[$Size..$Items.Length]; ,$x
}
}
}
Usage:
@(0,1,2,3,4,5,6,7,8,9) | Slice-Array -Size 3 | ForEach-Object { "IDs: $($_ -Join ",")" }
edited Aug 30 '17 at 18:28
answered Aug 29 '17 at 22:15
craigcraig
17.1k1778147
17.1k1778147
add a comment |
add a comment |
cls
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
<#
Manual Selection:
$ids | Select-Object -First 3 -Skip 0
$ids | Select-Object -First 3 -Skip 3
$ids | Select-Object -First 3 -Skip 6
$ids | Select-Object -First 3 -Skip 9
#>
# Select via looping
$idx = 0
while ($($size * $idx) -lt $ids.Length){
$group = $ids | Select-Object -First $size -skip ($size * $idx)
$group -join ","
$idx ++
}
add a comment |
cls
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
<#
Manual Selection:
$ids | Select-Object -First 3 -Skip 0
$ids | Select-Object -First 3 -Skip 3
$ids | Select-Object -First 3 -Skip 6
$ids | Select-Object -First 3 -Skip 9
#>
# Select via looping
$idx = 0
while ($($size * $idx) -lt $ids.Length){
$group = $ids | Select-Object -First $size -skip ($size * $idx)
$group -join ","
$idx ++
}
add a comment |
cls
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
<#
Manual Selection:
$ids | Select-Object -First 3 -Skip 0
$ids | Select-Object -First 3 -Skip 3
$ids | Select-Object -First 3 -Skip 6
$ids | Select-Object -First 3 -Skip 9
#>
# Select via looping
$idx = 0
while ($($size * $idx) -lt $ids.Length){
$group = $ids | Select-Object -First $size -skip ($size * $idx)
$group -join ","
$idx ++
}
cls
$ids=@(0,1,2,3,4,5,6,7,8,9)
$size=3
<#
Manual Selection:
$ids | Select-Object -First 3 -Skip 0
$ids | Select-Object -First 3 -Skip 3
$ids | Select-Object -First 3 -Skip 6
$ids | Select-Object -First 3 -Skip 9
#>
# Select via looping
$idx = 0
while ($($size * $idx) -lt $ids.Length){
$group = $ids | Select-Object -First $size -skip ($size * $idx)
$group -join ","
$idx ++
}
answered Aug 29 '17 at 22:03
ChiliYagoChiliYago
3,413185896
3,413185896
add a comment |
add a comment |
To add an explanation to Bill Stewart's effective solution:
Outputting a collection such as an array[1] either implicitly or using return
sends its elements individually through the pipeline; that is, the collection is enumerated (unrolled):
# Count objects received.
PS> (1..3 | Measure-Object).Count
3 # Array elements were sent *individually* through the pipeline.
Using the unary form of ,
(comma; the array-construction operator) to prevent enumeration is a conveniently concise, though somewhat obscure workaround:
PS> (, (1..3) | Measure-Object).Count
1 # By wrapping the array in a helper array, the original array was preserved.
That is, , <collection>
creates a transient single-element helper array around the original collection so that the enumeration is only applied to the helper array, outputting the enclosed original collection as-is, as a single object.
A conceptually clearer, but more verbose and slower approach is to use Write-Output -NoEnumerate
, which clearly signals the intent to output a collection as a single object.
PS> (Write-Output -NoEnumerate (1..3) | Measure-Object).Count
1 # Write-Output -NoEnumerate prevented enumeration.
Pitfall with respect to visual inspection:
On outputting for display, the boundaries between multiple arrays are seemingly erased again:
PS> (1..2), (3..4) # Output two arrays without enumeration
1
2
3
4
That is, even though two 2-element arrays were each sent as a single object each, the output, through showing elements each on their own line, makes it look like a flat 4-element array was received.
A simple way around that is to stringify each array, which turns each array into a string containing a space-separated list of its elements.
PS> (1..2), (3..4) | ForEach-Object { "$_" }
1 2
3 4
Now it is obvious that two separate arrays were received.
[1] What data types are enumerated:
Instances of data types that implement the IEnumerable
interface are automatically enumerated, but there are exceptions:
Types that also implement IDictionary
, such as hashtables, are not enumerated, and neither are XmlNode
instances.
Conversely, instances of DataTable
(which doesn't implement IEnumerable
) are enumerated (as the elements of their .Rows
collection) - see the source code
Additionally, note that stdout output from external program is enumerated line by line.
add a comment |
To add an explanation to Bill Stewart's effective solution:
Outputting a collection such as an array[1] either implicitly or using return
sends its elements individually through the pipeline; that is, the collection is enumerated (unrolled):
# Count objects received.
PS> (1..3 | Measure-Object).Count
3 # Array elements were sent *individually* through the pipeline.
Using the unary form of ,
(comma; the array-construction operator) to prevent enumeration is a conveniently concise, though somewhat obscure workaround:
PS> (, (1..3) | Measure-Object).Count
1 # By wrapping the array in a helper array, the original array was preserved.
That is, , <collection>
creates a transient single-element helper array around the original collection so that the enumeration is only applied to the helper array, outputting the enclosed original collection as-is, as a single object.
A conceptually clearer, but more verbose and slower approach is to use Write-Output -NoEnumerate
, which clearly signals the intent to output a collection as a single object.
PS> (Write-Output -NoEnumerate (1..3) | Measure-Object).Count
1 # Write-Output -NoEnumerate prevented enumeration.
Pitfall with respect to visual inspection:
On outputting for display, the boundaries between multiple arrays are seemingly erased again:
PS> (1..2), (3..4) # Output two arrays without enumeration
1
2
3
4
That is, even though two 2-element arrays were each sent as a single object each, the output, through showing elements each on their own line, makes it look like a flat 4-element array was received.
A simple way around that is to stringify each array, which turns each array into a string containing a space-separated list of its elements.
PS> (1..2), (3..4) | ForEach-Object { "$_" }
1 2
3 4
Now it is obvious that two separate arrays were received.
[1] What data types are enumerated:
Instances of data types that implement the IEnumerable
interface are automatically enumerated, but there are exceptions:
Types that also implement IDictionary
, such as hashtables, are not enumerated, and neither are XmlNode
instances.
Conversely, instances of DataTable
(which doesn't implement IEnumerable
) are enumerated (as the elements of their .Rows
collection) - see the source code
Additionally, note that stdout output from external program is enumerated line by line.
add a comment |
To add an explanation to Bill Stewart's effective solution:
Outputting a collection such as an array[1] either implicitly or using return
sends its elements individually through the pipeline; that is, the collection is enumerated (unrolled):
# Count objects received.
PS> (1..3 | Measure-Object).Count
3 # Array elements were sent *individually* through the pipeline.
Using the unary form of ,
(comma; the array-construction operator) to prevent enumeration is a conveniently concise, though somewhat obscure workaround:
PS> (, (1..3) | Measure-Object).Count
1 # By wrapping the array in a helper array, the original array was preserved.
That is, , <collection>
creates a transient single-element helper array around the original collection so that the enumeration is only applied to the helper array, outputting the enclosed original collection as-is, as a single object.
A conceptually clearer, but more verbose and slower approach is to use Write-Output -NoEnumerate
, which clearly signals the intent to output a collection as a single object.
PS> (Write-Output -NoEnumerate (1..3) | Measure-Object).Count
1 # Write-Output -NoEnumerate prevented enumeration.
Pitfall with respect to visual inspection:
On outputting for display, the boundaries between multiple arrays are seemingly erased again:
PS> (1..2), (3..4) # Output two arrays without enumeration
1
2
3
4
That is, even though two 2-element arrays were each sent as a single object each, the output, through showing elements each on their own line, makes it look like a flat 4-element array was received.
A simple way around that is to stringify each array, which turns each array into a string containing a space-separated list of its elements.
PS> (1..2), (3..4) | ForEach-Object { "$_" }
1 2
3 4
Now it is obvious that two separate arrays were received.
[1] What data types are enumerated:
Instances of data types that implement the IEnumerable
interface are automatically enumerated, but there are exceptions:
Types that also implement IDictionary
, such as hashtables, are not enumerated, and neither are XmlNode
instances.
Conversely, instances of DataTable
(which doesn't implement IEnumerable
) are enumerated (as the elements of their .Rows
collection) - see the source code
Additionally, note that stdout output from external program is enumerated line by line.
To add an explanation to Bill Stewart's effective solution:
Outputting a collection such as an array[1] either implicitly or using return
sends its elements individually through the pipeline; that is, the collection is enumerated (unrolled):
# Count objects received.
PS> (1..3 | Measure-Object).Count
3 # Array elements were sent *individually* through the pipeline.
Using the unary form of ,
(comma; the array-construction operator) to prevent enumeration is a conveniently concise, though somewhat obscure workaround:
PS> (, (1..3) | Measure-Object).Count
1 # By wrapping the array in a helper array, the original array was preserved.
That is, , <collection>
creates a transient single-element helper array around the original collection so that the enumeration is only applied to the helper array, outputting the enclosed original collection as-is, as a single object.
A conceptually clearer, but more verbose and slower approach is to use Write-Output -NoEnumerate
, which clearly signals the intent to output a collection as a single object.
PS> (Write-Output -NoEnumerate (1..3) | Measure-Object).Count
1 # Write-Output -NoEnumerate prevented enumeration.
Pitfall with respect to visual inspection:
On outputting for display, the boundaries between multiple arrays are seemingly erased again:
PS> (1..2), (3..4) # Output two arrays without enumeration
1
2
3
4
That is, even though two 2-element arrays were each sent as a single object each, the output, through showing elements each on their own line, makes it look like a flat 4-element array was received.
A simple way around that is to stringify each array, which turns each array into a string containing a space-separated list of its elements.
PS> (1..2), (3..4) | ForEach-Object { "$_" }
1 2
3 4
Now it is obvious that two separate arrays were received.
[1] What data types are enumerated:
Instances of data types that implement the IEnumerable
interface are automatically enumerated, but there are exceptions:
Types that also implement IDictionary
, such as hashtables, are not enumerated, and neither are XmlNode
instances.
Conversely, instances of DataTable
(which doesn't implement IEnumerable
) are enumerated (as the elements of their .Rows
collection) - see the source code
Additionally, note that stdout output from external program is enumerated line by line.
edited Nov 14 '18 at 23:44
answered Nov 14 '18 at 14:25
mklement0mklement0
132k21246284
132k21246284
add a comment |
add a comment |
Craig himself has conveniently wrapped the splitting (partitioning) functionality in a robust function:
Let me offer a better-performing evolution of it (PSv3+ syntax, renamed to Split-Array
), which:
more efficiently collects the input objects using an extensible
System.Collections.Generic.List[object]]
collection.doesn't modify the collection during splitting, and instead extracts ranges of elements from it.
function Split-Array {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[String] $InputObject
,
[ValidateRange(1, [int]::MaxValue)]
[int] $Size = 10
)
begin { $items = New-Object System.Collections.Generic.List[object] }
process { $items.AddRange($InputObject) }
end {
$chunkCount = [Math]::Floor($items.Count / $Size)
foreach ($chunkNdx in 0..($chunkCount-1)) {
, $items.GetRange($chunkNdx * $Size, $Size).ToArray()
}
if ($chunkCount * $Size -lt $items.Count) {
, $items.GetRange($chunkCount * $Size, $items.Count - $chunkCount * $Size).ToArray()
}
}
}
With small input collections, the optimization won't matter much, but once you get into the thousands of elements, the speed-up can be dramatic:
To give a rough sense of the performance improvement, using Time-Command
:
$ids = 0..1e4 # 10,000 numbers
$size = 3 # chunk size
Time-Command { $ids | Split-Array -size $size }, # optimized
{ $ids | Slice-Array -size $size } # original
Sample result from a single-core Windows 10 VM with Windows 5.1 (the absolute times aren't important, but the factors are):
Command Secs (10-run avg.) TimeSpan Factor
------- ------------------ -------- ------
$ids | Split-Array -size $size 0.150 00:00:00.1498207 1.00
$ids | Slice-Array -size $size 10.382 00:00:10.3820590 69.30
Note how the unoptimized function was almost 70 times slower.
add a comment |
Craig himself has conveniently wrapped the splitting (partitioning) functionality in a robust function:
Let me offer a better-performing evolution of it (PSv3+ syntax, renamed to Split-Array
), which:
more efficiently collects the input objects using an extensible
System.Collections.Generic.List[object]]
collection.doesn't modify the collection during splitting, and instead extracts ranges of elements from it.
function Split-Array {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[String] $InputObject
,
[ValidateRange(1, [int]::MaxValue)]
[int] $Size = 10
)
begin { $items = New-Object System.Collections.Generic.List[object] }
process { $items.AddRange($InputObject) }
end {
$chunkCount = [Math]::Floor($items.Count / $Size)
foreach ($chunkNdx in 0..($chunkCount-1)) {
, $items.GetRange($chunkNdx * $Size, $Size).ToArray()
}
if ($chunkCount * $Size -lt $items.Count) {
, $items.GetRange($chunkCount * $Size, $items.Count - $chunkCount * $Size).ToArray()
}
}
}
With small input collections, the optimization won't matter much, but once you get into the thousands of elements, the speed-up can be dramatic:
To give a rough sense of the performance improvement, using Time-Command
:
$ids = 0..1e4 # 10,000 numbers
$size = 3 # chunk size
Time-Command { $ids | Split-Array -size $size }, # optimized
{ $ids | Slice-Array -size $size } # original
Sample result from a single-core Windows 10 VM with Windows 5.1 (the absolute times aren't important, but the factors are):
Command Secs (10-run avg.) TimeSpan Factor
------- ------------------ -------- ------
$ids | Split-Array -size $size 0.150 00:00:00.1498207 1.00
$ids | Slice-Array -size $size 10.382 00:00:10.3820590 69.30
Note how the unoptimized function was almost 70 times slower.
add a comment |
Craig himself has conveniently wrapped the splitting (partitioning) functionality in a robust function:
Let me offer a better-performing evolution of it (PSv3+ syntax, renamed to Split-Array
), which:
more efficiently collects the input objects using an extensible
System.Collections.Generic.List[object]]
collection.doesn't modify the collection during splitting, and instead extracts ranges of elements from it.
function Split-Array {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[String] $InputObject
,
[ValidateRange(1, [int]::MaxValue)]
[int] $Size = 10
)
begin { $items = New-Object System.Collections.Generic.List[object] }
process { $items.AddRange($InputObject) }
end {
$chunkCount = [Math]::Floor($items.Count / $Size)
foreach ($chunkNdx in 0..($chunkCount-1)) {
, $items.GetRange($chunkNdx * $Size, $Size).ToArray()
}
if ($chunkCount * $Size -lt $items.Count) {
, $items.GetRange($chunkCount * $Size, $items.Count - $chunkCount * $Size).ToArray()
}
}
}
With small input collections, the optimization won't matter much, but once you get into the thousands of elements, the speed-up can be dramatic:
To give a rough sense of the performance improvement, using Time-Command
:
$ids = 0..1e4 # 10,000 numbers
$size = 3 # chunk size
Time-Command { $ids | Split-Array -size $size }, # optimized
{ $ids | Slice-Array -size $size } # original
Sample result from a single-core Windows 10 VM with Windows 5.1 (the absolute times aren't important, but the factors are):
Command Secs (10-run avg.) TimeSpan Factor
------- ------------------ -------- ------
$ids | Split-Array -size $size 0.150 00:00:00.1498207 1.00
$ids | Slice-Array -size $size 10.382 00:00:10.3820590 69.30
Note how the unoptimized function was almost 70 times slower.
Craig himself has conveniently wrapped the splitting (partitioning) functionality in a robust function:
Let me offer a better-performing evolution of it (PSv3+ syntax, renamed to Split-Array
), which:
more efficiently collects the input objects using an extensible
System.Collections.Generic.List[object]]
collection.doesn't modify the collection during splitting, and instead extracts ranges of elements from it.
function Split-Array {
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[String] $InputObject
,
[ValidateRange(1, [int]::MaxValue)]
[int] $Size = 10
)
begin { $items = New-Object System.Collections.Generic.List[object] }
process { $items.AddRange($InputObject) }
end {
$chunkCount = [Math]::Floor($items.Count / $Size)
foreach ($chunkNdx in 0..($chunkCount-1)) {
, $items.GetRange($chunkNdx * $Size, $Size).ToArray()
}
if ($chunkCount * $Size -lt $items.Count) {
, $items.GetRange($chunkCount * $Size, $items.Count - $chunkCount * $Size).ToArray()
}
}
}
With small input collections, the optimization won't matter much, but once you get into the thousands of elements, the speed-up can be dramatic:
To give a rough sense of the performance improvement, using Time-Command
:
$ids = 0..1e4 # 10,000 numbers
$size = 3 # chunk size
Time-Command { $ids | Split-Array -size $size }, # optimized
{ $ids | Slice-Array -size $size } # original
Sample result from a single-core Windows 10 VM with Windows 5.1 (the absolute times aren't important, but the factors are):
Command Secs (10-run avg.) TimeSpan Factor
------- ------------------ -------- ------
$ids | Split-Array -size $size 0.150 00:00:00.1498207 1.00
$ids | Slice-Array -size $size 10.382 00:00:10.3820590 69.30
Note how the unoptimized function was almost 70 times slower.
edited Nov 15 '18 at 3:00
answered Nov 14 '18 at 16:19
mklement0mklement0
132k21246284
132k21246284
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f45948580%2fslice-a-powershell-array-into-groups-of-smaller-arrays%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You're just assigning
$ids
to$x
and sending it down the stream to be iterated by| % {
.– TheIncorrigible1
Aug 29 '17 at 21:35
3
Use
,$x
instead of just$x
.– Bill_Stewart
Aug 29 '17 at 21:39