Можно ли вернуть действительный ключ из хеш-таблицы?

Учитывая хеш-таблицу (или словарь), например:

$HashTable = @{ 'One' = 1; 'Two' = 2; 'Three' = 3 }

(который по умолчанию нечувствителен к регистру, но не обязательно должен быть в каждой ситуации для моего случая. Фактически, я понятия не имею о компараторе, который используется в данной хеш-таблице или словаре)

Получить соответствующее значение для конкретного элемента легко:

$HashTable['two'] # Or: $HashTable.get_item('two')
2

Но возможно ли также вернуть фактический ключ (определенного элемента), например:

$HashTable.get_Key('two')

Что вернет Two (с большой буквы T), а не ошибку или two в нижнем регистре?


Обновление 1

Видимо я не совсем ясно сформулировал свой вопрос, поэтому перефразирую его:

Для любой хеш-таблицы (или словаря) с неизвестным компаратором я ищу способ получить исходный key конкретного элемента, аналогично тому, как я получил бы value конкретного элемента. Значение:

  • Если ключ не существует, я бы не ожидал ничего взамен (аналогично поиску значения с использованием того же ключа)
  • Если ключ существует, я ожидаю взамен (единственный) исходный ключ (аналогично тому, как если бы взамен было получено одно) значение)

Между тем я начинаю верить, что ответ на мой вопрос такой:

Это невозможно

Просто потому, что ключ хеш-таблицы не переупорядочивается, а только hashcode и данные восстановления коллизий, но не весь ключ (/string, размер которого может быть практически неограниченным). Это также не имеет смысла, потому что весь ключ по-прежнему доступен в коллекции .keys, но, очевидно, (обратная) ссылка на реальный предмет исчезла...


Обновление 2

Честно говоря, то, что я ожидал от этого вопроса, было либо:

  • стандартный способ получения оригинального ключа
  • или «невозможно» с возможным объяснением, почему это так

Но оказалось, что у меня есть список интересных альтернатив, которые я мог бы использовать вместо этого. Для моего реального варианта использования производительность важна, но более важным является тот факт, реагирует ли предложение предоставления в соответствии с моими ожиданиями. Поэтому я установил тестовый стенд Pester, чтобы подтвердить это:

