Reputation: 29159
For example, given a list 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8
and a number 4, it returns a list of list with length of 4, that is
(1, 2, 3, 4), (5, 6, 7, 8), (1, 2, 3, 4), (5, 6, 7, 8)
.
Basically I want to implement the following Python code in Powershell.
s = 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8
z = zip(*[iter(s)]*4) # Here N is 4
# z is (1, 2, 3, 4), (5, 6, 7, 8), (1, 2, 3, 4), (5, 6, 7, 8)
The following script returns 17 instead of 5.
$a = 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,0
$b = 0..($a.Length / 4) | % { @($a[($_*4)..($_*4 + 4 - 1)]) }
$b.Length
Upvotes: 14
Views: 29983
Reputation: 1
$chunk_size = 10
@(1..100) | % { $c = @(); $i = 0 } { if ($i % $chunk_size -eq 0) { $c += ,@() }; $c[-1] += $_; $i++ }
$c
$chunk_size = 10 # Set desired chunk size
@(1..100) | % `
{ # Initialize loop variables
$c = @(); # Cumulating array of chunks
$i = 0; # Incrementing index
} {
if ($i % $chunk_size -eq 0) { # If the index is divisible by chunk size
$c += ,@() # Add another chunk to the accumulator
}
$c[-1] += $_; # Add current element to the last chunk
$i++ # Increment
}
$c
Upvotes: 0
Reputation: 59822
A simple method using a List<T>
to collect input objects elements and output without enumerating using $PSCmdlet.WriteObject
. This is compatible with Windows PowerShell 5.1.
function Split-Collection {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[object[]] $InputObject,
[Parameter(Position = 0)]
[ValidateRange(1, [int]::MaxValue)]
[int] $ChunkSize = 5
)
begin {
$list = [System.Collections.Generic.List[object]]::new()
}
process {
foreach($item in $InputObject) {
$list.Add($item)
if($list.Count -eq $ChunkSize) {
$PSCmdlet.WriteObject($list.ToArray())
$list.Clear()
}
}
}
end {
if($list.Count) {
$PSCmdlet.WriteObject($list.ToArray())
}
}
}
Usage:
PS ..\pwsh> 0..10 | Split-Collection 3 | ForEach-Object { "[$_]" }
[0 1 2]
[3 4 5]
[6 7 8]
[9 10]
PS ..\pwsh> Split-Collection -InputObject (0..10) 3 | ForEach-Object { "[$_]" }
[0 1 2]
[3 4 5]
[6 7 8]
[9 10]
An even simpler way to do it if using PowerShell 7+ is with Enumerable.Chunk
from LINQ.
PS ..\pwsh> [System.Linq.Enumerable]::Chunk([int[]] (0..10), 3) | ForEach-Object { "[$_]" }
[0 1 2]
[3 4 5]
[6 7 8]
[9 10]
Upvotes: 1
Reputation: 637
If collection is big enough (e.g. millions of elements), then performance may matter. None of the solutions proposed before satisfied me first of all from performance standpoints. Here is my solution:
Function Chunk {
param(
[object[]] $source,
[int] $size = 1)
$chunkCount = [Math]::Ceiling($source.Count / $size)
0 .. ($chunkCount - 1) `
| ForEach-Object {
$startIndex = $_ * $size
$endIndex = [Math]::Min(($_ + 1) * $size, $source.Count)
,$source[$startIndex .. ($endIndex - 1)]
}
}
Example of usage:
Chunk @(1..17) 4 | foreach { $_ -join ',' }
Output:
1,2,3,4
5,6,7,8
9,10,11,12
13,14,15,16
17
Performance measure:
(Measure-Command { Chunk @(1..1000000) 1000 }).TotalMilliseconds
Output: 140.467
(in milliseconds)
Upvotes: 0
Reputation: 914
Providing a solution using select
. It doesn't need to worry whether $list.Count
can be divided by $chunkSize
.
function DivideList {
param(
[object[]]$list,
[int]$chunkSize
)
for ($i = 0; $i -lt $list.Count; $i += $chunkSize) {
, ($list | select -Skip $i -First $chunkSize)
}
}
DivideList -list @(1..17) -chunkSize 4 | foreach { $_ -join ',' }
Output:
1,2,3,4
5,6,7,8
9,10,11,12
13,14,15,16
17
Upvotes: 6
Reputation: 11
Clear-Host
$s = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
$count = $s.Length
$split = $count/2
$split --
$b = $s[0..$split]
$split ++
$a = $s[$split..$count]
write-host "first array"
$b
write-host "next array"
$a
#clean up
Get-Variable -Exclude PWD,*Preference | Remove-Variable -EA 0
Upvotes: 1
Reputation: 61
@Shay Levy Answer: if you change the value of a to 1..15 then your solution not working anymore ( Peter Reavy comment )
So this worked for me:
$a = 1..15
$z=for($i=0; $i -lt $a.length; $i+=4){if ($a.length -gt ($i+3)) { ,($a[$i]..$a[$i+3])} else { ,($a[$i]..$a[-1])}}
$z.count
Upvotes: 3
Reputation: 1568
This is a bit old, but I figured I'd throw in the method I use for splitting an array into chunks. You can use Group-Object with a constructed property:
$bigList = 1..1000
$counter = [pscustomobject] @{ Value = 0 }
$groupSize = 100
$groups = $bigList | Group-Object -Property { [math]::Floor($counter.Value++ / $groupSize) }
$groups
will be a collection of GroupInfo objects; in this case, each group will have exactly 100 elements (accessible as $groups[0].Group
, $groups[1].Group
, and so on.) I use an object property for the counter to avoid scoping issues inside the -Property script block, since a simple $i++
doesn't write back to the original variable. Alternatively, you can use $script:counter = 0
and $script:counter++
and get the same effect without a custom object.
Upvotes: 58
Reputation: 29159
$a = 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,0
$b = 0..([Math]::ceiling($a.Length / 4) - 1) |
% { @(, $a[($_*4)..($_*4 + 4 - 1)]) }
Don't know why I had to put a comma after (
.
Upvotes: 1
Reputation: 126722
PS> $a = 1..16
PS> $z=for($i=0; $i -lt $a.length; $i+=4){ ,($a[$i]..$a[$i+3])}
PS> $z.count
4
PS> $z[0]
1
2
3
4
PS> $z[1]
5
6
7
8
PS> $z[2]
9
10
11
12
PS> $z[3]
13
14
15
16
Upvotes: 7
Reputation: 6823
Wrote this in 2009 PowerShell Split-Every Function
Probably can be improved.
Function Split-Every($list, $count=4) {
$aggregateList = @()
$blocks = [Math]::Floor($list.Count / $count)
$leftOver = $list.Count % $count
for($i=0; $i -lt $blocks; $i++) {
$end = $count * ($i + 1) - 1
$aggregateList += @(,$list[$start..$end])
$start = $end + 1
}
if($leftOver -gt 0) {
$aggregateList += @(,$list[$start..($end+$leftOver)])
}
$aggregateList
}
$s = 1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8
$r = Split-Every $s 4
$r[0]
""
$r[1]
""
$r[2]
""
$r[3]
Upvotes: 12