I am experimenting on two-dimensional arrays. I want some sort of a matrix, all with the same character. I can define a blank multi-dimensional array with fixed elements, and supply it with a character using loops. However, I can also do #(something)*n to directly define an array already supplied with something.
From what I understood so far, this is how to do it:
> $arr = ,(,'E'*3)*3
These seems alright:
> $arr[1]
E
E
E
> $arr[1][2]
E
But when I try to replace a character somewhere, like $arr[1][2] = 'D', many characters are replaced:
> $arr
E
E
D
E
E
D
E
E
D
Is my array definition wrong? Added: Then, how to correctly define it 'quickly'?
Using the * operator on non-numeric values creates copies of the original value. However, if the item you're copying isn't of a primitive(-ish) type like String or Char, the result will not be a duplicate of that object, but a copy of the object reference. Since all instances will then be pointing to the same object, changing one will change all.
To create distinct instances you need to repeat the array instantiation in a loop, as PetSerAl showed in the comments:
$arr = 1..3 | ForEach-Object { ,(,'E' * 3) }
In this particular case you could also create a "template" array and clone it:
$a0 = ,'E' * 3
$arr = 1..3 | ForEach-Object { ,$a0.Clone() }
Note, however, that cloning an object will not clone nested object references, so the latter is not a viable approach in all scenarios.
Something like this won't work the way you intend (because the references of the nested hashtable objects are still pointing to the same actual hashtables after cloning the array object):
PS C:\> $a0 = ([PSCustomObject]#{'x'='E'}),([PSCustomObject]#{'x'='E'})
PS C:\> $arr = 1..2 | ForEach-Object { ,$a0.Clone() }
PS C:\> $arr
x
-
E
E
E
E
PS C:\> $arr[1][1].x = 'F'
PS C:\> $arr
x
-
E
F
E
F
But something like this will work:
PS C:\> $arr = 1..2 | ForEach-Object { ,(([PSCustomObject]#{'x'='E'}),([PSCustomObject]#{'x'='E'})) }
PS C:\> $arr
x
-
E
E
E
E
PS C:\> $arr[1][1].x = 'F'
PS C:\> $arr
x
-
E
E
E
F
Related
I have a PowerShell script that fails if only 1 string is fed to an array because it splits it into characters when using Get-Unique and/or Sort-Object. However, if multiple values are provided then it works as expected. So for example:
Expected behavior:
PS X:\> $t = #("asd","bcd") | Get-Unique
PS X:\> $t[0]
asd
PS X:\> $t[1]
bcd
Unexpected (with 1 value):
PS X:\> $t = #("asd") | Get-Unique
PS X:\> $t[0]
a
PS X:\> $t[1]
s
PS X:\> $t[2]
d
Could someone explain why this is happening and how to prevent it?
I'd appreciate any input as my searches did not bring any luck.
Thanks
Get-Unique doesn't split anything - it just returns the one string value as is, and as a result, $t now contains a scalar string, not an array:
PS ~> $t = "asd" |Get-Unique
PS ~> $t.GetType().FullName
System.String
PS ~> $t
asd
But as soon as you try to access the string value with the index accessor [], it returns the individual [char] value found at the given index.
If you want to ensure the output from Get-Unique (or Sort-Object or any other command that might return 0, 1, or more objects as output), wrap the pipeline in the array subexpression operator #():
PS ~> $t = #( "asd" | Get-Unique )
PS ~> $t[0]
asd
The assignment operator = “Sets the value of a variable to the specified value” as said in the reference doc . Not surprisingly, changes on a variable (in my case an array) that has been previously assigned to another variable do not affect the value of this latter one.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before new value of V: $A"
A before new value of V: a b c
PS C:\> $V="e","f"
PS C:\> write-host "A after new value of V: $A"
A after new value of V: a b c
PS C:\>
But when the method clear() is used the behaviour seems different.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before clearing V: $A"
A before clearing V: a b c
PS C:\> $V.clear()
PS C:\> write-host "A after clearing V: $A"
A after clearing V:
PS C:\>
It seems that clear() method applied on $V acts also on $A. As if the assignment were by reference, strange enough, only for this method. In fact, if a new value is assigned to $V after having cleared it, $A is still affected only by the clear.
PS C:\> $V="a", "b", "c"
PS C:\> $A=$V
PS C:\> write-host "A before clearing V: $A"
A before clearing V: a b c
PS C:\> $V.clear()
PS C:\> $V="e","f"
PS C:\> write-host "A after clearing V: $A"
A after clearing V:
PS C:\>
There are possibilities to avoid this effect, although not precisely identical: $A=$V.clone() or use of cmdlet Clear-Variable -name V or $V=$null instead $V.clear() or perhaps others better than these that somebody could suggest.
But my question is:
how to explain the "propagation" of the effect of clear method on $V to the other array $A?
Tests have been done on PS ver.5.1.
Use Clone() method to get true copy of an array.
$V = "a", "b", "c"
$A = $V.Clone()
Write-Host "A before new value of V: $A"
$V = "e","f"
Write-Host "A after new value of V: $A"
A before new value of V: a b c
A after new value of V: a b c
For explanation, read Copying Arrays and Hash Tables:
Copying arrays or hash tables from one variable to another works, but
may produce unexpected results. The reason is that arrays and hash
tables are not stored directly in variables, which always store only a
single value. When you work with arrays and hash tables, you are
dealing with a reference to the array or hash table. So, if you
copy the contents of a variable to another, only the reference will be
copied, not the array or the hash table. That could result in the
following unexpected behavior:
$array1 = 1,2,3
$array2 = $array1
$array2[0] = 99
$array1[0]
99
Although the contents of $array2 were changed in this example, this
affects $array1 as well, because they are both identical. The
variables $array1 and $array2 internally reference the same
storage area. Therefore, you have to create a copy if you want to
copy arrays or hash tables,:
$array1 = 1,2,3
$array2 = $array1.Clone()
$array2[0] = 99
$array1[0]
1
Whenever you add new elements to an array (or a hash table) or remove
existing ones, a copy action takes place automatically in the
background and its results are stored in a new array or hash table.
The following example clearly shows the consequences:
# Create array and store pointer to array in $array2:
$array1 = 1,2,3
$array2 = $array1
# Assign a new element to $array2. A new array is created in the process and stored in $array2:
$array2 += 4
$array2[0]=99
# $array1 continues to point to the old array:
$array1[0]
1
BTW, you can meet the terms Value type, Reference type and Pointer type more often…
For me what you see is normal, I try to explain myself :
$V="a", "b", "c"
$a = $V
now $a contains the same reference as $V. if you clear $V then $a is cleared.
now if you write $V = "b","c", you affect $V with the reference of a new tab. and this reference is not the same that you affect to $a. so now if you clear $V, $a is not cleared.
Am I clear enought ?
$readFile = get-content $readInput
#create an empty array to be filled with bank account numbers
$fNameArray = #()
for($i = 0; $i -lt $readFile.length; $i++){
#assigns a random letter from the list to $letter.
#$letter = get-random -inputobject ("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z") -count $readFile.length
$letter = $readFile[$i] | foreach-object{get-random -inputobject ("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z") -count $readFile[$i].length}
$fnameArray += "$letter"
}
$fnameArray
the code is reading in a file that has a list of names and randomizing the letters for Data Masking. The only problem I am running into is the output is like such:
L R Y E B
R O M I
U Q N G R
H K Y
M G A W Q
J G W Y D K T
X E Q
J Y P I G
It looks like it is output with spaces between the letters. How do I eliminate them?
The unary form of the -join operator joins (concatenates) all array elements without a separator.
> -join ('a', 'b', 'c')
abc
Therefore, simply use:
$fnameArray += -join $letter
By contrast, "$letter" stringifies the array using $OFS (the output field separator) as the separator, which defaults to a space, which explains your output.
Therefore, you could alternatively set $OFS to '' (the empty string) and use "$letter".
However, the -join approach is simpler and doesn't require you to create a local scope for / restore the previous $OFS value.
$array=#("blue","green","black")
[string]$input=Read-host "Input:"
$array[$input]
If I enter the number 1 into the input this gives no output. When I
Write-host $input
I get
System.Collections.ArrayList+ArrayListEnumeratorSimple
What I'm looking to do next is:
$inputlist=#("0")
$inputlist += $array[$input]
But I seem to end up with an array where each element is a single letter. I would like them to be one string in $inputlist[1].
$Input is an automatic variable and shouldn't be used in the way you do. Give your variable a different name and the problem will disappear:
PS C:\> $array = #('blue', 'green', 'black')
PS C:\> $val = Read-host 'Input'
Input: 2
PS C:\> $val
2
PS C:\> $val.GetType().FullName
System.String
PS C:\> $array[$val]
green
Splitting the string into an array of single characters can be handled by casting the string to a character array and then to a string array:
PS C:\> [string[]][char[]]$array[$val]
g
r
e
e
n
You'd still be able to append the characters to an array if you cast the string just to char[] (without casting it to string[] afterwards), but then you'd have an array with mixed types:
PS C:\> $inputList = #('0')
PS C:\> $inputList += [char[]]$array[$val]
PS C:\> $inputList
0
g
r
e
e
n
PS C:\> $inputList[0].GetType().FullName
System.String
PS C:\> $inputList[1].GetType().FullName
System.Char
If you want the entire string as the second element of the array, your existing code should already do that:
PS C:\> $inputList = #('0')
PS C:\> $inputList += $array[$val]
PS C:\> $inputList
0
green
I'm trying to figure out the best way to get unique combinations from a powershell array. For instance, my array might be
#(B,C,D,E)
I would be hoping for an output like this :
B
C
D
E
B,C
B,D
B,E
C,D
C,E
D,E
B,C,D
C,D,E
B,C,D,E
I do not want re-arranged combos. If combo C,D exists already then I do not want combo D,C. It's redundant for my purposes.
I looked into the functions here : Get all combinations of an array
But they aren't what I want. I've been working on figuring this out myself, but have spent quite a bit of time without success. I thought I'd ask the question here so that if someone else already know I'm not wasting my time.
Thanks!
This is an adaptation from a solution for a C# class I took that asked this same question. For any set find all subsets, including the empty set.
function Get-Subsets ($a){
#uncomment following to ensure only unique inputs are parsed
#e.g. 'B','C','D','E','E' would become 'B','C','D','E'
#$a = $a | Select-Object -Unique
#create an array to store output
$l = #()
#for any set of length n the maximum number of subsets is 2^n
for ($i = 0; $i -lt [Math]::Pow(2,$a.Length); $i++)
{
#temporary array to hold output
[string[]]$out = New-Object string[] $a.length
#iterate through each element
for ($j = 0; $j -lt $a.Length; $j++)
{
#start at the end of the array take elements, work your way towards the front
if (($i -band (1 -shl ($a.Length - $j - 1))) -ne 0)
{
#store the subset in a temp array
$out[$j] = $a[$j]
}
}
#stick subset into an array
$l += -join $out
}
#group the subsets by length, iterate through them and sort
$l | Group-Object -Property Length | %{$_.Group | sort}
}
Use like so:
PS C:>Get-Subsets #('b','c','d','e')
b
c
d
e
bc
bd
be
cd
ce
de
bcd
bce
bde
cde
bcde
Note that computational costs go up exponentially with the length of the input array.
Elements SecondstoComplete
15 46.3488228
14 13.4836299
13 3.6316713
12 1.2542701
11 0.4472637
10 0.1942997
9 0.0867832
My tired attempt at this. I did manage to get it to produce the expected results but how it does it is not as elegant. Uses a recursive functionality.
Function Get-Permutations{
Param(
$theInput
)
$theInput | ForEach-Object{
$element = $_
$sansElement = ($theInput | Where-Object{$_ -ne $element})
If($sansElement.Count -gt 1){
# Build a collection of permutations using the remaining elements that were not isolated in this pass.
# Use the single element since it is a valid permutation
$perms = ,$element
For($elementIndex = 0;$elementIndex -le ($sansElement.Count - 1);$elementIndex++){
$perms += ,#(,$element + $sansElement[0..$elementIndex] | sort-object)
}
# For loop does not send to output properly so that is the purpose of collecting the results of this pass in $perms
$perms
# If there are more than 2 elements in $sansElement then we need to be sure they are accounted for
If($sansElement -gt 2){Get-Permutations $sansElement}
}
}
}
Get-Permutations B,C,D,E | %{$_ -join ","} | Sort-Object -Unique
I hope I can explain myself clearly....So each pass of the function will take an array. Each individual element of that array will be isolated from the rest of the array which is represented by the variables $element and $sansElement.
Using those variables we build individual and progressively larger arrays composing of those elements. Let this example show using the array 1,2,3,4
1
1,2
1,2,3
1,2,3,4
The above is done for each "number"
2
2,1
2,1,3
2,1,3,4
and so forth. If the returned array contains more that two elements (1,2 would be the same as 2,1 in your example so we don't care about pairs beyond one match) we would take that array and run it through the same function.
The real issue is that the logic here (I know this might be hard to swallow) creates several duplicates. I suppose you could create a hashtable instead which I will explore but it does not remove the logic flaw.
Regardless of me beating myself up as long as you don't have thousands of elements the process would still produce results.
Get-Permutations would return and array of arrays. PowerShell would display that one element per line. You asked for comma delimited output which is where -join comes in. Sort-Object -Unique takes those sorted string an discards the duplicates.
Sample Output
B
B,C
B,C,D
B,C,D,E
B,C,E #< Missing from your example output.
B,D
B,D,E #< Missing from your example output.
B,E
C
C,D
C,D,E
C,E
D
E