Describe 'Retrive the original key from a hashtable or dictionary' { # https://stackoverflow.com/a/78656228

    BeforeAll {
        $CaseInsensitive = [Ordered]@{
            One   = 1
            Two   = 2
            Three = 3
            4 = 'Four'
        }

        $CaseSensitive = [System.Collections.Specialized.OrderedDictionary]::new()
        $CaseSensitive['One']   = 1
        $CaseSensitive['two']   = 2
        $CaseSensitive['Two']   = 2
        $CaseSensitive['three'] = 3
        $CaseSensitive['Three'] = 3
        $CaseSensitive['THREE'] = 3
        $CaseSensitive[[Object]4] = 'Four'

        # mklement0 https://stackoverflow.com/a/78656228/1701026
        function GetKey($Dictionary, $lookupKey) {
            if ($Dictionary.Contains($lookupKey)) {
                $candidateKeys = $Dictionary.Keys.Where({ $_ -eq $lookupKey }, 0, 2)
                if ($candidateKeys.Count -ge 2) { $lookupKey }
                else                            { $candidateKeys[0] }
            }
        }

        # Fabrice SANGA https://stackoverflow.com/a/78656281/1701026
        # $CaseInsensitive | Add-Member -Force -MemberType ScriptMethod -Name get_Key -Value {
            # param([string] $CaseInsensitiveIndex)
            # return $this.Keys.Where{ $_ -ceq ([CultureInfo]::InvariantCulture).TextInfo.ToTitleCase($CaseInsensitiveIndex) }
        # }
        # $CaseSensitive | Add-Member -Force -MemberType ScriptMethod -Name get_Key -Value {
            # param([string] $CaseInsensitiveIndex)
            # return $this.Keys.Where{ $_ -ceq ([CultureInfo]::InvariantCulture).TextInfo.ToTitleCase($CaseInsensitiveIndex) }
        # }
        # function GetKey($Dictionary, $lookupKey) {
            # $Dictionary.get_Key($lookupKey)
        # }

        # Abraham Zinala https://stackoverflow.com/a/78657526/1701026
        # function GetKey($HashTable, $to_match) {
            # [System.Linq.Enumerable]::Where(
            # ($entries = [System.Collections.DictionaryEntry[]]@($HashTable.GetEnumerator())),
                # [Func[System.Collections.DictionaryEntry, bool]]{
                    # param($entry)
                    # $entry.Key -match $to_match -and $entries.Where{$_.Key -match $to_match}.Count -eq 1 -or 
                    # $entry.Key -cmatch $to_match -and $entries.Where{$_.Key -match $to_match}.Count -ge 1
                # }
            # ).Key
        # }

        # iRon
        # function GetKey($Dictionary, $LookupKey) {
            # if ($Dictionary -is [System.Collections.IDictionary]) {
                # if (-not $Dictionary.Contains($LookupKey)) { return }
            # } else {
                # if (-not $Dictionary.ContainsKey($LookupKey)) { return }
            # }
            # foreach ($Key in $Dictionary.get_Keys()) {
                # if ($Key -ceq $LookupKey) { return $Key }
                # if ($Key -ieq $LookupKey) { $iKey = $Key }
            # }
            # if ($Null -ne $iKey) { $iKey } else { $LookupKey }
        # }
    }

    Context 'Case insensitive, existing key' {


        It 'One' {
            $CaseInsensitive['One'] | Should -Be 1
            GetKey $CaseInsensitive 'One' | Should -BeExactly 'One'
        }

        It 'ONE' {
            $CaseInsensitive['ONE'] | Should -Be 1
            GetKey $CaseInsensitive 'ONE' | Should -BeExactly 'One'
        }

        It 'two' {
            $CaseInsensitive['two'] | Should -Be 2
            GetKey $CaseInsensitive 'two' | Should -BeExactly 'Two'
        }

        It 'Two' {
            $CaseInsensitive['Two'] | Should -Be 2
            GetKey $CaseInsensitive 'Two' | Should -BeExactly 'Two'
        }

        It 'TWO' {
            $CaseInsensitive['TWO'] | Should -Be 2
            GetKey $CaseInsensitive 'TWO' | Should -BeExactly 'Two'
        }

        It 'three' {
            $CaseInsensitive['three'] | Should -Be 3
            GetKey $CaseInsensitive 'three' | Should -BeExactly 'Three'
        }

        It 'Three' {
            $CaseInsensitive['Three'] | Should -Be 3
            GetKey $CaseInsensitive 'Three' | Should -BeExactly 'Three'
        }

        It 'THREE' {
            $CaseInsensitive['THREE'] | Should -Be 3
            GetKey $CaseInsensitive 'THREE' | Should -BeExactly 'Three'
        }

        It '4' {
            $CaseInsensitive[[Object]4] | Should -Be 'Four'
            GetKey $CaseInsensitive 4 | Should -Be 4
        }
    }

    Context 'Case insensitive, missing key' {

        It '"4"' {
            $CaseInsensitive['4'] | Should -BeNullOrEmpty
            GetKey $CaseInsensitive '4' | Should -BeNullOrEmpty
        }
    }

    Context 'Case sensitive, existing key' {

        It 'One' {
            $CaseSensitive['One'] | Should -Be 1
            GetKey $CaseSensitive 'One' | Should -BeExactly 'One'
        }

        It 'two' {
            $CaseSensitive['Two'] | Should -Be 2
            GetKey $CaseSensitive 'Two' | Should -BeExactly 'Two'
        }

        It 'Two' {
            $CaseSensitive['Two'] | Should -Be 2
            GetKey $CaseSensitive 'Two' | Should -BeExactly 'Two'
        }

        It 'three' {
            $CaseSensitive['three'] | Should -Be 3
            GetKey $CaseSensitive 'three' | Should -BeExactly 'three'
        }

        It 'Three' {
            $CaseSensitive['Three'] | Should -Be 3
            GetKey $CaseSensitive 'Three' | Should -BeExactly 'Three'
        }

        It 'THREE' {
            $CaseSensitive['THREE'] | Should -Be 3
            GetKey $CaseSensitive 'THREE' | Should -BeExactly 'THREE'
        }

        It '4' {
            $CaseInsensitive[[Object]4] | Should -Be 'Four'
            GetKey $CaseInsensitive 4 | Should -Be 4
        }
    }

    Context 'Case sensitive, missing key' {

        It 'one' {
            $CaseSensitive['one'] | Should -BeNullOrEmpty
            GetKey $CaseSensitive 'one' | Should -BeNullOrEmpty
        }

        It 'ONE' {
            $CaseSensitive['ONE'] | Should -BeNullOrEmpty
            GetKey $CaseSensitive 'ONE' | Should -BeNullOrEmpty
        }

        It 'TWO' {
            $CaseSensitive['TWO'] | Should -BeNullOrEmpty
            GetKey $CaseSensitive 'TWO' | Should -BeNullOrEmpty
        }

        It 'Four' {
            $CaseSensitive['Four'] | Should -BeNullOrEmpty
            GetKey $CaseSensitive 'Four' | Should -BeNullOrEmpty
        }

        It '"4"' {
            $CaseInsensitive['4'] | Should -BeNullOrEmpty
            GetKey $CaseInsensitive '4' | Should -BeNullOrEmpty
        }
    }
}

