Creating Arraylist of Arraylist by slicing existing arraylist - powershell

I have the following variable defined
$A = New-Object -TypeName "System.Collections.ArrayList"
Now I add n elements to it :
$A.Add(1..n)
Now I want to divide $A into p parts of k elements each(The last one might have lesser elements if p*k>$A.count).
How do I do that?

You can use a function to split an array into several smaller arrays.
Below a slighty adapted version of that function found here:
function Split-Array {
[CmdletBinding(DefaultParametersetName = 'ByChunkSize')]
Param(
[Parameter(Mandatory = $true, Position = 0)]
$Array,
[Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'ByChunkSize')]
[ValidateRange(1,[int]::MaxValue)]
[int]$ChunkSize,
[Parameter(Mandatory = $true, Position = 1, ParameterSetName = 'ByParts')]
[ValidateRange(1,[int]::MaxValue)]
[int]$Parts
)
$items = $Array.Count
switch ($PsCmdlet.ParameterSetName) {
'ByChunkSize' { $Parts = [Math]::Ceiling($items / $ChunkSize) }
'ByParts' { $ChunkSize = [Math]::Ceiling($items / $Parts) }
default { throw "Split-Array: You must use either the Parts or the ChunkSize parameter" }
}
# when the given ChunkSize is larger or equal to the number of items in the array
# use TWO unary commas to return the array as single sub array of the result.
if ($ChunkSize -ge $items) { return ,,$Array }
$result = for ($i = 1; $i -le $Parts; $i++) {
$first = (($i - 1) * $ChunkSize)
$last = [Math]::Min(($i * $ChunkSize) - 1, $items - 1)
,$Array[$first..$last]
}
return ,$result
}
In your case you could use it like:
$p = 4 # the number of parts you want
$subArrays = Split-Array $A.ToArray() -Parts $p
or
$k = 4 # the max number items in each part
$subArrays = Split-Array $A.ToArray() -ChunkSize $k

Here is a function I came up with to chunk your System.Collections.ArrayList into a nested array list of p parts. It uses a System.Collections.Specialized.OrderedDictionary to group the size k chunks by index / chunksize, which is then rounded down to the nearest integer using System.Math.Floor. It then only fetches the groups with keys from 0 to $Parts.
function Split-ArrayList {
[CmdletBinding()]
param (
# Arraylist to slice
[Parameter(Mandatory=$true)]
[System.Collections.ArrayList]
$ArrayList,
# Chunk size per part
[Parameter(Mandatory=$true)]
[ValidateRange(1, [int]::MaxValue)]
[int]
$ChunkSize,
# Number of parts
[Parameter(Mandatory=$true)]
[ValidateRange(1, [int]::MaxValue)]
[int]
$Parts
)
# Group chunks into hashtable
$chunkGroups = [ordered]#{}
for ($i = 0; $i -lt $ArrayList.Count; $i++) {
# Get the hashtable key by dividing the index by the chunk size
# Round down to nearest integer using Math.Floor
[int]$key = [Math]::Floor($i / $ChunkSize)
# Add new arraylist for key if it doesn't exist
# ContainsKey is not supported for ordered dictionary
if ($chunkGroups.Keys -notcontains $key) {
$chunkGroups.Add($key, [System.Collections.ArrayList]::new())
}
# Add number to hashtable
[void]$chunkGroups[$key].Add($ArrayList[$i])
}
# Create nested ArrayList of parts
$result = [System.Collections.ArrayList]::new()
for ($key = 0; $key -lt $Parts; $key++) {
[void]$result.Add($chunkGroups[$key])
}
$result
}
Usage:
$A = [System.Collections.ArrayList]::new(1..10)
Split-ArrayList -ArrayList $A -ChunkSize 4 -Parts 1 |
ForEach-Object { "{ " + ($_ -join ", ") + " }" }
# { 1, 2, 3, 4 }
Split-ArrayList -ArrayList $A -ChunkSize 4 -Parts 2 |
ForEach-Object { "{ " + ($_ -join ", ") + " }" }
# { 1, 2, 3, 4 }
# { 5, 6, 7, 8 }
Split-ArrayList -ArrayList $A -ChunkSize 4 -Parts 3 |
ForEach-Object { "{ " + ($_ -join ", ") + " }" }
# { 1, 2, 3, 4 }
# { 5, 6, 7, 8 }
# { 9, 10 }
Note: I didn't really account for the cases where you might want to exclude Parts, so I made every parameter mandatory. You can amend the function to be more flexible with different inputs.

