I have 2 arrays like this:
$array1 = #('Item10', 'Item20', 'Item30', 'Item40')
$array2 = #('Item1', 'Item3')
I would like to have my final array like this:
$arrayfinal = #('Item30', 'Item40')
meaning: find 'like' Item1 from array2 in array1, so remove it !
I tried: with the '-like' operator, the '-ne' operator, tried to add '*' and use the 'like' operator but could not find what I want.
Thanks for your help
You need to test each value in $array1 against each possible prefix from $array2 until you get a match (or not) - in other words, nested filters.
My preferred approach is to nest .Where({...}, 'First') inside Where-Object:
$arrayfinal = $array1 |Where-Object {$item = $_; -not $array2.Where({$item -like "${_}*"}, 'First')}
The 'First' mode will make .Where() return as soon as the first item satisfies the condition, thereby not needlessly going through all of $array2 on each iteration.
Related
I have two list, A and B and I needed the element of the B list not present in the A list to be in a created list C in Powershell.
(list or array)
A
B
C
One
One
Second
Two
Two
Three
Three
Third
Second
Fourth
third
In this example in C there is the word Second as it is not present in A list.
So it is not the difference between two list, it is one to another, but not the other way around.
I have tested the Compare-Object command but it will exclude the element that are not in B but in A and the ones that are in B but not in A. Which is not what I am looking for.
I also tried something like this :
foreach ($elem in $A) { if ($B -contains $elem) { "there is a match" } }
But it didn't work as I want.
I'm a bit lost a this point.
Use the -notin operator:
$C = #($B) -notin $A
Supplying an enumerable left-hand operand to a scalar comparison operator like -notin turns the operator into a filter - and suddenly it works just like $B |Where-Object { $_ -notin $A }
So finally I decided to use the following command:
Compare-Object -IncludeEqual -ReferenceObject $A -DifferenceObject $B
With the side indicator, I could manage my third table the way I want.
I encountered some issue in converting my existing vbs script to PowerShell script. I have illustrate here with some dummy codes instead of my original code. In example 1, I only have 1 set of elements in the array, upon return the array variable to the function, it will only display P.
However in example 2, where I have 2 set of elements in the array, upon return the array variable to the function, it will display the elements properly.
If you print the array inside the function in example 1 and 2. There isn't any issue in getting the results.
I have googled and not able to find any solution to it. Many thanks in advance for the kind help.
Example 1:
function testArray {
$array1 = #()
$array1 += ,#("Apple","Banana")
return $array1
}
$array2 = testArray
Write-Host $array2[0][1]
Result is "P".
Example 2:
function testArray {
$array1 = #()
$array1 += ,#("Apple","Banana")
$array1 += ,#("Orange","Pineapple")
return $array1
}
$array2 = testArray
Write-Host $array2[0][0]
Result is "Apple".
PowerShell unrolls arrays returned from a function. Prepend the returned array with the comma operator (,, unary array construction operator) to wrap it in another array, which is unrolled on return, leaving the nested array intact.
function testArray {
$array1 = #()
$array1 += ,#("Apple","Banana")
return ,$array1
}
when you declare single line array like
$array1 = "Apple","Banana"
when you call :
$array1[0][1]
this will happen :
this code
function testArray {
$array1 = #()
$array1 += ,#("Apple","Banana")
return $array1
}
$array2 = testArray
Write-Host $array2[0][1]
exact the same of this:
$array1 = "Apple","Banana"
but when you declare 2 row of array like :
function testArray {
$array1 = #()
$array1 += ,#("Apple","Banana")
$array1 += ,#("Orange","Pineapple")
return $array1
}
$array2 = testArray
Write-Host $array2[0][0]
this will happen :
if you need apple in your first code just call array[0] not array[0][0]. array[0][0] return char for you.
sorry for my bad english i hope you understand
$array1 inside your function TestArray is always of rank 1, fixed, and has never an element with type array. Check how a + is defined on arrays.
In this case you want to use an ArrayList:
$array1=[Collections.ArrayList]::new(10) # 10 is capacity
After that you can use the Add function. Even with arrays. And this time they will not be flattened, as was the case with the + array operator.
Other dynamic collection types also exist. But this one looks ok for many purposes.
I'm attempting to ensure an array of unknown length is split in one-or-more arrays with a maximum of 20 elements.
I've searched around and found several answers for similar questions based on arrays containing number sequences (or maybe I'm just missing something), but nothing for an array of strings.
I've come up with a solution, but it seems a little convoluted and I feel it could probably be better.
For example, take this array -
$metricDefinitionsHash= [ordered]#{"this one" = "111"; "that one" = "222"; "another one" = "333"}
And for the sake of this example, that's say I want only a maximum of two elements -
$metricsQueryParts = #()
$counter = 0
$metricDefinitionsHash.Keys | ForEach-Object {
$index = [System.Math]::Floor($counter / 2)
if ($metricsQueryParts.Length -eq $index)
{
$metricsQueryParts += ""
$metricsQueryParts[$index] = #()
}
$metricsQueryParts[$index] += $_
$counter++
}
So now I have an array of arrays -
#(#("this one", "that one"), #("another one"))
But... Can I achieve my goal in a more efficient way?
Use a for loop, the range operator (..), and the unary array construction operator (,):
[array]$keys = $metricDefinitionsHash.Keys
$size = 3
$metricsQueryParts = #()
for ($i=0; $i -lt $keys.Count; $i+=$size) {
$metricsQueryParts += ,$keys[$i..($i+$size-1)]
}
[array]$keys casts the key/value collection .Keys to a generic array (so that the index operator can be used on it).
The expression $i..($i+$size-1) gives you the indexes from $i up to the index before $i+$size, i.e. the next $size elements from the array starting at position $i. If there are less than $size elements the expression will give just the remaining number of elements.
The unary array construction operator ensures that $keys[...] is appended to $metricsQueryParts as a nested array instead of just concatenating the two arrays.
I really like Ansgar's solution but the keys collection isn't an array and as such you can't use the array range syntax. Keys is an OrderedDictionaryKeyValueCollection. I have used out-string -stream to force the OrderedDictionaryKeyValueCollection to an array.
$keys = $metricDefinitionsHash.Keys | out-string -stream
$size = 2
$metricsQueryParts = #()
for ($i=0; $i -lt $keys.Count; $i+=$size) {
$metricsQueryParts += ,$keys[$i..($i+$size-1)]
}
I want to create an empty array of arrays in Powershell to hold "tuples" of values (arrays are immutable).
Therefore I try something like:
The type of $arr is Object[]. I've read that += #(1,2) appends the given element (i.e. #(1,2)) to $arr (actually creates a new array). However, in this case it seems that the arrays are concatenated, why?
$arr = #()
$arr += #(1,2)
$arr.Length // 2 (not 1)
If I do as follows, it seems that $arr contains the two arrays #(1,2),#(3,4), which is what I want:
$arr = #()
$arr += #(1,2),#(3,4)
$arr.Length // 2
How do I initialize an empty array of arrays, such that I can add one subarray at a time, like $arr += #(1,2)?
The answer from Bruce Payette will work. The syntax seems a bit awkward to me, but it does work. At least it is not Perl.
Another way to do this would be with an ArrayList. To me, this syntax is more clear and more likely to be understood by another developer (or myself) in six months.
[System.Collections.ArrayList]$al = #()
$al.Add(#(1,2))
$al.Add(#(3,4))
foreach ($e in $al) {
$e
$e.GetType()
}
The + operator concatenates arrays. To add an array as a single element, prefix the element to add with a comma. Something like #() + , (1,2) + , (3, 4).
As far as I can tell, you can't do it natively in PowerShell, nor can you do it with the [System.Array] type. In both cases, you seem to need to define both the length and the type of the array. Because I wanted a completely empty array of arrays, with the ability to store any type of value in the array, I did it this way.
$x=[System.Collections.ArrayList]::new()
$x.Count
$x.GetType()
$x.Add([System.Collections.ArrayList]::new()) > $null
$x.Count
$x[0].Count
$x[0].GetType()
$x[0].Add("first element") > $null
$x.Count
$x[0].Count
$x[0][0]
I have an array and when I try to append a string to it the array converts to a single string.
I have the following data in an array:
$Str
451 CAR,-3 ,7 ,10 ,0 ,3 , 20 ,Over: 41
452 DEN «,40.5,0,7,0,14, 21 , Cover: 4
And I want to append the week of the game in this instance like this:
$Str = "Week"+$Week+$Str
I get a single string:
Week16101,NYG,42.5 ,3 ,10 ,3 ,3 , 19 ,Over 43 102,PHI,- 1,14,7,0,3, 24 , Cover 4 103,
Of course I'd like the append to occur on each row.
Instead of a for loop you could also use the Foreach-Object cmdlet (if you prefer using the pipeline):
$str = "apple","lemon","toast"
$str = $str | ForEach-Object {"Week$_"}
Output:
Weekapple
Weeklemon
Weektoast
Another option for PowerShell v4+
$str = $str.ForEach({ "Week" + $Week + $_ })
Something like this will work for prepending/appending text to each line in an array.
Set array $str:
$str = "apple","lemon","toast"
$str
apple
lemon
toast
Prepend text now:
for ($i=0; $i -lt $Str.Count; $i++) {
$str[$i] = "yogurt" + $str[$i]
}
$str
yogurtapple
yogurtlemon
yogurttoast
This works for prepending/appending static text to each line. If you need to insert a changing variable this may require some modification. I would need to see more code in order to recommend something.
Another solution, which is fast and concise, albeit a bit obscure.
It uses the regex-based -replace operator with regex '^' which matches the position at the start of each input string and therefore effectively prepends the replacement string to each array element (analogously, you could use '$' to append):
# Sample array.
$array = 'one', 'two', 'three'
# Prepend 'Week ' to each element and create a new array.
$newArray = $array -replace '^', 'Week '
$newArray then contains 'Week one', 'Week two', 'Week three'
To show an equivalent foreach solution, which is syntactically simpler than a for solution (but, like the -replace solution above, invariably creates a new array):
[array] $newArray = foreach ($element in $array) { 'Week ' + $element }
Note: The [array] cast is needed to ensure that the result is always an array; without it, if the input array happens to contain just one element, PowerShell would assign the modified copy of that element as-is to $newArray; that is, no array would be created.
As for what you tried:
"Week"+$Week+$Str
Because the LHS of the + operation is a single string, simple string concatenation takes place, which means that the array in $str is stringified, which by default concatenates the (stringified) elements with a space character.
A simplified example:
PS> 'foo: ' + ('bar', 'baz')
foo: bar baz
Solution options:
For per-element operations on an array, you need one of the following:
A loop statement, such as foreach or for.
Michael Timmerman's answer shows a for solution, which - while syntactically more cumbersome than a foreach solution - has the advantage of updating the array in place.
A pipeline that performs per-element processing via the ForEach-Object cmdlet, as shown in Martin Brandl's answer.
An expression that uses the .ForEach() array method, as shown in Patrick Meinecke's answer.
An expression that uses an operator that accepts arrays as its LHS operand and then operates on each element, such as the -replace solution shown above.
Tradeoffs:
Speed:
An operator-based solution is fastest, followed by for / foreach, .ForEach(), and, the slowest option, ForEach-Object.
Memory use:
Only the for option with indexed access to the array elements allows in-place updating of the input array; all other methods create a new array.[1]
[1] Strictly speaking, what .ForEach() returns isn't a .NET array, but a collection of type [System.Collections.ObjectModel.Collection[psobject]], but the difference usually doesn't matter in PowerShell.