Использовать другую хеш-таблицу или ту же самую с инвертированными парами ключ-значение?

Santiago Squarzon 22.06.2024 16:12

@SantiagoSquarzon, я думал об этом, но что, если я не знаю, чувствительна ли данная хеш-таблица (или словарь) к регистру или нет. Это приводит меня в замешательство22 с другим моим вопросом (вы ответили): Проверьте, чувствителен ли словарь к регистру

iRon 22.06.2024 16:23

Что ж, тогда вы уже получили ответ :) вам нужно будет выполнить проверку для каждого типа, реализующего IDIctionary. Для хеш-таблицы вы можете получить ее из поля _keycomparer.

Santiago Squarzon 22.06.2024 16:29

Что должно произойти в $HashTable.get_Key('two'), если в вашем примере хеш-таблица нечувствительна к регистру? Каков вариант использования? Вы хотите получить ключ, принадлежащий заданному значению? Я думаю, что тебе нужно $hashTable.GetKeyByValue(2), и в этом случае ты можешь использовать ReferenceEquals, чтобы получить это.

Santiago Squarzon 22.06.2024 17:03

@SantiagoSquarzon, по первому вопросу: если хеш-таблица нечувствительна к регистру, я бы ожидал Two, если хеш-таблица чувствительна к регистру, я бы ничего не ожидал взамен, поскольку нет совпадающих элементов. Что касается вашего второго вопроса, я углублюсь в метод GetKeyByValue, но подозреваю, что это тоже может не сработать, поскольку value может не быть уникальным.

iRon 22.06.2024 17:15

Для первого ответа: Разве не для этого нужен .ContainsKey?

Santiago Squarzon 22.06.2024 17:18

Если бы словарь был нечувствителен к регистру, мог бы быть только один ключ с другим регистром, если бы словарь был чувствителен к регистру, тогда нужно было бы использовать точный регистр, чтобы .ContainsKey возвращал true ?

Santiago Squarzon 22.06.2024 17:35

@SantiagoSquarzon, я обновил вопрос и надеюсь, что он станет более понятным, когда я ожидаю чего-то (оригинала key) взамен.

iRon 22.06.2024 19:04

Вам нужен DictionaryEntry? $HashtTable.GetEnumerator().Where{ $_.Key -match 'two' }[0]

Abraham Zinala 22.06.2024 20:16

