Расширенное сопоставление с образцом в Powershell - PullRequest
4 голосов
/ 22 апреля 2020

Надеюсь, вы сможете мне чем-нибудь помочь. Благодаря @ mklement0 я получил отличный скрипт, соответствующий большинству c, исходному шаблону для слов в алфавитном порядке. Однако не хватает полнотекстового поиска и выбора. Пример текущего скрипта с небольшой выборкой из нескольких слов в файле Words.txt:

App
Apple
Apply
Sword
Swords
Word
Words

Становится:

App
Sword
Word

Это здорово, так как он действительно сужается до Basi c шаблон на линию! Однако в результате этого построчно все еще существует шаблон, который может быть дополнительно сужен и представляет собой «Слово» (заглавные буквы не важны), поэтому в идеале результат должен быть:

App
Word

И «Меч» удаляется, поскольку он попадает в более базовый шаблон c с префиксом «Word».

У вас есть какие-либо предложения о том, как этого добиться? Имейте в виду, что это будет словарный список из примерно 250 тысяч слов, поэтому я не знаю заранее, что я ищу

CODE (из связанной статьи , дескрипторы префикс соответствует только):

$outFile = [IO.File]::CreateText("C:\Temp\Results.txt")   # Output File Location
$prefix = ''                   # initialize the prefix pattern

foreach ($line in [IO.File]::ReadLines('C:\Temp\Words.txt')) # Input File name.
 {

  if ($line -like $prefix) 
    { 
    continue                   # same prefix, skip
    }

  $line                        # Visual output of new unique prefix
  $prefix = "$line*"           # Saves new prefix pattern
  $outFile.writeline($line)    # Output file write to configured location
}

Ответы [ 3 ]

3 голосов
/ 22 апреля 2020

Вы можете попробовать двухэтапный подход:

  • Шаг 1: Найти список уникальных префиксов в алфавитно отсортированном списке слов. Это делается путем последовательного чтения строк, и, следовательно, требуется только, чтобы вы хранили уникальные префиксы как целое в памяти.

  • Шаг 2. Сортировка полученных префиксов по порядку длины и повторение. над ними, проверяя на каждой итерации, представлено ли уже имеющееся слово подстрокой этого в списке результатов.

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

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

Вам нужно посмотреть, достаточно ли хороша производительность; для лучшей производительности. NET типы используются напрямую, насколько это возможно.

# Read the input file and build the list of unique prefixes, assuming
# alphabetical sorting.
$inFilePath = 'C:\Temp\Words.txt' # Be sure to use a full path.
$uniquePrefixWords = 
  foreach ($word in [IO.File]::ReadLines($inFilePath)) {
    if ($word -like $prefix) { continue }
    $word
    $prefix = "$word*"
  }

# Sort the prefixes by length in ascending order (shorter ones first).
# Note: This is a more time- and space-efficient alternative to:
#    $uniquePrefixWords = $uniquePrefixWords | Sort-Object -Property Length
[Array]::Sort($uniquePrefixWords.ForEach('Length'), $uniquePrefixWords)

# Build the result lists of unique shortest words with the help of a regex.
# Skip later - and therefore longer - words, if they are already represented
# in the result list of word by a substring.
$regexUniqueWords = ''; $first = $true
foreach ($word in $uniquePrefixWords) {
  if ($first) { # first word
    $regexUniqueWords = $word
    $first = $false
  } elseif ($word -notmatch $regexUniqueWords) {
    # New unique word found: add it to the regex as an alternation (|)
    $regexUniqueWords += '|' + $word
  }
}

# The regex now contains all unique words, separated by "|".
# Split it into an array of individual words, sort the array again...
$resultWords = $regexUniqueWords.Split('|')
[Array]::Sort($resultWords)

# ... and write it to the output file.
$outFilePath = 'C:\Temp\Results.txt' # Be sure to use a full path.
[IO.File]::WriteAllLines($outFilePath, $resultWords)
2 голосов
/ 22 апреля 2020

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

Вместо этого вы можете отсортировать по длина , а затем отслеживать паттерны, которые не могут быть удовлетворены более коротким, используя набор ha sh:

function Reduce-Wildcard
{
    param(
        [string[]]$Strings,
        [switch]$SkipSort
    )

    # Create set containing all patterns, removes all duplicates
    $Patterns = [System.Collections.Generic.HashSet[string]]::new($Strings, [StringComparer]::CurrentCultureIgnoreCase)

    # Now that we only have unique terms, sort them by length
    $Strings = $Patterns |Sort-Object -Property Length

    # Start from the shortest possible pattern
    for ($i = 0; $i -lt ($Strings.Count - 1); $i++) {
        $current = $Strings[$i]
        if(-not $Patterns.Contains($current)){
            # Check that we haven't eliminated current string already
            continue
        }

        # There's no reason to search for this substring 
        # in any of the shorter strings
        $j = $i + 1
        do {
            $next = $Strings[$j]

            if($Patterns.Contains($next)){
                # Do we have a substring match?
                if($next -like "*$current*"){
                    # Eliminate the superstring
                    [void]$Patterns.Remove($next)
                }
            }

            $j++
        } while ($j -lt $Strings.Count)
    }

    # Return the substrings we have left
    return $Patterns
}

Затем используйте как:

$strings = [IO.File]::ReadLines('C:\Temp\Words.txt')

$reducedSet = Reduce-Wildcard -Strings $strings

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

Reduce-Wildcard @(
    Reduce-Wildcard -Strings @('App','Apple')
    Reduce-Wildcard -Strings @('Sword', 'Words')
    Reduce-Wildcard -Strings @('Swords', 'Word')
)

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

$patterns = @()
Get-ChildItem dictionaries\*.txt |ForEach-Object {
  $patterns = Reduce-Wildcard -String @(
    $_ |Get-Content
    $patterns
  )
}
0 голосов
/ 23 апреля 2020

Мои два цента:

Использование -Like или RegEx может в долгосрочной перспективе обойтись дорого, зная, что они использовали во внутреннем l oop выбора, вызов будет увеличиваться экспоненциально с размером списка слов. Кроме того, шаблон операций -Like и RegEx может потребоваться экранировать (особенно для Regex, где, например, точка . имеет особое значение. Я подозреваю, что этот вопрос имеет отношение к проверке пароля сложности).

Предполагая, что не имеет значения, находится ли список вывода в нижнем регистре, я бы использовал метод String.Contains(). В противном случае, если регистр выходных данных имеет значение, вы можете подготовить таблицу ha sh, например $List[$Word.ToLower()] = $Word, и использовать ее для восстановления фактического регистра в конце.

# Remove empty words, sort by word length and change everything to lowercase
# knowing that .Contains is case sensitive (and therefore presumably a little faster)
$Words = $Words | Where-Object {$_} | Sort-Object Length | ForEach-Object {$_.ToLower()}
# Start with list with smallest words (I presume each word contains at least 3 characters)
$Result = [System.Collections.ArrayList]@($Words | Where-Object Length -Eq $Words[0].Length)
# Add the word to the list if it doesn't contain any of the all ready listed words
ForEach($Word in $Words) {
    If (!$Result.Where({$Word.Contains($_)},'First')) { $Null = $Result.Add($Word) }
}

2020-04-23 updated скрипт с предложением @ Mathias :

Вы можете использовать Where({$Word.Contains($_)},'First'), чтобы избежать сравнения со всеми $Result каждый раз

, что примерно в два раза быстрее.

...