Related

How to generate a large prime via cryptography provider?

I want to generate a 2048-bit prime number via the buildin cryptography provider in Powershell. This is the code I have so far, but testing the result via Rabin-Miller Test is telling me, that the number is NOT prime. What is wrong here?
$rsa = [System.Security.Cryptography.RSA]::Create(2048)
$format = [System.Security.Cryptography.CngKeyBlobFormat]::GenericPrivateBlob
$bytes = $rsa.Key.Export($format)
[bigint]$prime = 0
foreach($b in $bytes) {$prime = ($prime -shl 8) + $b}
$prime
This link tells me, that the BLOB should contain both RSA primes, but for any reason I am not able to get that info as expected: https://learn.microsoft.com/en-us/windows/win32/seccrypto/rsa-schannel-key-blobs#private-key-blobs
Finally I solved it after digging a bit deeper into the generic BLOB-format. The learning point here is the fact, that a 4096 RSA key includes two 2048-bit primes. One is on the last 256 bytes in the BLOB and the other prime is in the 256 bytes in front of that.
Here is the working code:
# generating two random 2048-bit PRIME numbers:
cls
$rsa = [System.Security.Cryptography.RSA]::Create(4096)
$key = $rsa.Key.Export('PRIVATEBLOB')
$len = $key.Length
$Pb = [byte[]]::new(256+1)
[array]::Copy($key, $len-512, $Pb, 1, 256)
[array]::Reverse($Pb)
$P = [bigint]$Pb
write-host $P
# optionally same for the second prime in the BLOB:
$Qb = [byte[]]::new(256+1)
[array]::Copy($key, $len-256, $Qb, 1, 256)
[array]::Reverse($Qb)
$Q = [bigint]$Qb
write-host $Q
# optionally here the Test-Function:
function Is-PrimeRabinMiller ([BigInt] $Source, [int] $Iterate) {
if ($source -eq 2 -or $source -eq 3) {return $true}
if (($source -band 1) -eq 0) {return $false}
[BigInt]$d = $source - 1;
$s = 0;
while (($d -band 1) -eq 0) {$d = $d -shr 1; $s++;}
if ($source.ToByteArray().Length -gt 255) {
$sourceLength = 255
}
else {
$sourceLength = $source.ToByteArray().Length
}
$rngProv = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()
[Byte[]] $bytes = $sourceLength
[BigInt]$a = 0
foreach ($i in 1..$iterate) {
do {
$rngProv.GetBytes($bytes)
$a = [BigInt]$bytes
} while (($a -lt 2) -or ($a -ge ($source - 2)))
[BigInt]$x = ([BigInt]::ModPow($a,$d,$source))
if ($x -eq 1 -or ($x -eq $source-1)) {continue}
foreach ($j in 1..($s-1)) {
$x = [BigInt]::ModPow($x, 2, $source)
if ($x -eq 1) {return $false}
if ($x -eq $source-1) {break}
}
return $false
}
return $true
}
if (Is-PrimeRabinMiller $P 42) {"P is prime!"}
if (Is-PrimeRabinMiller $Q 42) {"Q is prime!"}
Also I created a small function based on the above findings, that generates a random large prime numer:
function get-RandomPrime {
Param (
[parameter(Mandatory=$true)]
[ValidateRange(256, 8192)]
[ValidateScript({$_ % 8 -eq 0})]
[int]$bitLength
)
$rsa = [System.Security.Cryptography.RSA]::Create($bitLength*2)
$key = $rsa.Key.Export('PRIVATEBLOB')
$len = $key.Length
$byteLength = [int]$bitLength/8
$bytes = [byte[]]::new($byteLength+1)
[array]::Copy($key, $len-$byteLength, $bytes, 1, $byteLength)
[array]::Reverse($bytes)
[bigint]$bytes
}
$prime = get-RandomPrime -bitLength 2048
write-host "`nprime (DEC):" $prime
write-host "`nprime (HEX):" $prime.ToString('X2').SubString(1)