@iRon, я согласен, что это в целом невозможно, и я переработал нижнюю часть своего ответа, чтобы обсудить гипотетически более эффективное решение, основанное на гипотетическом доступе к экземпляру компаратора. Тем не менее, решение, основанное на трех нелинейных поисках, возможно, в частности, для System.Collections.Generic.SortedDictionary`2, хотя на практике вы можете не заметить разницу по сравнению с оптимизированным линейным поиском, за исключением больших словарей.

mklement0 22.06.2024 20:19

@AbrahamZinala, нет, вопрос в том, чтобы получить регистро-точную форму исходного ключа, которая использовалась при добавлении записи.

mklement0 22.06.2024 20:21

PS, @iRon: Я хотел сказать: в целом невозможно сделать эффективно, но возможно, как показано в средней части моего ответа.

mklement0 22.06.2024 21:13

Спасибо за добавление тестов; Я понимаю, что и мое решение, и решение Абрахама возвращают ожидаемые результаты. Что касается производительности: я добавил альтернативное решение, которое оптимизирует случай, когда ключ поиска уже находится в точной форме; оно также бывает короче.

mklement0 23.06.2024 13:44

@mklement0 mklement0, я обновил тесты, поскольку обнаружил ошибку в решении Авраама (см. комментарий к его ответу). это означает, что на данный момент вашим решением является только то, которое действительно работает и выдерживает тесты Pester.

iRon 23.06.2024 21:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
14
196
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Предисловие:


Мне неизвестен эффективный способ получения исходного ключа в точной по регистру форме, но ниже приводится краткий способ (в ущерб производительности 'First' можно опустить) с использованием встроенного .Where() метод:

@{
 'One' = 1
 'Two' = 2
 'Three' = 3
}.Keys.Where({ $_ -eq 'two' }, 'First')[0] # -> 'Two'

Примечание:

  • Вышеупомянутое надежно работает только со словарями, нечувствительными к регистру, которыми являются хеш-таблицы PowerShell и [ordered] литералы хэш-таблицы (обратите внимание, что использование 'First' не является строго обязательным, но является оптимизацией производительности: он перестает следить за первым - и определение только в регистронезависимом словаре — match [0] извлекает единственный элемент коллекции совпадений, который .Where() неизменно возвращает).

  • В случае словарей, чувствительных к регистру, вышеизложенное может сообщать о ложных срабатываниях — см. ниже решение, которое также работает с такими словарями.

    • Если, напротив, вы хотите получить (потенциально) все ключи из регистрозависимого словаря, которые идентичны или имеют вариации по регистру данного ключа поиска (независимо от того, соответствует ли ключ поиска одному из них с точностью до регистра), замените
      .Keys.Where({ $_ -eq 'two' }, 'First')[0] с
      .Keys.Where({ $_ -eq 'two' })
  • Причина, по которой это неэффективно, заключается в необходимости выполнения линейного (O(N)) поиска по коллекции ключей, а тот факт, что блок сценария должен вызываться для каждого элемента коллекции, не помогает.


Работа со словарем, чувствительность к регистру которого неизвестна, требует дополнительной работы, включая, возможно, проверку всех ключей:

# Note: [System.Collections.Specialized.OrderedDictionary] is the type
#       underlying [ordered] hashtable literals in PowerShell.
#       It - like [System.Collections.Hashtable] - is case-SENSITIVE by default; 
#       it is PowerShell that makes them case-INsensitive behind the scenes.
$dictionary = [System.Collections.Specialized.OrderedDictionary]::new()
$dictionary['One'] = 1
$dictionary['TWO'] = 2
$dictionary['two'] = 2  # case variation
$dictionary['Three'] = 2

$lookupKey = 'two'

# -> Stores 'two' in $actualKey, the case-exact match.
$actualKey = 
  if ($dictionary.Contains($lookupKey)) {
    $candidateKeys = $dictionary.Keys.Where({ $_ -eq $lookupKey }, 0, 2)
    if ($candidateKeys.Count -ge 2) { $lookupKey }
    else                            { $candidateKeys }
  }
  • .Contains($lookupKey), отсеивает ключи поиска, которые не соответствуют существующему ключу, независимо от регистра или без учета регистра.

  • .Where({ $_ -eq $lookupKey }, 0, 2) ищет не более 2 кандидатов (потенциальных) ключевых совпадений, без учета регистра; ограничение поиска двумя кандидатами является оптимизацией производительности: по наличию двух (или более) ключей, которые являются вариациями регистра друг друга, можно сделать вывод, что имеющийся словарь чувствителен к регистру.

  • Если есть (по крайней мере) два потенциальных ключа ($candidateKeys.Count -ge 2), это означает, что хеш-таблица чувствительна к регистру и, следовательно, ключ поиска по определению уже находится в форме с точным регистром (в противном случае .Contains() не возвращался бы $true) , поэтому $lookupKey возвращается.

  • В противном случае, если возвращается только один ключ-кандидат, этот ключ по определению является правильным и возвращается (обратите внимание, что в этом случае нет необходимости использовать [0] для вывода самого единственного элемента, поскольку использование Оператор if вызывает автоматическое перечисление любой коллекции (перечисляемой), как в конвейере).

Альтернатива, которая оптимизируется для случая, когда ключ поиска уже находится в точной по регистру форме:

$actualKey =
  if ($dictionary.Contains($lookupKey)) {
      if (-1 -ne [Array]::IndexOf($dictionary.Keys, $lookupKey)) { $lookupKey }
      else { $dictionary.Keys.Where({ $_ -eq $lookupKey }, 'First') }
  }
  • Как только существование соответствующего ключа подтверждено с помощью .Contains(), сначала выполняется регистрозависимый (линейный) поиск с использованием [Array]::IndexOf() (который выполняет сравнения с учетом регистра и региональных параметров), который работает намного лучше, чем фильтр .Where().

  • Если обнаружено совпадение с точным регистром, ключ поиска по определению уже находится в форме с точным регистром.

  • В противном случае подразумевается, что словарь нечувствителен к регистру и, следовательно, этого достаточно для поиска первого (и по определению только) ключа в коллекции .Keys, который без учета регистра соответствует ключу поиска, используя .Where() с -eq.


Гипотетическое эффективное решение потребует эффективных (O(1)) поисков в коллекции .Keys, которые сами возвращают совпадающие ключи, но, как вы заметили, словари не предназначены для этого, поскольку они переводят ключи в абстрактные хеши (числа) для поиска. значений (и эти хэши не имеют обратной связи с ключами, из которых они были получены).

Если бы это было возможно - это не так[1] - получить System.Collections.IEqualityComparer / System.Collections.Generic.IEqualityComparer`1 , используемый данным экземпляром словаря, и конкретный используемый экземпляр имеет тип предоставляется через статические свойства класса System.StringComparer , например [System.StringComparer]::OrdinalIgnoreCase, вы можете, по крайней мере, сделать вывод о чувствительности к регистру и оптимизировать на основе этого: если словарь чувствителен к регистру и поиск значения (.Contains()) завершается успешно, вы по определению имеете дело с формой ключа, точной по регистру; в противном случае достаточно (линейно) найти первый ключ, совпадающий без учета регистра, в .Keys.
Если данный словарь имеет тип System.Collections.Generic.SortedDictionary`2, вы можете выполнить бинарный поиск по коллекции .Keys, воспользовавшись тем фактом, что коллекция ключей по определению отсортирована по строки:
[Array]::BinarySearch($sortedDict.Keys, $lookupKey, [StringComparer]::InvariantCultureIgnoreCase)

В частности, с помощью System.Collections.Generic.SortedDictionary`2 вы можете добиться относительно эффективного решения, даже не зная компаратора, с помощью двух-трех нелинейных поисков (хотя на практике вы можете не заметить разницы с линейным подходом, за исключением больших словарей):

  • Проверьте, соответствует ли ключ поиска .Contains(), и остановитесь, если нет.

  • Если это так, сначала используйте двоичный поиск с учетом регистра: если он успешен, ключ поиска по определению уже находится в форме с учетом регистра, поэтому верните его.

  • В противном случае (словарь должен быть нечувствителен к регистру) повторите поиск без учета регистра, который по определению найдет единственный соответствующий ключ, который является регистрозависимым вариантом ключа поиска, и вернет его.


