Convert Array of Numbers into a String of Ranges - powershell

I was asking myself how easily you could convert an Array of Numbers Like = 1,2,3,6,7,8,9,12,13,15 into 1 String that "Minimizes" the numbers, so Like = "1-3,6-9,12-13,15".
I am probably overthinking it because right now I don't know how I could achieve this easily.
My Attempt:
$newArray = ""
$array = 1,2,3,6,7,8,9,12,13,15
$before
Foreach($num in $array){
If(($num-1) -eq $before){
# Here Im probably overthinking it because I don't know how I should continue
}else{
$before = $num
$newArray += $num
}
}

This should working, Code is self explaining, hopefully:
$array = #( 1,2,3,6,7,8,9,12,13,15 )
$result = "$($array[0])"
$last = $array[0]
for( $i = 1; $i -lt $array.Length; $i++ ) {
$current = $array[$i]
if( $current -eq $last + 1 ) {
if( !$result.EndsWith('-') ) {
$result += '-'
}
}
elseif( $result.EndsWith('-') ) {
$result += "$last,$current"
}
else {
$result += ",$current"
}
$last = $current
}
if( $result.EndsWith('-') ) {
$result += "$last"
}
$result = $result.Trim(',')
$result = '"' + $result.Replace(',', '","') +'"'
$result

I have a slightly different approach, but was a little too slow to answer. Here it is:
$newArray = ""
$array = 1,2,3,6,7,8,9,12,13,15
$i = 0
while($i -lt $array.Length)
{
$first = $array[$i]
$last = $array[$i]
# while the next number is the successor increment last
while ($array[$i]+1 -eq $array[$i+1] -and ($i -lt $array.Length))
{
$last = $array[++$i]
}
# if only one in the interval, output that
if ($first -eq $last)
{
$newArray += $first
}
else
{
# else output first and last
$newArray += "$first-$last"
}
# don't add the final comma
if ($i -ne $array.Length-1)
{
$newArray += ","
}
$i++
}
$newArray

Here is another approach to the problem. Firstly, you can group elements by index into a hashtable, using index - element as the key. Secondly, you need to sort the dictionary by key then collect the range strings split by "-" in an array. Finally, you can simply join this array by "," and output the result.
$array = 1, 2, 3, 6, 7, 8, 9, 12, 13, 15
$ranges = #{ }
for ($i = 0; $i -lt $array.Length; $i++) {
$key = $i - $array[$i]
if (-not ($ranges.ContainsKey($key))) {
$ranges[$key] = #()
}
$ranges[$key] += $array[$i]
}
$sequences = #()
$ranges.GetEnumerator() | Sort-Object -Property Key -Descending | ForEach-Object {
$sequence = $_.Value
$start = $sequence[0]
if ($sequence.Length -gt 1) {
$end = $sequence[-1]
$sequences += "$start-$end"
}
else {
$sequences += $start
}
}
Write-Output ($sequences -join ",")
Output:
1-3,6-9,12-13,15

Related

How would you generate a unique sequence from 000 to 9ZZ

