IndexOf() 或 .FindIndex() 不区分大小写

IndexOf() or .FindIndex() case-insensitive

我正在尝试通过详细记录问题来验证一些 XML,包括所需的属性顺序和大写错误。如果所需的属性顺序是 one, two, three 并且有问题的 XML 有 one, three, two 我想记录它。如果一个属性只是错误地大写,说 TWO 而不是 two 我也想记录它。 目前我有两个数组,$ordered 包含属性名称(正确的大写)和 $miscapitalized 包含错误大写属性的名称。 因此,给定 one, three, TWO 的属性和 one, two, three

的所需顺序

$ordered = one, two, three

$miscapitalized = TWO

从这里我想追加错误的大写,所以一个新变量

$logged = one, two (TWO), three

我可以得到 $ordered 的索引,其中大写错误发生在

foreach ($attribute in $ordered) {
    if ($attribute -iin $miscapitalized) {
        $indexOrdered = [array]::IndexOf($ordered, $attribute)
    }
}

但是,我无法根据(正确大写的)$attribute 获取 $miscapitalized 中的索引。我试过了

$miscapitalized = @('one', 'two', 'three')
$miscapitalized.IndexOf('TWO')

这不起作用,因为 .IndexOf() 区分大小写。我发现 this[Collections.Generic.List[Object]] 可以工作,所以我想也许 Generic.List 是功能的来源。所以我尝试了

$miscapitalized = [System.Collections.Generic.List[String]]@('one', 'two', 'three')
$miscapitalized.FindIndex('TWO')

哪个抛出

Cannot find an overload for "FindIndex" and the argument count: "1".

这让我想到 this 说我需要一个实际的谓词类型,而不仅仅是一个字符串。此时我已经不知所措,我唯一能想到的是 $miscapitalized.FindIndex([System.Predicate]::new('TWO')),但它不起作用。我怀疑 Predicate could/should 不知何故是一个正则表达式,但我似乎找不到任何指向正确方向的东西,或者至少我能理解并认识到它指向正确的方向。我还发现 https://www.powershellstation.com/2010/05/18/passing-predicates-as-parameters-in-powershell/ 将代码块称为谓词,但我不清楚它与术语谓词的用法相同(这是一个广泛使用的术语),我也不知道如何制作代码块这在这里会有帮助。

我确实想出了这种方法,它在 $miscapitalized 中使用与在 $ordered 中相同的 foreach 搜索,并且确实有效。但我想知道是否有一种不需要嵌套循环的更优雅的方法。另外,理解这里应用的 Predicate 似乎很有用,以及(可能)如何使用代码块。

$ordered = @('one', 'two', 'three')
$miscapitalized = @('TWO')
$replacements = [System.Collections.Specialized.OrderedDictionary]::new()
foreach ($orderedAttribute in $ordered) { 
    if ($orderedAttribute -iin $miscapitalized) {
        $indexOrdered = [array]::IndexOf($ordered, $orderedAttribute)

        foreach ($miscapitalizedAttribute in $miscapitalized) {
            if (($miscapitalizedAttribute -iin $ordered) -and ($miscapitalizedAttribute -ieq $orderedAttribute) -and ($miscapitalizedAttribute -cne $orderedAttribute)) {
                #$indexMiscapitalized = [array]::IndexOf($miscapitalized, $miscapitalizedAttribute)
                $replacements.Add($indexOrdered, "$orderedAttribute ($miscapitalizedAttribute)")
            }
        }
    }
}
if ($replacements.Count -gt 0) {
    foreach ($index in $replacements.Keys) {
        $ordered[$index] = $replacements.$index
    }
}

$ordered

编辑:根据下面的评论,我试过了

$ordered = @('one', 'two', 'three')
$miscapitalized = @('TWO', 'Three')
$replacements = [System.Collections.Specialized.OrderedDictionary]::new()
foreach ($orderedAttribute in $ordered) { 
    if ($orderedAttribute -iin $miscapitalized) {
        $indexOrdered = [array]::IndexOf($ordered, $orderedAttribute)
        if ($indexMiscapitalized = $miscapitalized.FindIndex({param($s) $s -eq $orderedAttribute})) {
            $replacements.Add($indexOrdered, "$orderedAttribute ($($miscapitalized[$indexMiscapitalized]))")
        }
    }
}
if ($replacements.Count -gt 0) {
    foreach ($index in $replacements.Keys) {
        $ordered[$index] = $replacements.$index
    }
}