[1]

+1 за альтернативу (и предположение, что, вероятно, это невозможно сделать). Проблема с альтернативой заключается в том, что если хеш-таблица чувствительна к регистру, я могу получить взамен несколько ключей (или только первый). Я не думаю, что есть способ вытащить компаратор из хеш-таблицы и каким-то образом использовать его в фильтре where.
iRon 22.06.2024 15:58

@железо. Используйте Add-Member и определите это альтернативное решение как новый член объекта $Hashtable. Вот так $Hashtable | Add-Member -MemberType ScriptMethod -Name get_Key -Value { param([string] $a) return $this.Keys.Where{ $_ -eq $a } }. Если компаратор чувствителен к регистру, вполне нормально получить несколько ключей. Если компаратор не чувствителен к регистру, вы получите только один ключ.

Fabrice SANGA 22.06.2024 16:51

@FabriceSANGA, «Если компаратор чувствителен к регистру, вполне нормально получить несколько ключей». Я уверен, что понимаю, что вы имеете в виду. Как я это вижу: (независимо от компаратора) хеш-таблица (или словарь) обычно возвращает один элемент (или ни одного).

iRon 22.06.2024 16:54

@железо. Если компаратор чувствителен к регистру, вы сможете ввести клавиши 'Two', 'tWo', 'two' как три разных ключа. Но вызов ScriptMethod таким образом get_Key('two') соответствует трем клавишам. Вот почему @mklment0 использует 'First' в методе where(), чтобы получить только первый из них.