How would you generate a unique sequence from 000 to 9ZZ. Lastly, my export-csv is not working. Please see the data and export output below.
The alphanumeric sequence starts from 0 through 9 and then A through Z.
Please be advised, my PowerShell skill are a bit new. :)
$i = #()
$a = 0..9
$b = 65..90 | Foreach{[Char]$_}
$i = $a + $b
For($d = 0; $d -le $i.count; $d++){
$g = $i[$d]
For($e = 0; $e -le $i.count; $e++){
$h = $i[$e]
For($f = 0; $f -le $i.count; $f++){
$j = $i[$f]
$k = "{0}{1}{2}" -f $g, $h, $j
$k #| Export-Csv -Path .\List.csv -NoTypeInformation -Append
If($k -eq '9ZZ'){
Break
}
}
}
}
Data Output:
000
001
.
.
|
V
009
00A
.
.
00Z
00 <-- I don't get this.
010
Export:
Length
3 <-- I don't get this either.
.
.
|
v
3
Any and all help is appreciated. Thank you in Advanced. ;)
I would do it this way, with 3 foreach loops and a labeled break:
$digits = 0..9
$chars = (65..90).ForEach([char])
$dict = $digits + $chars
$result = :outer foreach($i in $dict)
{
foreach($x in $dict)
{
foreach($z in $dict)
{
'{0}{1}{2}' -f $i,$x,$z
if($i -eq 9 -and $x -eq 'Z' -and $z -eq 'Z')
{
break outer
}
}
}
}
$result | Export-Csv ...
This is a classic off-by-1 error - your loops run from 0 through $i.count (included), which is exactly 1 longer than $i.
Change -le $i.count to -lt $i.count in all 3 loop conditions and it'll work.
You could simplify your code by generating the full range of digits/characters up front a little differently, and then use 3 nested foreach loops instead:
$digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.ToCharArray()
$ranges =
:outerLoop
foreach($a in $digits){
foreach($b in $digits){
foreach($c in $digits){
# save and output new value
($label = "${a}${b}${c}")
# exit the label generation completely if we've reached the desired upper boundary
if($label -eq '9ZZ'){ break outerLoop }
}
}
}
$ranges now contain the correct range of labels from 000 through 9ZZ
To complement the helpful existing answers with a generalized solution for producing permutations of characters with a given number of places, using recursive helper function Get-Permutations:
function Get-Permutations {
param(
[Parameter(Mandatory)]
[string] $Chars, # e.g. '0123456789'
[Parameter(Mandatory)]
[uint] $NumPlaces # e.g. 2, to produce '00', '01', ..., '99'
)
switch ($NumPlaces) {
0 { return }
1 { [string[]] $Chars.ToCharArray() }
default {
Get-Permutations -Chars $Chars -NumPlaces ($NumPlaces-1) | ForEach-Object {
foreach ($c in $chars.ToCharArray()) { $_ + $c }
}
}
}
}
You'd call it as follows (to get '000', '001', ..., but all the way up to 'ZZZ' - you can post-filter with ... | Where-Object { $_ -le '9ZZ' })
Get-Permutations '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' 3
Note:
In PowerShell (Core) 7+ you can create the string of characters with
-join ('0'..'9' + 'A'..'Z')
In Windows PowerShell, where you cannot use [char] instances with the range operator, .., you can use the following, more concise alternative to your approach:
-join ([char[]] (48..57) + [char[]] (65..90)), based on the chars.' code points obtained with, e.g. [int] [char] 'A'
similarly you could cheat since '009' -le '00A'
$sb = [System.Text.StringBuilder]::new()
function inc([string]$s){
[byte[]]$v = $s.ToCharArray()
$radix = $v.Count-1
$carry = $true
while($carry -and $radix -ge 0){
$carry = $false
switch(++$v[$radix]){
91{$v[$radix--] = 48; $carry = $true}
58{$v[$radix] = 65}
}
}
$null = $sb.clear()
if($carry){$null = $sb.Append('1')}
return $sb.Append([char[]]$v).ToString()
}
Describe 'Test inc function' {
It 'increments 000' {inc '000' | should be '001'}
It 'increments 001' {inc '001' | should be '002'}
It 'increments 008' {inc '008' | should be '009'}
It 'increments 009' {inc '009' | should be '00A'}
It 'increments 00A' {inc '00A' | should be '00B'}
It 'increments 00Y' {inc '00Y' | should be '00Z'}
It 'increments 00Z' {inc '00Z' | should be '010'}
It 'increments 010' {inc '010' | should be '011'}
It 'increments 0ZZ' {inc '0ZZ' | should be '100'}
It 'increments 1ZZ' {inc '1ZZ' | should be '200'}
It 'increments ZZZ' {inc 'ZZZ' | should be '1000'}
}
measure-command {for($i = '000'; $i -le '9ZZ'; $i = inc $i){$i}}
&{for($i = '000'; $i -le '9ZZ'; $i = inc $i){
$i
}} | set-content -Path .\List.csv
Edit: unnecessarily faster
Inspired by mklement0's awesome answer
$cache = #{}
function Get-Permutations{
param(
[Parameter(Mandatory=$true)][string]$Alphabet # e.g. '0123456789'
,[Parameter(Mandatory=$true)][uint32]$Length # e.g. 2, to produce '00', '01', ..., '99'
)
if($Length -eq 0)
{return}
if($Length -eq 1)
{return $Alphabet.ToCharArray()}
if($Alphabet -NotIn $cache.Keys){
$cache.$Alphabet = new-object String[][] $Length
$cache.$Alphabet[1] = $Alphabet.ToCharArray()
}elseif($cache.$Alphabet.Count -lt $Length){
$tmp = new-object String[][] $Length
[Array]::Copy($cache.$Alphabet,$tmp,$cache.$Alphabet.Count)
$cache.$Alphabet = $tmp
}
Get-Permutations-Helper $Alphabet $Length
}
function Get-Permutations-Helper{
param(
[string]$Alphabet
,[uint32]$Length
)
$TailLength = $Length-1
if($cache.$Alphabet[$TailLength] -eq $null){
$cache.$Alphabet[$TailLength] = Get-Permutations-Helper $Alphabet $TailLength
}
foreach($head in $Alphabet.ToCharArray()){
foreach($tail in $cache.$Alphabet[$TailLength]){
$head + $tail
}
}
}
Describe 'Test Get-Permutations function' {
It 'Makes an ordered ABC,2 permutation' {
$valid = #('AA','AB','AC','BA','BB','BC','CA','CB','CC')
$cache = #{}; $i = 0
Get-Permutations 'ABC' 2 | foreach-object {
$_ | should be $valid[$i++]
}
}
It 'Extends the cache' {
$cache = #{}
$null = Get-Permutations 'ABC' 2
$cache.'ABC'[1][0] | should be 'A'
$cache.'ABC'[1][0] = 'test'
$results = Get-Permutations 'ABC' 3
$cache.'ABC'[2][0] | should be 'Atest'
$results[0] | should be 'AAtest'
}
}
$digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
$spin = 10
$ttl=$null;for($i=0;$i -lt $spin;$i++){
$cache = #{}
$ttl+=measure-command {
Get-Permutations $digits 3
}
};$ttl;$ttl.TotalMilliseconds/$spin
$ttl=$null;for($i=0;$i -lt $spin;$i++){
$cache = #{}
$ttl+=measure-command {
$enu = (Get-Permutations $digits 3).GetEnumerator();
while($enu.MoveNext() -and ($v = $enu.Current) -le '9ZZ'){$v}
}
};$ttl;$ttl.TotalMilliseconds/$spin
$enu = (Get-Permutations $digits 3).GetEnumerator();
&{while($enu.MoveNext() -and ($v = $enu.Current) -le '9ZZ'){
$v
}} | set-content -Path .\List.csv

