I am trying to find a better way to edit a list of hashtables in powershell. I have successfully looped though the list to find duplicates, but I am not sure how to edit the hashtable and update the list. Does anyone know how to do this at scale?
The test data is formated like this : ( I know it is terrible, but that is the way of the world! )
$TEST_DATA = @(
"CA>TORONTO>MAP-LAYOUT-1", `
"US>NEW-YORK>MAP-LAYOUT-25", `
"US>DENVER>MAP-LAYOUT-2", `
"UK>LODNON>MAP-LAYOUT-2", `
"EU>PARIS>MAP-LAYOUT-25", `
"EU>VENICE>MAP-LAYOUT-2", `
"EU>BERLIN>MAP-LAYOUT-8", `
"EU>PARIS>MAP-LAYOUT-30", `
"EU>VENICE>NEW-MAP-LAYOUT-2", `
"EU>ATHENS>NEW-MAP-LAYOUT-8", `
"CA>TORONTO>NEW-MAP-LAYOUT-1", `
"CA>VANCOUVER>MAP-LAYOUT-1", `
"US>MIAMI>MAP-LAYOUT-25", `
"RU>MOSCOW>MAP-LAYOUT-2", `
"RU>ST-PETERSBURG>MAP-LAYOUT-2", `
"JP>TOKYO>MAP-LAYOUT-25", `
"EU>PARIS>MAP-LAYOUT-100", `
"AU>SYDNEY>MAP-LAYOUT-3", `
"AU>PERTH>MAP-LAYOUT-3", `
"MX>MEXICO-CITY>MAP-LAYOUT-5", `
"MX>TIJAUNA>MAP-LAYOUT-1", `
"CN>SHANGHAI>MAP-LAYOUT-8", `
"CA>CALGARY>MAP-LAYOUT-1"
)
and my little powershell script is like this :
$MAPS = @()
$TEST_DATA | Foreach {
$COUNTRY = ($_).split('>')[0]
$CITY = ($_).split('>')[1]
$MAP = ($_).split('>')[2]
If ( $MAPS.count -gt 0) {
$MAPS | Foreach {
$MAPS_ROW = $_
Write-Host $MAPS_ROW
If (( $MAPS_ROW.COUNTRY -eq $COUNTRY ) -and ( $MAPS_ROW.CITY -eq $CITY )) {
Write-Host '--------' -ForegroundColor Red
Write-Host "CONFLICT DETECTED ! "
write-Host '--------' -ForegroundColor Red
Write-Host ""
Write-Host "CONFLICT MAP : $MAPS_ROW"
Write-Host ""
If (($MAP.lenght -gt $MAPS_ROW.MAP.length) -or ( $MAP.lenght -gt ($MAPS_ROW.MAP.trim("NEW-")).length)) {
$MAPS.Remove($MAPS_ROW)
$NEW_MAP = New-Object PSObject -property @{ COUNTRY="$COUNTRY"; CITY="$CITY"; MAP="$MAP"; }
$MAPS = $NEW_MAP
} else {
Write-Host '--------' -ForegroundColor Yellow
Write-Host "FALSE MAP CONFLICT DETECHED ! $MAPS_ROW "
Write-Host '--------' -ForegroundColor Yellow
}
} else {
Write-Host '--------' -ForegroundColor BLue
Write-Host "NOT IN : 'MAPS'"
Write-Host '--------' -ForegroundColor Blue
}
}
$NEW_MAP = New-Object PSObject -property @{ COUNTRY="$COUNTRY"; CITY="$CITY"; MAP="$MAP"; }
$MAPS = $NEW_MAP
} else {
Write-Host '--------' -ForegroundColor BLue
Write-Host "First MAP Entry"
Write-Host '--------' -ForegroundColor Blue
$NEW_MAP = New-Object PSObject -property @{ COUNTRY="$COUNTRY"; CITY="$CITY"; MAP="$MAP"; }
$MAPS = $NEW_MAP
}
}
$SORTED_MAPS = $MAPS | Sort-Object { $_.COUNTRY }, { $_.CITY }, { $_.MAP } | Select-Object @{n="COUNTRY";e={$_.COUNTRY}}, @{n="CITY";e={$_.CITY}}, @{n="MAP";e={$_.MAP}}
Write-Host '--------' -ForegroundColor Blue
$SORTED_MAPS
Write-Host '--------' -ForegroundColor Blue
and the looping sort of works and I get a nice clean sorted list at the end.
COUNTRY CITY MAP
------- ---- ---
AU PERTH MAP-LAYOUT-3
AU SYDNEY MAP-LAYOUT-3
CA CALGARY MAP-LAYOUT-1
CA TORONTO MAP-LAYOUT-1
CA TORONTO NEW-MAP-LAYOUT-1
CA VANCOUVER MAP-LAYOUT-1
CN SHANGHAI MAP-LAYOUT-8
EU ATHENS NEW-MAP-LAYOUT-8
EU BERLIN MAP-LAYOUT-8
EU PARIS MAP-LAYOUT-100
EU PARIS MAP-LAYOUT-25
EU PARIS MAP-LAYOUT-30
EU VENICE MAP-LAYOUT-2
EU VENICE NEW-MAP-LAYOUT-2
JP TOKYO MAP-LAYOUT-25
MX MEXICO-CITY MAP-LAYOUT-5
MX TIJAUNA MAP-LAYOUT-1
RU MOSCOW MAP-LAYOUT-2
RU ST-PETERSBURG MAP-LAYOUT-2
UK LODNON MAP-LAYOUT-2
US DENVER MAP-LAYOUT-2
US MIAMI MAP-LAYOUT-25
US NEW-YORK MAP-LAYOUT-25
and my problems are :
- How to remove the duplicates from the list?
I tried to sort with the unique switch but coudn't get it to work properly...
- How to make sure that ther is one unique entry for each city with the most correct map name?
I tried to do a check on country and city fields, but that doesn't seem to allow me to add and remove the hashtable from the array.
- Is it better to just create a new arraylist and try to to sort the map bit ?
with conditions like .
If the map contains "new-" or some other tag it has a highest preference. eg: "EU VENICE NEW-MAP-LAYOUT-2 " would be the result for venice.
If the has the highest increment value would be the second highest priority... eg: "EU PARIS MAP-LAYOUT-100 " would be the result for paris.
Thanks in advance for any advice on how to tackle these issues...
Ta,
X
CodePudding user response:
I'd break the code into 2 parts - the first part to process the input lines and extract some extended properties like "IsNew" and "Priority" from the map name, and the second part to apply your selection logic.
That way it's much clearer to see what the "business logic" is for determining the priority of maps, versus the more mechanical code to parse your input.
Part 1 - Pre-process input lines
# convert all the rows first
# (error handling omitted for brevity)
$TEST_MAPS = $TEST_DATA | foreach-object {
$parts = $_.Split(">");
write-output ([pscustomobject] ([ordered] @{
#"TEXT" = $_
"COUNTRY" = $parts[0]
"CITY" = $parts[1]
"MAP" = $parts[2]
#"BASENAME" = if( $parts[2].StartsWith("NEW-") ) { $parts[2].Substring("NEW-".Length) } else { $parts[2] }
"ISNEW" = $parts[2].StartsWith("NEW-")
"PRIORITY" = [int] $parts[2].Substring($parts[2].LastIndexOf("-") 1)
}));
}
If we output $TEST_MAPS
it looks like this:
$TEST_MAPS | ft
COUNTRY CITY MAP ISNEW PRIORITY
------- ---- --- ----- --------
CA TORONTO MAP-LAYOUT-1 False 1
US NEW-YORK MAP-LAYOUT-25 False 25
US DENVER MAP-LAYOUT-2 False 2
UK LODNON MAP-LAYOUT-2 False 2
EU PARIS MAP-LAYOUT-25 False 25
EU VENICE MAP-LAYOUT-2 False 2
EU BERLIN MAP-LAYOUT-8 False 8
EU PARIS MAP-LAYOUT-30 False 30
EU VENICE NEW-MAP-LAYOUT-2 True 2
EU ATHENS NEW-MAP-LAYOUT-8 True 8
CA TORONTO NEW-MAP-LAYOUT-1 True 1
CA VANCOUVER MAP-LAYOUT-1 False 1
US MIAMI MAP-LAYOUT-25 False 25
RU MOSCOW MAP-LAYOUT-2 False 2
RU ST-PETERSBURG MAP-LAYOUT-2 False 2
JP TOKYO MAP-LAYOUT-25 False 25
EU PARIS MAP-LAYOUT-100 False 100
AU SYDNEY MAP-LAYOUT-3 False 3
AU PERTH MAP-LAYOUT-3 False 3
MX MEXICO-CITY MAP-LAYOUT-5 False 5
MX TIJAUNA MAP-LAYOUT-1 False 1
CN SHANGHAI MAP-LAYOUT-8 False 8
CA CALGARY MAP-LAYOUT-1 False 1
Part 2 - Apply map selection logic for each city
All we need to do now is group the maps into cities and decide which map in each group has the highest precedence.
By the way, you didn't specifically say what "wins" if a map with NEW-
has a lower "Priority" than one without - e.g. NEW-MAP-LAYOUT-1
vs MAP-LAYOUT-200
, so for the sake of argument I've assumed "Priority" takes precedence (i.e. MAP-LAYOUT-200
beats NEW-MAP-LAYOUT-1
).
# group the maps by city
$MAP_GROUPS = $TEST_MAPS | group-object -Property COUNTRY, CITY;
# select the "most correct" map name for each group
$MAPS = $MAP_GROUPS | foreach-object {
write-output (
$_.Group
| sort-object -Property PRIORITY, ISNEW -Descending
| select-object -First 1
);
}
which gives the following results:
$MAPS | sort-object -Property COUNTRY, CITY | ft
COUNTRY CITY MAP ISNEW PRIORITY
------- ---- --- ----- --------
AU PERTH MAP-LAYOUT-3 False 3
AU SYDNEY MAP-LAYOUT-3 False 3
CA CALGARY MAP-LAYOUT-1 False 1
CA TORONTO NEW-MAP-LAYOUT-1 True 1
CA VANCOUVER MAP-LAYOUT-1 False 1
CN SHANGHAI MAP-LAYOUT-8 False 8
EU ATHENS NEW-MAP-LAYOUT-8 True 8
EU BERLIN MAP-LAYOUT-8 False 8
EU PARIS MAP-LAYOUT-100 False 100
EU VENICE NEW-MAP-LAYOUT-2 True 2
JP TOKYO MAP-LAYOUT-25 False 25
MX MEXICO-CITY MAP-LAYOUT-5 False 5
MX TIJAUNA MAP-LAYOUT-1 False 1
RU MOSCOW MAP-LAYOUT-2 False 2
RU ST-PETERSBURG MAP-LAYOUT-2 False 2
UK LODNON MAP-LAYOUT-2 False 2
US DENVER MAP-LAYOUT-2 False 2
US MIAMI MAP-LAYOUT-25 False 25
US NEW-YORK MAP-LAYOUT-25 False 25
You'll lose a lot of your imperative-style logging with this approach, but I've assumed that was mainly for debugging your existing code. If you specifically need the logging output in that format then this answer probably won't help much :-).
CodePudding user response:
Mr. E,
I's suggest the use of an array of Custom Objects as follows.
Clear-Host
$Maps = @()
$MapsArgs = @{PropertyNames = "Country", "City", "Map"
Delimiter = '>'}
$Test_Data.GetEnumerator() |
ForEach-Object {
$Obj = $_ | ConvertFrom-String @MapsArgs
$Maps = $obj
}
$Maps2 = $Maps | Sort-Object -Unique -Property Country,City
$Maps2
Output:
Country City Map
------- ---- ---
AU PERTH MAP-LAYOUT-3
AU SYDNEY MAP-LAYOUT-3
CA CALGARY MAP-LAYOUT-1
CA TORONTO NEW-MAP-LAYOUT-1
CA VANCOUVER MAP-LAYOUT-1
CN SHANGHAI MAP-LAYOUT-8
EU ATHENS NEW-MAP-LAYOUT-8
EU BERLIN MAP-LAYOUT-8
EU PARIS MAP-LAYOUT-30
EU VENICE MAP-LAYOUT-2
JP TOKYO MAP-LAYOUT-25
MX MEXICO-CITY MAP-LAYOUT-5
MX TIJAUNA MAP-LAYOUT-1
RU MOSCOW MAP-LAYOUT-2
RU ST-PETERSBURG MAP-LAYOUT-2
UK LODNON MAP-LAYOUT-2
US DENVER MAP-LAYOUT-2
US MIAMI MAP-LAYOUT-25
US NEW-YORK MAP-LAYOUT-25
Be aware that if you have a larger list you will probably want to use something other than the = to build the array. Like a dictionary where you can use an AddItem() property for efficiency.
BTW, you do not need the line continuation characters in your $Test_data the comma's serve to tell PS that more is coming as does the (.
CodePudding user response:
Your array defines a CSV with data delimited by >
, so here's an approach based on that:
$TEST_DATA = "CA>TORONTO>MAP-LAYOUT-1",
"US>NEW-YORK>MAP-LAYOUT-25",
"US>DENVER>MAP-LAYOUT-2",
"UK>LODNON>MAP-LAYOUT-2",
"EU>PARIS>MAP-LAYOUT-25",
"EU>VENICE>MAP-LAYOUT-2",
"EU>BERLIN>MAP-LAYOUT-8",
"EU>PARIS>MAP-LAYOUT-30",
"EU>VENICE>NEW-MAP-LAYOUT-2",
"EU>ATHENS>NEW-MAP-LAYOUT-8",
"CA>TORONTO>NEW-MAP-LAYOUT-1",
"CA>VANCOUVER>MAP-LAYOUT-1",
"US>MIAMI>MAP-LAYOUT-25",
"RU>MOSCOW>MAP-LAYOUT-2",
"RU>ST-PETERSBURG>MAP-LAYOUT-2",
"JP>TOKYO>MAP-LAYOUT-25",
"EU>PARIS>MAP-LAYOUT-100",
"AU>SYDNEY>MAP-LAYOUT-3",
"AU>PERTH>MAP-LAYOUT-3",
"MX>MEXICO-CITY>MAP-LAYOUT-5",
"MX>TIJAUNA>MAP-LAYOUT-1",
"CN>SHANGHAI>MAP-LAYOUT-8",
"CA>CALGARY>MAP-LAYOUT-1"
$result = $TEST_DATA | ConvertFrom-Csv -Delimiter '>' -Header 'Country','City','Map' |
Sort-Object Country, City | # sort on Country, then City
Group-Object Country, City | # group on Country, then City
ForEach-Object {
# see if we have an item (or perhaps multiple items) starting with 'NEW'
$newMap = $_.Group | Where-Object {$_.Map -match '^NEW'}
if ($newMap) {
# take the top-numbered item from the 'NEW-' item(s)
$newMap | Sort-Object { [int]($_.Map -replace '\D ') } -Descending |
Select-Object -First 1 | Select-Object *, @{Name = 'IsNew'; Expression = {$true}}
}
else {
# no 'NEW' items found, so just take the top-numbered item in the group
$_.Group | Sort-Object { [int]($_.Map -replace '\D ') } -Descending |
Select-Object -First 1 | Select-Object *, @{Name = 'IsNew'; Expression = {$false}}
}
}
# show on screen
$result | Format-Table -AutoSize
# output as CSV file
$result | Export-Csv -Path 'X:\Somewhere\SortedMap.csv' -NoTypeInformation
Output:
Country City Map IsNew
------- ---- --- -----
AU PERTH MAP-LAYOUT-3 False
AU SYDNEY MAP-LAYOUT-3 False
CA CALGARY MAP-LAYOUT-1 False
CA TORONTO NEW-MAP-LAYOUT-1 True
CA VANCOUVER MAP-LAYOUT-1 False
CN SHANGHAI MAP-LAYOUT-8 False
EU ATHENS NEW-MAP-LAYOUT-8 True
EU BERLIN MAP-LAYOUT-8 False
EU PARIS MAP-LAYOUT-100 False
EU VENICE NEW-MAP-LAYOUT-2 True
JP TOKYO MAP-LAYOUT-25 False
MX MEXICO-CITY MAP-LAYOUT-5 False
MX TIJAUNA MAP-LAYOUT-1 False
RU MOSCOW MAP-LAYOUT-2 False
RU ST-PETERSBURG MAP-LAYOUT-2 False
UK LODNON MAP-LAYOUT-2 False
US DENVER MAP-LAYOUT-2 False
US MIAMI MAP-LAYOUT-25 False
US NEW-YORK MAP-LAYOUT-25 False