Сравнение двух больших текстовых массивов в PowerShell

#arrays #powershell

#массивы #powershell

Вопрос:

У меня есть два массива, между которыми я хотел бы использовать разницу. У меня был некоторый успех с COMPARE-OBJECT, но он слишком медленный для больших массивов. В этом примере $ALLVALUES и $ ODD — это два моих массива.

Раньше я мог эффективно делать это, используя FINDSTR ex. FINDSTR /V /G:ODD.txt ALLVALUES.txt > EVEN.txt FINDSTR выполнил это менее чем за 2 секунды для 110 000 элементов. (даже приходилось читать и записывать с диска)

Я пытаюсь вернуться к производительности FINDSTR, где это дало бы мне все ALLVALUES.txt это НЕ соответствовало ODD.txt (давая мне ЧЕТНЫЕ значения в этом случае)

ПРИМЕЧАНИЕ: Этот вопрос касается не ЧЕТНОГО или нечетного, а только практического примера, который можно быстро и визуально проверить, работает ли он так, как требуется.

Вот код, с которым я играл. Используя COMPARE-OBJECT, 100 000 заняло около 200 секунд против 2 секунд для FINDSTR на моем компьютере. Я думаю, что есть гораздо более элегантный способ сделать это в PowerShell. Спасибо за вашу помощь.

 # -------  Build the MAIN array
$MIN = 1
$MAX = 100000
$PREFIX = "AA"

$ALLVALUES = while ($MIN -le $MAX) 
{
   "$PREFIX{0:D6}" -f $MIN  
}


# -------  Build the ODD values from the MAIN array
$MIN = 1
$MAX = 100000
$PREFIX = "AA"

$ODD = while ($MIN -le $MAX) 
{
   If ($MIN%2) {
      "$PREFIX{0:D6}" -f $MIN  
   }
  ELSE {
    $MIN  
   }
}

Measure-Command{$EVEN = Compare-Object -DifferenceObject $ODD -ReferenceObject $ALLVALUES -PassThru}
  

Комментарии:

1. Опубликованный метод хэширования выполняется очень быстро. Спасибо за ответ! Любые другие методы, которые следует учитывать с разумной скоростью?

2. Я добавил Compare-stringArray и улучшил код hashset.

Ответ №1:

Массивы — это объекты, а не просто большие двоичные объекты текста, которые обрабатываются findstr.
Самая быстрая разница между строковыми массивами — .NET3.5 HashSet.SymmetricExceptWith.

 $diff = [Collections.Generic.HashSet[string]]$a
$diff.SymmetricExceptWith([Collections.Generic.HashSet[string]]$b)
$diffArray = [string[]]$diff
  

46 мс для 100 тыс. элементов на процессоре i7 с использованием ваших данных.

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

 function Diff-Array($a, $b, [switch]$unique) {
    if ($unique.IsPresent) {
        $diff = [Collections.Generic.HashSet[string]]$a
        $diff.SymmetricExceptWith([Collections.Generic.HashSet[string]]$b)
        return [string[]]$diff
    }
    $occurrences = @{}
    foreach ($_ in $a) { $occurrences[$_]   }
    foreach ($_ in $b) { $occurrences[$_]-- }
    foreach ($_ in $occurrences.GetEnumerator()) {
        $cnt = [Math]::Abs($_.value)
        while ($cnt--) { $_.key }
    }
}
  

Использование:

 $diffArray = Diff-Array $ALLVALUES $ODD
  

340 мс, в 8 раз медленнее, чем hashset, но в 110 раз быстрее, чем Compare-Object!

И, наконец, мы можем создать более быстрый объект сравнения для массивов строк / чисел:

 function Compare-StringArray($a, $b, [switch]$unsorted) {
    $occurrences = if ($unsorted.IsPresent) { @{} }
                   else { [Collections.Generic.SortedDictionary[string,int]]::new() }
    foreach ($_ in $a) { $occurrences[$_]   }
    foreach ($_ in $b) { $occurrences[$_]-- }
    foreach ($_ in $occurrences.GetEnumerator()) {
        $cnt = $_.value
        if ($cnt) {
            $diff = [PSCustomObject]@{
                InputObject = $_.key
                SideIndicator = if ($cnt -lt 0) { '=>' } else { '<=' }
            }
            $cnt = [Math]::Abs($cnt)
            while ($cnt--) {
                $diff
            }
        }
    }
}
  

100 тыс. элементов: в 20-28 раз быстрее, чем Compare-Object, выполняется за 2100 мс / 1460 мс (без сортировки)
10 тыс. элементов: в 2-3 раза быстрее, завершается за 210 мс / 162 мс (без сортировки)

Комментарии:

1. потрясающее решение, обработано 1,4 млн строк против 600 тыс. за 30 секунд, в моем случае я использовал IntersectWith, потому что я хотел пересечение