有不多组件哈希表(字典)键的内置分隔符.
至于一个风俗分隔器:对于组件本身不太可能出现的角色,您最好的选择是NUL(字符与代码点0x0),您可以将其表示为"`0"在 PowerShell 中。然而,表演基于约定的每次查找的字符串操作都很尴尬(例如$Index."James`0Cook"),一般来说仅在以下情况下有效串化关键部件是可行的- 或者如果它们都是以字符串开头,如您的示例所示。
使用数组for multi-component keys 在语法上更可取,但通常使用集合不是按原样工作,因为 .NET引用类型通常,即使它们恰好代表相同的数据,也不会有意义地比较不同的实例 - 请参阅this answer。
- 注意:以下假设元素作为键的集合做有意义地比较(他们自己字符串或 .NET值类型或具有自定义相等逻辑的 .NET 引用类型)。如果该假设不成立,则没有可靠的通用解决方案,但是您自己提出的基于链接答案中显示的 CLIXML 序列化的尽力而为的方法可能会起作用。
zett42's helpful answer 使用元组, 哪个做进行有意义的比较不同的实例,其会员包含相等的数据。
然而,需要构造一个元组实例对于每个添加/修改/查找语法上很尴尬(例如。,
$Index.([Tuple]::Create('James', 'Cook'))
那里是一种制作常规 PowerShell 的方法数组作为匆忙的钥匙, 以某种方式只会增加复杂性创造哈希表(调用构造函数),同时允许常规数组语法添加/更新和查找(例如,$Index.('James', 'Cook'))。
# Sample objects for the hashtable.
$list = ConvertFrom-Csv @'
Id, LastName, FirstName, Country
1, Aerts, Ronald, Belgium
2, Berg, Ashly, Germany
3, Cook, James, England
4, Duval, Frank, France
5, Lyberg, Ash, England
6, Fischer, Adam, Germany
'@
# Initialize the hashtable with a structural equality comparer, i.e.
# a comparer that compares the *elements* of the array and only returns $true
# if *all* compare equal.
# This relies on the fact that [System.Array] implements the
# [System.Collections.IStructuralEquatable] interface.
$dict = [hashtable]::new([Collections.StructuralComparisons]::StructuralEqualityComparer)
# Add entries that map the combination of first name and last name
# to each object in $list.
# Note the regular array syntax.
$list.ForEach({ $dict.($_.FirstName, $_.LastName) = $_ })
# Use regular array syntax for lookups too.
# Note: CASE MATTERS
$dict.('James', 'Cook')
重要的: 以上执行区分大小写的比较(就像 zett42 的元组解决方案一样),不像常规的 PowerShell 哈希表。
进行不区分大小写的比较需要更多的工作,因为需要[System.Collections.IEqualityComparer]接口的自定义实现,即case-麻木不仁[System.Collections.StructuralComparisons]::StructuralEqualityComparer 提供的实现:
# Case-insensitive IEqualityComparer implementation for arrays.
class CaseInsensitiveArrayEqualityComparer: System.Collections.IEqualityComparer {
[bool] Equals([object] $o1, [object] $o2) {
if ($o1 -isnot [array] -or $o2 -isnot [array]) { return $false }
return ([System.Collections.IStructuralEquatable] $o1).Equals($o2, [System.StringComparer]::InvariantCultureIgnoreCase)
}
[int] GetHashCode([object] $o) {
if ($o -isnot [Array]) { return $o.GetHashCode() }
return ([System.Collections.IStructuralEquatable] $o).GetHashCode([StringComparer]::InvariantCultureIgnoreCase)
}
}
# Pass an instance of the custom equality comparer to the constructor.
$dict = [hashtable]::new([CaseInsensitiveArrayEqualityComparer]::new())
笔记:
-
Santiago Squarzon 发现 ([System.Collections.IStructuralEquatable] $o).GetHashCode([StringComparer]::InvariantCultureIgnoreCase) 是一种内置方法,可以根据元素的大小写获取数组的哈希码-麻木不仁哈希码。
-
下面的原始解决方案计算数组的不区分大小写的哈希码逐个元素,这样既麻烦又效率低。也许他们仍然对如何计算哈希码感兴趣。
可选阅读:逐个元素的哈希码实现:
# Case-insensitive IEqualityComparer implementation for arrays.
# See the bottom section of this answer for a better .NET 7+ alternative.
class CaseInsensitiveArrayEqualityComparer: System.Collections.IEqualityComparer {
[bool] Equals([object] $o1, [object] $o2) {
if ($o1 -isnot [array] -or $o2 -isnot [array]) { return $false }
return ([System.Collections.IStructuralEquatable] $o1).Equals($o2, [System.StringComparer]::InvariantCultureIgnoreCase)
}
[int] GetHashCode([object] $o) {
if ($o -isnot [Array]) { return $o.GetHashCode() }
[int] $hashCode = 0
foreach ($el in $o) {
if ($null -eq $el) {
continue
} elseif ($el -is [string]) {
$hashCode = $hashCode -bxor $el.ToLowerInvariant().GetHashCode()
} else {
$hashCode = $hashCode -bxor $el.GetHashCode()
}
}
return $hashCode
}
}
$list = ConvertFrom-Csv @'
Id, LastName, FirstName, Country
1, Aerts, Ronald, Belgium
2, Berg, Ashly, Germany
3, Cook, James, England
4, Duval, Frank, France
5, Lyberg, Ash, England
6, Fischer, Adam, Germany
'@
# Pass an instance of the custom equality comparer to the constructor.
$dict = [hashtable]::new([CaseInsensitiveArrayEqualityComparer]::new())
$list.ForEach({ $dict.($_.FirstName, $_.LastName) = $_ })
# Now, case does NOT matter.
$dict.('james', 'cook')
关于的注释.GetHashCode() 执行在上面的自定义比较器类中:
-
需要自定义 .GetHashCode() 实现来返回相同的所有比较相等的对象的哈希码([int] 值)(也就是说,如果 $o1 -eq $o2 是 $true,$o1.GetHashCode() 和 $o2.GetHashCode() 必须返回相同的值)。
-
虽然不需要哈希码独特的(并且不可能在所有情况下),理想情况下,尽可能少的对象共享相同的哈希码,因为这样可以减少所谓的冲突的数量,这会降低哈希表的查找效率 - 有关背景信息,请参阅相关的 Wikipedia article .
-
上面的实现使用了一个相当简单的基于-bxor(按位异或)的算法,它为两个具有相同元素的数组生成相同的哈希码,但在不同的顺序.
-
.GetHashCode() 帮助主题显示了更复杂的方法,包括使用辅助的元组实例,如它的哈希码算法是顺序感知的——虽然简单,但这种方法的计算成本很高,并且需要更多的工作才能获得更好的性能。有关 .NET 7+ 选项,请参阅底部部分。
zett42的碰撞测试代码(已改编),它确定在 1000 个数组中,有多少具有给定数量的元素的随机字符串值导致相同的哈希码,即产生冲突,并从中计算冲突百分比。如果您需要提高上述实现的效率,您可以使用此代码对其进行测试(可能也用于测量测试)运行看看不同的实现如何比较)。
# Create an instance of the custom comparer defined above.
$cmp = [CaseInsensitiveArrayEqualityComparer]::new()
$numArrays = 1000
foreach ($elementCount in 2..5 + 10) {
$numUniqueHashes = (
1..$numArrays |
ForEach-Object {
$cmp.GetHashCode(@(1..$elementCount | ForEach-Object { "$(New-Guid)" }))
} |
Sort-Object -Unique
).Count
[pscustomobject] @{
ElementCount = $elementCount
CollisionPercentage = '{0:P2}' -f (($numArrays - $numUniqueHashes) / $numArrays)
}
}
所有测试的 about 输出为 0%,因此 -bxor 方法似乎足以防止冲突,至少对于随机字符串并且不包括元素不同的数组变体命令只要。
继续阅读以获得卓越的 .NET 7+ 解决方案。
.NET 7+ 中的高级自定义相等比较器实现(至少需要 PowerShell 7.3 的预览版):
zett42 指出,[HashCode]::Combine(),在 .NET 7+ 中可用,允许更有效的实现,因为它:
笔记:
# .NET 7+ / PowerShell 7.3+
# Case-insensitive IEqualityComparer implementation for arrays
# using [HashCode]::Combine() - limited to 8 elements.
class CaseInsensitiveArrayEqualityComparer: System.Collections.IEqualityComparer {
[bool] Equals([object] $o1, [object] $o2) {
if ($o1 -isnot [array] -or $o2 -isnot [array]) { return $false }
return ([System.Collections.IStructuralEquatable] $o1).Equals($o2, [System.StringComparer]::InvariantCultureIgnoreCase)
}
[int] GetHashCode([object] $o) {
if ($o -isnot [Array] -or 0 -eq $o.Count) { return $o.GetHashCode() }
$o = $o.ForEach({ $_ -is [string] ? $_.ToLowerInvariant() : $_ })
$hashCode = switch ($o.Count) {
1 { [HashCode]::Combine($o[0]) }
2 { [HashCode]::Combine($o[0], $o[1]) }
3 { [HashCode]::Combine($o[0], $o[1], $o[2]) }
4 { [HashCode]::Combine($o[0], $o[1], $o[2], $o[3]) }
5 { [HashCode]::Combine($o[0], $o[1], $o[2], $o[3], $o[4]) }
6 { [HashCode]::Combine($o[0], $o[1], $o[2], $o[3], $o[4], $o[5]) }
7 { [HashCode]::Combine($o[0], $o[1], $o[2], $o[3], $o[4], $o[5], $o[6]) }
8 { [HashCode]::Combine($o[0], $o[1], $o[2], $o[3], $o[4], $o[5], $o[6], $o[7]) }
default { throw 'Not implemented for more than 8 array elements.' }
}
return $hashCode
}
}
然而,正如 zett42 指出的那样,您可以通过调用[HashCode]::Combine() 来克服值计数限制迭代地, 在一个循环中.
在案件的情况下——麻木不仁执行,这不是太多的开销,因为无论如何你都需要一个循环,即为了在 [string] 类型的值上调用 .ToLowerInvariant()(这是上面的 .ForEach() 调用隐式执行的操作)。
这是他的实现:
# .NET 7+ / PowerShell 7.3+
# Case-insensitive IEqualityComparer implementation for arrays
# using [HashCode]::Combine() *iteratively*, with *no* element-count limit.
class CaseInsensitiveArrayEqualityComparer: System.Collections.IEqualityComparer {
[bool] Equals([object] $o1, [object] $o2) {
if ($o1 -isnot [array] -or $o2 -isnot [array]) { return $false }
return ([System.Collections.IStructuralEquatable] $o1).Equals($o2, [System.StringComparer]::InvariantCultureIgnoreCase)
}
[int] GetHashCode([object] $o) {
if ($o -isnot [Array] -or 0 -eq $o.Count) { return $o.GetHashCode() }
$hashCode = 0
$o.ForEach({
$value = $_ -is [string] ? $_.ToLowerInvariant() : $_
$hashCode = [HashCode]::Combine( $hashCode, $value )
})
return $hashCode
}
}