How to remove the last comma in powershell?

Below is my script and I want to pass the value if not null to my $weekDays variable in comma separated format but I want to remove the last comma, so please help me on this.
$a = "sun"
$b = "mon"
$c = $null
$d = $a,$b,$c
$weekDays = $null
Foreach ($i in $d)
{
if ($i)
{
$weekDays = $i
$weekDays = $weekDays + ","
Write-Host "$weekDays"
}
}
Output: sun,mon,
I want: sun, mon
No need to loop through the list yourself since there's already the -join operator for that purpose, but you need to remove the null elements first
($d | Where-Object { $_ -ne $null }) -join ", "
Where-Object (alias where) will filter out the null elements.
If you just want to exclude the last null item then use this
$d[0..($d.Length - 2)] -join ", "
Note that your code produces the below output
sun,
mon,
and not sun,mon, in the same line. To print with new lines like that you need to use
($d | where { $_ -ne $null }) -join ",`n"
$a = "sun"
$b = "mon"
$c = $null
$d = $a,$b,$c
$weekDays = $null
Foreach ($i in $d)
{
if ($i)
{
if (-not ([string]::IsNullOrEmpty($weekDays)))
{
$weekDays += ","
}
$weekDays = $weekDays + $i
}
}
Write-Host "$weekDays"
##Output : sun,mon, I want : sun, mon
Your variable is null at start and you want to prepend a comma in all subsequent cases, that is, when the variable is no longer null.

I would like to loop this array and Insert the Value Array by Array