Fabrice SANGA 22.06.2024 16:59

Спасибо, @iRon. Хорошая мысль относительно хеш-таблиц с учетом регистра - я обновил ответ, чтобы показать решение и для них (и, следовательно, также для хеш-таблиц с неизвестным статусом чувствительности к регистру), а также добавил раздел для решения (очевидно, невыполнимого) желание получить компаратор.

mklement0 22.06.2024 18:12

На основе тестов Pester и предоставленных сценариев использования.

Вы можете добавить член метода сценария get_Key в хеш-таблицу, которая выполняет поиск по списку ключей, который точно соответствует формату заголовка (без учета всех заглавных букв*) указанной строки параметра $CaseInsensitiveKey, используя метод System.Globalization.TextInfo.ToTitleCase():

$Hashtable | Add-Member -Force -MemberType ScriptMethod -Name get_Key -Value {
  param([string] $CaseInsensitiveKey)
  if (($Keys = @($this.Keys.Where{ $_ -eq $CaseInsensitiveKey })).Count -gt 1) {
    return $Keys.Where{ $_ -ceq $CaseInsensitiveKey }
  }
  return $Keys[0]
}

* Формат Title-Case включает все прописные буквы, которые в этом случае игнорируются.

Базовое решение

$this.Keys возвращает список ключей, который фильтруется по индексу в методе Where(). Это означает, что возвращаемое значение не является сфабрикованным. Из отредактированного решения добавлен дополнительный тест на то, что ключ поиска возвращает значение, когда он индексируется хеш-таблицей. Однако решение не самое оптимальное, поскольку поиск производится дважды.


Результат теста Пестера

Starting discovery in 1 files.
Discovery found 20 tests in 19ms.
Running tests.
[+] W:\PowerShell\markdown-to-html-shortcut\MarkdownToHtmlShortcut\test.ps1 119ms (39ms|63ms)
Tests completed in 123ms
Tests Passed: 20, Failed: 0, Skipped: 0 NotRun: 0

Тест на Пестера и результаты

Я не ищу value, я ищу оригинал (актуальный) key независимо от используемого компаратора. Другими словами: если ключ существует, я хотел бы получить его исходное имя (которое может незначительно отличаться от ключа поиска из-за используемого компаратора, а может и не отличаться).

iRon 22.06.2024 16:14

@iRon, я думаю, что реализовал ваш вариант использования, который не знает о реализованном средстве сравнения, и он возвращает только один ключ, если он существует и записан в формате заголовка.

Fabrice SANGA 22.06.2024 18:58

iRon, Он извлекает ключ.

Fabrice SANGA 22.06.2024 19:44

Хорошо, завтра проведу еще несколько тестов.

iRon 22.06.2024 19:47

iRon ([CultureInfo]::InvariantCulture).TextInfo.ToTitleCase('two'‌​) возвращается Two. Таким образом в списке ключей выглядит ключ Two и сравнение выполняется с учетом регистра.

Fabrice SANGA 22.06.2024 19:47

Обратите внимание, что вопрос не связан с регистром заголовка. Цель состоит в том, чтобы получить исходный ключ - в том виде, в котором он использовался при добавлении записи - точно по регистру, для любого словаря с неизвестной чувствительностью к регистру, чего ваш код не делает. Также обратите внимание, что класс [StringComparer] имеет статические свойства, которые предоставляют готовые средства сравнения на равенство (например, [StringComparer]::CurrentCultureIgnoreCase), поэтому нет необходимости создавать свои собственные. Наконец, хотя показать обертку метода сценария для желаемой функциональности не помешает, это не является целью вопроса.

