哈希表和键的顺序问题

39

有没有一种方法可以保留哈希表中键的添加顺序?就像推入/弹出机制一样。

示例:

$hashtable = @{}

$hashtable.Add("Switzerland", "Bern")
$hashtable.Add("Spain", "Madrid")
$hashtable.Add("Italy", "Rome")
$hashtable.Add("Germany", "Berlin")
$hashtable

我希望保留我向哈希表中添加元素的顺序。

7个回答

66

在PowerShell V1 / V2中没有内置的解决方案。您需要使用.NET System.Collections.Specialized.OrderedDictionary

$order = New-Object System.Collections.Specialized.OrderedDictionary
$order.Add("Switzerland", "Bern")
$order.Add("Spain", "Madrid")
$order.Add("Italy", "Rome")
$order.Add("Germany", "Berlin")


PS> $order

Name                           Value
----                           -----
Switzerland                    Bern
Spain                          Madrid
Italy                          Rome
Germany                        Berlin

在 PowerShell V3 中,您可以将其转换为 [ordered]:

PS> [ordered]@{"Switzerland"="Bern"; "Spain"="Madrid"; "Italy"="Rome"; "Germany"="Berlin"}

Name                           Value
----                           -----
Switzerland                    Bern
Spain                          Madrid
Italy                          Rome
Germany                        Berlin

感谢您的回答!我知道 PS v3 [ordered],但我必须使用 PS 2.0。 - Philipp
谢谢,说实话:这个实现很奇怪,我看不出来为什么要将其作为默认实现。你需要无序以获得原始速度吗?为此创建一个自定义类,并让基类保持有序插入顺序,因为大多数人都会期望如此。 - user1708042
2
请注意,如果您想将有序哈希传递给函数(https://dev59.com/olgQ5IYBdhLWcg3wkEtJ),则还必须使用此类型(`System.Collections.Specialized.OrderedDictionary`)作为参数。 您不能使用 [ordered][hash] 快捷方式的变体作为参数类型,并且必须使用完整类型。 您可以将其作为 [hash] 参数传递,但是您会失去排序(因为它作为不太具体的扩展 hash 类型传递;更多信息请参见该链接),这可能是不可取的。 - ruffin
谢谢你关于[ordered]的提示。我在声明全局变量时成功地使用了这个方法:Set-Variable -Name MyOrderedHashtable -Scope global -Option AllScope -Value ([ordered]@{}) - MikeOnline

10
你可以使用有序字典代替:
像这样:
$list = New-Object System.Collections.Specialized.OrderedDictionary
$list.Add("Switzerland", "Bern")
$list.Add("Spain", "Madrid")
$list.Add("Italy", "Rome")
$list.Add("Germany", "Berlin")
$list

7
您可以在添加元素时为其指定一个顺序键:
$hashtable = @{}
$hashtable[$hashtable.count] = @("Switzerland", "Bern")
$hashtable[$hashtable.count] = @("Spain", "Madrid")
$hashtable[$hashtable.count] = @("Italy", "Rome")
$hashtable[$hashtable.count] = @("Germany", "Berlin")
$hashtable

然后,您可以按键获取排序后的元素:
echo "`nHashtable keeping the order as they were added"
foreach($item in $hashtable.getEnumerator() | Sort Key)
{
    $item
}

1

PowerShell 1的方法是添加一个哈希表成员以保留添加顺序。无需使用System.Collections.Specialized.OrderedDictionary:

$Hash = New-Object PSObject                                       
$Hash | Add-Member -MemberType NoteProperty -Name key1 -Value val1
$Hash | Add-Member -MemberType NoteProperty -Name key2 -Value val2
$Hash | Add-Member -MemberType NoteProperty -Name key3 -Value val3

5
但那不是一个哈希表,它是一个PSCustomObject。即使您将变量命名为“$Hash”,也不是同一件事。;) 对于我尝试过的所有实际目的,OrderedDictionary 的功能与哈希表完全相同。 - Adi Inbar

1

为了与旧版 PowerShell 兼容,您可以考虑使用此 cmdlet:

Function Order-Keys {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)][HashTable]$HashTable,
        [Parameter(Mandatory = $false, Position = 1)][ScriptBlock]$Function,
        [Switch]$Descending
    )
    $Keys = $HashTable.Keys | ForEach {$_} # Copy HashTable + KeyCollection
    For ($i = 0; $i -lt $Keys.Count - 1; $i++) {
        For ($j = $i + 1; $j -lt $Keys.Count; $j++) {
            $a = $Keys[$i]
            $b = $Keys[$j]
            If ($Function -is "ScriptBlock") {
                $a = $HashTable[$a] | ForEach $Function
                $b = $HashTable[$b] | ForEach $Function
            }
            If ($Descending) {
                $Swap = $a -lt $b
            }
            Else
            {
                $Swap = $a -gt $b
            }
            If ($Swap) {
                $Keys[$i], $Keys[$j] = $Keys[$j], $Keys[$i]
            }
        }
    }
    Return $Keys
}

这个 cmdlet 返回一个按函数定义排序的键列表:
按名称排序:
$HashTable | Order-Keys | ForEach {Write-Host $_ $HashTable[$_]}
Germany Berlin
Italy Rome
Spain Madrid
Switzerland Bern

按值排序:
$HashTable | Order-Keys {$_} | ForEach {Write-Host $_ $HashTable[$_]}
Germany Berlin
Switzerland Bern
Spain Madrid
Italy Rome

您可能还考虑嵌套哈希表:
$HashTable = @{
    Switzerland = @{Order = 1; Capital = "Berne"}
    Germany     = @{Order = 2; Capital = "Berlin"}
    Spain       = @{Order = 3; Capital = "Madrid"}
    Italy       = @{Order = 4; Capital = "Rome"}
}

例如,按(哈希)顺序属性排序并返回键(国家):
$HashTable | Order-Keys {$_.Order} | ForEach {$_}

或按预定义的大写字母排序(降序):

$HashTable | Order-Keys {$_.Capital} -Descending | ForEach {$_}

1
这是一个对我有效的简单程序例程。
function sortedKeys([hashtable]$ht) {
  $out = @()
  foreach($k in $ht.keys) {
    $out += $k
  }
  [Array]::sort($out)
  return ,$out
}

和使用它的调用

forEach($k in (& sortedKeys $ht)) {
  ...
}

1
但问题是关于保持添加哈希的顺序,而不是按键进行排序。这个回答如何回答问题? - Peter Mortensen
Peter,你是正确的。我混淆了,认为有序字典类似于Java中的TreeMap而不是LinkedHashMap。感谢你的澄清。 - Steve Pritchard

-1
function global:sortDictionaryByKey([hashtable]$dictionary)
{
    return $dictionary.GetEnumerator() | sort -Property name;
}

5
欢迎来到Stack Overflow。虽然这段代码可能回答了问题,但提供有关为什么和/或如何回答问题的附加上下文可以提高其长期价值。如何回答问题。谢谢! - Elletlar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接