$number = $args[0]
$myarray = &{$h = #{}; foreach ($a in 1..$number) { $h["array$a"] = ""; } return $h; }
Write-Output $myarray
echo ============================================
if ($myarray["array1"] -eq "") {
$myarray["array1"] = "C:\myscripts\Powershell_testing\FolderA\aa.txt"
Write-Output $myarray
}
elseif ($myarray["array2"] -eq "") {
$myarray["array2"] = "C:\myscripts\Powershell_testing\FolderB\bb.txt"
Write-Output $myarray
}
elseif ($myarray["array3"] -eq "") {
$myarray["array3"] = "C:\myscripts\Powershell_testing\FolderC\cc.txt"
Write-Output $myarray
}
else {
Write-Output "Array is file exist"
}
enter image description here
First of all, you are not dealing with arrays at all. Your code is apparently meant to fill a Hashtable with constructed file paths as values.
Why not use a simple for loop to construct this filepath and add to the hash at the same time?
Something like:
$number = 4
$myHash = #{}
for ($i = 1; $i -le $number; $i++) {
# construct a file path and add that as value to the hashtable
$file = "C:\myscripts\Powershell_testing\Folder{0}\{1}{1}.txt" -f [char](64 + $i), [char](96 + $i)
$myHash["value$i"] = $file
}
$myHash
Output:
Name Value
---- -----
value1 C:\myscripts\Powershell_testing\FolderA\aa.txt
value3 C:\myscripts\Powershell_testing\FolderC\cc.txt
value4 C:\myscripts\Powershell_testing\FolderD\dd.txt
value2 C:\myscripts\Powershell_testing\FolderB\bb.txt
Or if you want it ordered (which a Hashtable by definition is not):
$number = 4
$myHash = [ordered]#{}
for ($i = 1; $i -le $number; $i++) {
# construct a file path and add that as value to the hashtable
$file = "C:\myscripts\Powershell_testing\Folder{0}\{1}{1}.txt" -f [char](64 + $i), [char](96 + $i)
$myHash["value$i"] = $file
}
$myHash
Output:
Name Value
---- -----
value1 C:\myscripts\Powershell_testing\FolderA\aa.txt
value2 C:\myscripts\Powershell_testing\FolderB\bb.txt
value3 C:\myscripts\Powershell_testing\FolderC\cc.txt
value4 C:\myscripts\Powershell_testing\FolderD\dd.txt
[char](64 + $i) and [char](96 + $i) give you the characters you need. ASCII 65 --> A and ASCII 97 --> a, and so on.

How to search imported CSV cell values for each row

I'm attempting to import a csv, search the cell values(columns) in each row and then find and count any null or blank values. Then if the count hits 13, do X. However, when I run this code it appears the cell/column values are a single object and not the individual values for each column? How can I search the individual cell values in the row?
SAMPLE CODE
$DataFileLocation = "\\Server\Output.csv"
$sheet = import-csv $DataFileLocation
$count = 0
foreach ($row in $sheet) {
foreach ($column in $row) {
Write-Host "Searching value: $column"
if ($column -eq $null -or " ") {
Write-Host "Found a blank!"
$count++
}
}
$count
if ($count -eq 13) {
Write-Host "Found match!" -ForegroundColor Red
}
$count = 0
}
Each $row is a PSCustomObject:
PS > $row.GetType().Name
PSCustomObject
Access the underlying property values:
foreach ($column in $row.psobject.Properties.Value){
...
}
Check if $column is $null, empty or white space:
if ([System.String]::IsNullOrWhiteSpace($column)) { ... }
Or something like
$DataFileLocation = "\\Server\Output.csv"
$sheet = import-csv $DataFileLocation
$count = 0
foreach ($row in $sheet){
($row | Get-Member) | ? { $_.MemberType -eq "NoteProperty" } | % {
$n = $_.Name; "$n = $($row."$n")"
}
}

How to get range between 0 and a number with Powershell?

From a number (163 - 1 ) and a scope (20), I try to get ranges (0..19, 20..39, ..., 160..162)
I can get all exept 160..162
$Counter = 163 -1
$Scope = "20"
$Modulo = $Counter % $Scope
$NbrLoop = [Math]::Ceiling($Counter / $Scope)
$j = 0
For($i=0; $i -le $Counter;$i++)
{
If($i % $Scope -eq 0 -and $i -ne 0)
{
$k = $i
$j..--$k
$j = $i
""
}
}
Could you please let me know how to proceed to get last range (160,161,162) ?
modify condition in if to accept last iteration
If($i % $Scope -eq 0 -and $i -ne 0 -or $i -eq $Counter)
however --$k must not be done int that case
$k = If ( $i -eq $Counter ) { $i } else { $i-1 }
$j..$k
Otherwise the number of iterations can be reduced
For($i=0; $i -lt $NbrLoop;$i++)
{
$j = $i * $scope;
$k = If( $i -eq $NbrLoop -1 ) { $Counter } else { ($i+1)*$scope -1 };
$j..$k
""
}