mklement0 22.06.2024 20:30

@mklement0. Спасибо, я упростил пример использования, используя компаратор равенства [StringComparer]::InvariantCulture. Но если вы прочитаете метод сценария get_Key, который я предлагаю добавить, вы увидите, что ключ возврата фильтруется с помощью индекса поиска по регистру заголовка. Ключ не сфабрикован, а сфабрикован индекс поиска. Возвращается ключ, соответствующий индексу, а не индекс. Индекс — это аргумент метода get_Key.

Fabrice SANGA 22.06.2024 21:10

Я до сих пор не понимаю угол заголовка-регистра, но позвольте мне проиллюстрировать мою точку зрения: Учитывая $HashTable = @{ 'One' = 1; 'twO' = 2; 'Three' = 3 } с добавленным вашим методом сценария, $HashTable.get_key('two'), $HashTable.get_key('TWO') и $HashTable.get_key('twO') все должны вернуть 'twO' - ключ в том случае, когда он был определен изначально - но ваш код возвращает ничего.

mklement0 22.06.2024 21:10

@mklement0, я понимаю вашу точку зрения, но определение варианта использования зависит от @iRon. То, что он объяснил, предполагает, что именно ключ заголовка и регистра должен быть возвращен. То есть если $HashTable = @{ 'One' = 1; 'twO' = 2; 'Three' = 3 }, то $HashTable.get_key('two') ничего не возвращает, а $HashTable.get_key('three') вернет Three.

Fabrice SANGA 22.06.2024 21:13

Я не думаю, что в вопросе («получить настоящий ключ») есть какая-либо двусмысленность, и ничто не указывает на то, что пример заглавной буквы T в Two был чем-то иным, кроме примера вариации падежа. Но я позволю @iRon высказать свое мнение.

mklement0 22.06.2024 21:16

Давайте продолжим обсуждение в чате.

Fabrice SANGA 22.06.2024 21:17

@mklement0, спасибо, что помог прояснить вопрос. @**Fabrice**, я добавил тестовый стенд Pester в обновление 2, чтобы вы могли видеть, какую реакцию я ожидаю от функции (к сожалению, ваше предложение не удалось при 6 баллах в тесте)

iRon 23.06.2024 13:13

@iRon, есть решение, основанное на вариантах использования. Я просто изменил предыдущий метод сценария и добавил дополнительный тест, соответствующий случаю использования, когда ключ отсутствует, и дополнительную строковую операцию с индексным ключом. Я завершил рассуждения доказательством того, что возвращенный ключ оригинальный, а не поддельный.

Fabrice SANGA 23.06.2024 20:20

В моих тестах на приставание есть примеры случаев заголовков, но для этого есть ограничение. Другими словами: эта хеш-таблица: $HashTable = @{ 'oNe' = 1 } должна вернуть oNe для: $HashTable.get_Key('oNe')

iRon 23.06.2024 20:57

На основе статей about_Hash_Tables и about_Arrays:

Обновлять:

$HashTable = @{ 'One' = 1; 'Two' = 2; 'Three' = 3}

$HashTable.Keys | Where-Object { $_ -eq 'two' }  # returns string of value 'Two'
$HashTable.Keys | Where-Object { $_ -eq 'foo' }  # returns $null

# or

$HashTable.Keys.Where( { $_ -eq 'two' } ) # returns Collection`1 of value @('Two')
$HashTable.Keys.Where( { $_ -eq 'foo' } ) # returns Collection`1 of value @()

Оригинальный ответ:

$HashTable = @{ 'One' = 1; 'Two' = 2; 'Three' = 3; 'Single' = 1 }

$HashTable.Keys | Where-Object { $HashTable.$_ -eq 1 }

'--- or using GetEnumerator method ---'
$HashTable.GetEnumerator().Where( {$_.Value -eq 1}) |
    Select-Object -ExpandProperty Name