$ordered

得到最后一个 (three/Three) 但缺少 two/TWO。但是明天可以尝试很多可能的解决方案,因为每个解决方案都可以学到一些东西。

也许,你想多了。您可以使用 Compare-Object 完成所有艰苦的工作,然后您可以检查结果并相应地记录它们:

# Reference array for attributes order and capitalization
[array]$reference = @(
    'one'
    'two'
    'three'
    'four'
)

# Example XML
[xml]$xml = '<foo one="1" oNe="oNe" thrEE="thrEE" two="2">dummy</foo>'

# Compare XML attributes to refrerence array
# -SyncWindow 0 - Order of items in the array matters
# 

Compare-Object -ReferenceObject $reference -DifferenceObject $xml.foo.Attributes.Name -SyncWindow 0 -CaseSensitive -includeEqual

这将产生:

InputObject SideIndicator
----------- -------------
one         ==
oNe         =>
two         <=
thrEE       =>
three       <=
two         =>
four        <=

如您所见,one 属性位于正确的索引处 (==) 并且大小写正确。我们还有额外的 oNe 属性,这是不合适的。

您还可以对 Compare-Object 结果进行分组并生成哈希表,您可以将其用于高级日志记录。您可以使用 SideIndicatorInputObject 属性进行各种查找和比较。

$group = Compare-Object -ReferenceObject $reference -DifferenceObject $xml.foo.Attributes.Name -SyncWindow 0 -CaseSensitive -includeEqual |
Group-Object -Property InputObject -AsHashTable -AsString

$group

结果

four  {@{InputObject=four; SideIndicator=<=}}
one   {@{InputObject=one; SideIndicator===}, @{InputObject=oNe; SideIndicator==>}}
thrEE {@{InputObject=thrEE; SideIndicator==>}, @{InputObject=three; SideIndicator=<=}}
two   {@{InputObject=two; SideIndicator=<=}, @{InputObject=two; SideIndicator==>}}

在这种情况下,哈希表键将不区分大小写,因此您可以这样做:

foreach ($r in $reference) {
    $ret = $group.$r | Where-Object {
        $_.SideIndicator -ne '==' -and $_.InputObject -cne $r
    } | Select-Object -ExpandProperty InputObject |
    ForEach-Object {
        'Index of {0}: {1}' -f $_, $xml.foo.Attributes.Name.IndexOf($_)
    }

    if ($ret) {
        @{ $r = $ret }
    }
}

Name  Value              
----  -----              
one   Index of "oNe": 1  
three Index of "thrEE": 2

您可以添加一个小的辅助函数来查找不区分大小写的索引:

function Find-Index {
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string[]]$Array,
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$Value
    )
    for ($i = 0; $i -lt $Array.Count; $i++) {
        if ($Array[$i] -eq $Value) { return $i }
    }
    -1

    # or combine the elements with some unlikely string
    # convert that to lowercase and split on the same unlikely string
    # then use regular IndexOf() against the value which is also lower-cased:
    # (($Array -join '~@~').ToLowerInvariant() -split '~@~').IndexOf($Value.ToLowerInvariant())
}

然后在下面,这样使用它:

# if any of the below arrays has only one item, wrap it inside @()
$ordered        = 'one','two','three'
$miscapitalized = 'One','TWO'

$logged = foreach ($item in $ordered) {
    $index = Find-Index $miscapitalized $item
    if ($index -ge 0) { 
        '{0} ({1})' -f $item, $miscapitalized[$index] 
    }
    else { $item }
}

$logged -join ','

输出

one (One),two (TWO),three

您可以用脚本块替换 FindIndex() 所需的谓词:

PS ~> $miscapitalized = [System.Collections.Generic.List[String]]@('one', 'two', 'three')
PS ~> $predicate = {param($s) $s -eq 'TWO'}
PS ~> $miscapitalized.FindIndex($predicate)
1

这将按预期工作,因为 PowerShell 的 -eq 运算符默认不区分大小写。