Evaluating a condition with AND and OR statements combined

I'm parsing files which all have following structure:
A=B #A==test
This means that variable A will be set to B, only if A equals 'test'
Conditions can get more complex as well
C=D #C==test1,D==test2
This means that C will be set to D if C equals 'test1' AND D equals 'test2'
So far so good, I parse the individual conditions one by one, and stop as soon as one evaluates to false.
Conditions can also have OR statements:
E=F #E==test3,(F==test4|F==test5),username!=abc
This means that E will be set to F if E equals 'test3' AND (F equals 'test4' OR F equals 'test5') AND username does not equal 'abc'
I'm stuck on evaluating this last condition. What is a good approach to evaluate such an expression?
Currently I'm parsing each condition into 3 arrays, being 'lefthands', 'operators, 'righthands'.
The condition #C==test1,D==test2 gets parsed into:
$lefthands=(C, D)
$righthands=(test1,test2)
$operands=(==, ==)
Afterwards I loop over the arrays and do the evaluation based on the operand that is currently being used.
if $operands[$i] -eq "==" ( return ($lefthands[$i] -eq $righthands[$i] }
As soon as one of the conditions returns false, I stop evaluating the complete condition.
This works fine if there are only AND statements, but with an OR statement present this does no longer work.
Looks like you've got a fairly straightforward infix expression grammar that goes something like this:
variable = [ a-z | A-Z ]
string = [ a-z | A-Z | 0-9 ]
atom = variable [ "==" | "!=" ] string
expr = [ atom | "(" expr ")" | expr [ "," | "|" ] expr ]
Assuming you can nest expressions to an arbitrary depth and you want to handle operator precedence properly (e.g. does w,x|y,z mean ((w,x)|y),z) or (w,x)|(y,z) or even w,(x|y),z, all of which give different results) you're going to struggle to evaluate the expression with basic string manipulation and you might need use a more complicated approach:
Lex the string into tokens
Change the order of the tokens (infix -> postfix) to make it easier to calculate results
Evaluate the expression given some values for variables
Lexing
This basically breaks the expression string down into a list of logical chunks - for example A==test might become identifier "A", operator "==", identifier "test".
It's called "infix" notation because the operator sits in-between the operands - e.g. A==test.
function ConvertTo-InfixTokens
{
param( [string] $Source )
$chars = $Source.ToCharArray();
$index = 0;
while( $index -lt $chars.Length )
{
$token = [pscustomobject] [ordered] #{
"Type" = $null
"Value" = [string]::Empty
};
$char = $chars[$index];
switch -regex ( $char )
{
# var or str
"[a-zA-z0-9]" {
$token.Type = "identifier";
$token.Value += $char;
$index += 1;
while( ($index -lt $chars.Length) -and ($chars[$index] -match "[a-zA-z0-9]") )
{
$token.Value += $chars[$index];
$index += 1;
}
}
# eq
"=" {
if( $chars[$index+1] -eq "=" )
{
$token.Type = "eq";
$token.Value = $chars[$index] + $chars[$index+1];
$index += 2;
}
else
{
throw "LEX01 - unhandled char '$($chars[$index+1])'";
}
}
# ne
"!" {
if( $chars[$index+1] -eq "=" )
{
$token.Type = "ne";
$token.Value = $chars[$index] + $chars[$index+1];
$index += 2;
}
else
{
throw "LEX02 - unhandled char '$($chars[$index+1])'";
}
}
"," {
$token.Type = "and";
$token.Value = $char;
$index += 1;
}
"\|" {
$token.Type = "or";
$token.Value = $char;
$index += 1;
}
"\(" {
$token.Type = "lb";
$token.Value = $char;
$index += 1;
}
"\)" {
$token.Type = "rb";
$token.Value = $char;
$index += 1;
}
default {
throw "LEX03 - unhandled char '$char'";
}
}
write-output $token;
}
}
Examples:
PS> ConvertTo-InfixTokens -Source "A==test"
Type Value
---- -----
identifier A
eq ==
identifier test
PS> ConvertTo-InfixTokens -Source "E==test3,(F==test4|F==test5),username!=abc"
Type Value
---- -----
identifier E
eq ==
identifier test3
and ,
lb (
identifier F
eq ==
identifier test4
or |
identifier F
eq ==
identifier test5
rb )
and ,
identifier username
ne !=
identifier abc
Convert to Postfix
Infix notation looks more natural because it's similar to normal maths, but it relies on you handling the precedence rules for operations when you evaluate expressions - e.g. is 1 + 1 * 2 + 2 equal to (((1 + 1) * 2) + 2) => 6 or 1 + (1 * 2) + 2 => 5 or (1 + 1) * (2 + 2) => 8? - which means you might need to look ahead to see what other operators are coming in case they need to be processed first.
It's easier to evaluate the expression if we convert it to postfix notation (or Reverse Polish Notation) - see this answer for some additional advantages. This basically puts the operands first and then follows with the operator - e.g. 1 + 1 * 2 + 2 becomes 1 1 2 * + 2 +, which will be logically processed as: ((1 (1 2 *) +) 2 +) => ((1 2 +) 2 +) => (3 2 +) => 5
The following code will apply this conversion:
# based on https://www.tutorialspoint.com/Convert-Infix-to-Postfix-Expression#:~:text=To%20convert%20infix%20expression%20to,maintaining%20the%20precedence%20of%20them.
function Convert-InfixToPostfix
{
param( [pscustomobject[]] $Tokens )
$precedence = #{
"or" = 1
"and" = 2
"eq" = 9
"ne" = 9
};
$stack = new-object System.Collections.Generic.Stack[pscustomobject];
for( $i = 0; $i -lt $Tokens.Length; $i++ )
{
$token = $Tokens[$i];
switch( $token.Type )
{
"identifier" {
write-output $token;
}
"lb" {
$stack.Push($token);
}
"rb" {
while( $stack.Peek().Type -ne "lb" )
{
write-output $stack.Pop();
}
$null = $stack.Pop();
}
default {
# must be a known operator
if( -not $precedence.ContainsKey($token.Type) )
{
throw "PF01 - unknown operator '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i";
}
while( ($stack.Count -gt 0) -and ($precedence[$token.Type] -le $precedence[$stack.Peek().Type]) )
{
write-output $stack.Pop();
}
$stack.Push($token);
}
}
}
while( $stack.Count -gt 0 )
{
write-output $stack.Pop();
}
}
Examples:
PS> $infix = ConvertTo-InfixTokens -Source "A==test"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $postfix
Type Value
---- -----
identifier A
identifier test
eq ==
PS> $infix = ConvertTo-InfixTokens -Source "E==test3,(F==test4|F==test5),username!=abc"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $postfix
Type Value
---- -----
identifier E
identifier test3
eq ==
identifier F
identifier test4
eq ==
identifier F
identifier test5
eq ==
or |
and ,
identifier username
identifier abc
ne !=
and ,
Evaluation
The last step is to take the postfix tokens and evaluate them. "All" we need to do is read the tokens from left to right - when we get an operand we put it on a stack, and when we get an operator we read some operands off the stack, apply the operator and push the result back onto the stack...
function Invoke-PostfixEval
{
param( [pscustomobject[]] $Tokens, [hashtable] $Variables )
$stack = new-object System.Collections.Generic.Stack[pscustomobject];
for( $i = 0; $i -lt $Tokens.Length; $i++ )
{
$token = $Tokens[$i];
if( $token.Type -eq "identifier" )
{
$stack.Push($token);
}
elseif( $token.Type -in #( "eq", "ne" ) )
{
$str = $stack.Pop().Value;
$var = $stack.Pop();
if( -not $Variables.ContainsKey($var.Value) )
{
throw "undefined variable '$($var.Value)' in token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i";
}
$val = $Variables[$var.Value];
$result = switch( $token.Type ) {
"eq" { $val -eq $str }
"ne" { $val -ne $str }
default { throw "unhandled operator '$($token.Type)' in token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i"; }
}
#write-host "'$val' $($token.Type) '$str' => $result";
$stack.Push([pscustomobject] [ordered] #{ "Type" = "bool"; Value = $result });
}
elseif( $token.Type -in #( "and", "or" ) )
{
$left = $stack.Pop().Value;
$right = $stack.Pop().Value;
$result = switch( $token.Type ) {
"and" { $left -and $right }
"or" { $left -or $right }
default { throw "unhandled operator '$($token.Type)' in token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i"; }
}
#write-host "'$left' $($token.Type) '$right' => $result";
$stack.Push([pscustomobject] [ordered] #{ "Type" = "bool"; Value = $result });
}
else
{
throw "EVAL01 - unhandled token '$($token | ConvertTo-Json -Depth 99 -Compress)' at index $i";
}
}
return $stack.Peek().Value;
}
Examples:
PS> $variables = [ordered] #{
"A" = "test";
"C" = "test1";
"D" = "test2";
"E" = "test3";
"F" = "test4";
"username" = "abc"
}
PS> $infix = ConvertTo-InfixTokens -Source "A==test"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $value = Invoke-PostfixEval -Tokens $postfix -Variables $variables
PS> $value
True
PS> $infix = ConvertTo-InfixTokens -Source "E==test3,(F==test4|F==test5),username!=abc"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $value = Invoke-PostfixEval -Tokens $postfix -Variables $variables
PS> $value
False # fails because "username!=abc" is false
PS> $infix = ConvertTo-InfixTokens -Source "E==test3,F==test4|F==test5,username!=abc"
PS> $postfix = Convert-InfixToPostfix -Tokens $infix
PS> $value = Invoke-PostfixEval -Tokens $postfix -Variables $variables
PS> $value
True # operator precedence means it's evaluated as "(E==test3,F==test4)|(F==test5,username!=abc)" and "(E==test3,F==test4)" is true
You'll probably want to add more error handling for production code, and write some Pester tests to make sure it works for a larger variety of inputs, but it worked for the limited tests I did here. If you find any examples that don't work feel free to post them in comments...

powershell equivalent of javascript array.splice(index, howmany, item1, ....., itemX) for string[]

javascript
array.splice(index, howmany, item1, ....., itemX)
index Required. The position to add/remove items. Negative value defines the position from the end of the array.
howmany Optional. Number of items to be removed.
item1, ..., itemX Optional. New elements(s) to be added.
Now, I want to get this same functionality in Powershell:
function splice {
param(
[string[]]$Array,
[int32]$Index,
[int32]$HowMany,
[string[]]$Item
)
}
$lines = #(
"abc",
"xyz"
"123"
)
$lines = splice -Array $lines -Index 1 -HowMany 0 -Item "foo", "bar"
Maybe there's a better way?
I was thinking more of using an ArrayList, because that already has methods RemoveRange() and InsertRange():
function splice {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Array,
[Parameter(Mandatory = $true, Position = 1)]
[int32]$Index,
[Parameter(Mandatory = $false, Position = 2)]
[int32]$HowMany = 0,
[Parameter(Mandatory = $false, Position = 3)]
[string[]]$Item = $null
)
# convert negative index to positive
$idx = if ($Index -lt 0) { $Array.Count + $Index } else { $Index }
if ($idx -lt 0 -or $idx -ge $Array.Count) {
Write-Error "Index $Index out of range"
return
}
# initialize an ArrayList with all values from $Array
# an ArrayList can hold elements of any type
$aList = [System.Collections.ArrayList]::new($Array)
# In this case, since you are using [string[]] to typecast the parameters for $Array and $Item, you could also use
# $aList = [System.Collections.Generic.List[string]]::new($Array)
# do we need to remove items?
if ($HowMany -ge 0) {
$aList.RemoveRange($idx, [math]::Min($HowMany, $aList.Count - $idx))
}
# do we need to insert/append new items?
if ($Item.Count) {
$aList.InsertRange($idx, $Item)
}
# return as array. use unary comma to ensure the function returns an array
# even if there is only one element left
,$alist.ToArray()
}
Some tests:
$test = 'abc', 'def', 'xyz', '123'
splice $test -1 5 'add this','and this one too' # remove last item '123' and append two new items
splice $test 2 1 'add this','and this one too' # remove third item 'xyz' and insert two new items
splice $test 3 0 'add this','and this one too' # insert two new items before fourth element '123'
splice $test 4 2 'add this','and this one too' # error 'splice : Index 4 out of range'
Here's a naive implementation with no error handling, and probably a terrible performance profile...
function splice
{
param
(
[string[]] $Array,
[int32] $Index,
[int32] $HowMany,
[string[]] $Item
)
foreach( $x in ($Array | select-object -First $Index) )
{
write-output $x
}
foreach( $x in $Item )
{
write-output $x
}
foreach( $x in ($Array | select-object -Skip ($Index + $HowMany)) )
{
write-output $x
}
}
Examples with test data taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
$months = #( "Jan", "March", "April", "June" );
splice $months 1 0 "Feb";
# inserts at index 1
# expected output: Array ["Jan", "Feb", "March", "April", "June"]
$months = #( "Jan", "Feb", "March", "April", "June" );
splice $months 4 1 "May";
# replaces 1 element at index 4
# expected output: Array ["Jan", "Feb", "March", "April", "May"]
Note that it doesn't mutate the existing array in place though, but you can simulate that by assigning the result back to the variable - e.g.:
$months = #( "Jan", "March", "April", "June" );
$months = splice $months 1 0 "Feb";
There's probably some nicer things you can do with the ValueFromPipeline parameter attribute to allow you to pipe a variable in, but it's well past my bedtime here...

Using multiple commands in the <init> part of a Powershell for loop

I get an error when trying to use multiple commands in the <Init> part of a for loop in Powershell. For example,
function Example {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)] [int] $Base,
[Parameter(Mandatory=$True)] [int] $Count
)
Process {
for ( $item = 1, $id = $Base; $item -le $Count; $id++, $item++ ) {
}
}
}
Example -Base 1 -Count 2
The Microsoft documentation says that <Init> "represents one or more commands" and that <Repeat> "represents one or more commands, separated by commas". The wording is different, so I realize that the syntax may be different.
The error I get is "The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property." with the underscore beneath the 1 in "$item = 1".
You have to wrap the <init> component of your for loop either with Subexpression operator $(..):
for($($item1 = 0; $item2 = 10); $item1 -lt 10 -or $item2 -gt 0; $item1++, $item2--) {
[pscustomobject]#{
item1 = $item1
item2 = $item2
}
}
Or, as Abraham points out in his comment, each variable assignment wrapped with parentheses: ($item1 = 0), ($item2 = 10).
Following your example function, this should work:
function Example {
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[int] $Base,
[Parameter(Mandatory)]
[int] $Count
)
Process {
for (($item = 1), ($id = $Base); $item -le $Count; $id++, $item++) {
[pscustomobject]#{
item = $item
id = $id
}
}
}
}
Example -Base 4 -Count 10

Powershell break a long array into a array of array with length of N in one line?

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
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.
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]
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
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
#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
$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 (.
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