Я не ищу key на основе какого-либо value. Я ищу оригинал key на основе поиска в хеш-таблице (или словаре), которая может быть чувствительной или нечувствительной к регистру.

iRon 22.06.2024 17:00

Ответ @iRon обновлен…

JosefZ 22.06.2024 20:13

К вашему сведению, я добавил к вопросу несколько (назойливых) тестов, чтобы сделать ожидаемый результат более ясным.

iRon 23.06.2024 14:27

Хорошо! Я считаю, что решение, охватывающее оба сценария сопоставления с учетом регистра и без учета регистра на основе количества, может выглядеть примерно так:

$HashTable = @{ 'One' = 1; 'Two' = 2; 'Three' = 3 }

$to_match = 'two'

[System.Linq.Enumerable]::Where(
    ($entries = [System.Collections.DictionaryEntry[]]@($HashTable.GetEnumerator())),
    [Func[System.Collections.DictionaryEntry, bool]]{
        param($entry)
        $entry.Key -match $to_match -and $entries.Where{$_.Key -match $to_match}.Count -eq 1 -or 
        $entry.Key -cmatch $to_match -and $entries.Where{$_.Key -match $to_match}.Count -ge 1
    }
)

где мы превращаем хеш-таблицу в набор явных DictionaryEntry, а затем фильтруем записи с помощью Linq.EnumerableWhere() метода. Так,

  • если ключи содержат ровно 1 соответствующий ключ, верните пару с учетом регистра
  • но если ключи содержат более одного соответствующего ключа, для возврата записи используйте сопоставление с учетом регистра.

Теперь мы получаем, что 'one' соответствует одной записи 'one','One','oNe' и т. д., но несколько записей, таких как 'two','TWO', сопоставляются с тем, что фильтруется в $to_match.

+1. Извините, за позднее подтверждение того, что это решение действительно работает, но мне нужно было время, чтобы его протестировать (см. мое тестирование в разделе «Обновление 2»).
iRon 23.06.2024 13:25

При более внимательном рассмотрении оказывается, что на самом деле в вашем подходе есть недостаток: вы предполагаете, что всегда существует несколько ключей с одним и тем же именем (но с разным регистром), если это касается словаря, чувствительного к регистру, но это не обязательно. случай. Я обновил тест Pester, чтобы уловить эту ловушку...

iRon 23.06.2024 20:38

@iRon, можешь подробнее рассказать об этом утверждении? В приведенном выше примере это тот же самый $HashTable, который вы использовали, без каких-либо изменений и без других ключей, соответствующих другому регистру. Другими словами, он работает как для записей с несколькими ключами, так и для записей с одним ключом. Возможно я что-то упустил, так что заранее прошу прощения за невежество.

Abraham Zinala 24.06.2024 03:06
$to_match = 'one' при наличии единственной записи в $HashTable, соответствующей ей, возвращается исходный ключ. Таким образом, если существует несколько записей одного и того же ключа (с разным регистром), он соответствует точному регистру. Итак, 'one','One','onE' вернется только 'one' на основе $to_match.
Abraham Zinala 24.06.2024 03:09

Общее ограничение заключается в том, что функция get_Key(<key>) должна реагировать аналогично получению значения из хеш-таблицы (с учетом или без учета регистра). Это означает, что если get_Item(<key>) ($HashTable[<key>]) возвращает (единственное) Value, get_Key(<key>) должно возвращать (единственное) key, а если $HashTable[<key>] ничего не возвращает, функция get_Key(<key>) должна ничего не возвращать. Для вашей функции, если я создаю хеш-таблицу с учетом регистра с одним элементом (просто .add('One')), функция get_Key('ONE') не должна ничего возвращать (как $HashTable['ONE']), но ваша функция это делает.

iRon 24.06.2024 08:36

Я понимаю, что вы имеете в виду, думаю, мое замешательство возникло из-за вашего комментария Which would return Two (which a capital T) rather than an error or two in lowercase?. Если я не ошибаюсь, ваш get_Key('one') не сможет получить ключ One, как указано в вашем вопросе. Тем не менее, я оставлю свой ответ здесь, так как он может помочь другим, лол.

Abraham Zinala 24.06.2024 23:47

Другие вопросы по теме