Фильтр Swift Array очень медленная производительность

У меня есть 2 массива строк beforeStringList и afterStringList. На самом деле это представления таблиц sqlite, и моя конечная цель — получить список элементов, которые присутствуют только в одном из массивов, чтобы я мог определить, было ли что-то добавлено или удалено. Например, если элемент существует в beforeStringList, а не в afterStringList, то он был удален и так далее. Моя логика заключалась в том, чтобы просто сравнить массивы и использовать операцию .contains, чтобы увидеть, содержит ли один массив элемент или нет. Однако это занимает очень много времени. Порядка многих минут, что не сработает. beforeStringList и afterStringList содержат около 100 тыс. элементов каждый.

Вот как я пытаюсь это запустить:

DispatchQueue.global().async {
                
     var output = beforeStringList.filter{afterStringList.contains($0) == false}

}

Я также попробовал использовать set, который ничем не отличался по производительности:

var set = Set(beforeStringList.map {$0})

DispatchQueue.global().async {
            
         var rowsAdded = set.filter { row in
               
              if afterStringList.contains(row) == false{
                    print("row: \(row)")
         }
                
         return true
     }

}

Открыт для всех идей, как это улучшить!

.contains(row), это то, что должно занять время. Поскольку порядок вас, похоже, не беспокоит, как насчет использования двух Set? На нем есть полезные методы с intersect, substract и т. д.
Larme 21.02.2024 17:57

Кроме того, вы проводите измерения в оптимизированной сборке выпуска? Отладочные сборки выполняются намного медленнее.

Alexander 21.02.2024 18:24

Вы неправильно используете Set, поэтому получаете квадратичную временную сложность. Set выполняется медленнее, чем Array. Его преимущество — быстрее выдавать contains чеки. Вам следует перебирать beforeStringList: Array, сверяя contains с afterStringList: Set. А еще лучше использовать методы Set, как сказал Ларм, которые делают это правильно.

Alexander 21.02.2024 18:25

Не лучше ли сделать это на уровне базы данных?

Joakim Danielson 21.02.2024 18:30
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
4
151
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Я тестировал «командное приложение macOS» с помощью простого запуска.

Вот код, который я тестировал:

var beforeStringList: [String] = []
var afterStringList: [String] = []

for _ in 0...10000 {
    beforeStringList.append("\(Int.random(in: 0...10000))")
    afterStringList.append( "\(Int.random(in: 0...10000))")
}

//Testing Initial code: ie Array.filter() and Array.contains()
var startDate = Date()
var output1 = beforeStringList.filter {
    afterStringList.contains($0) == false
}
print("Array.filter() and Array.contains(): \(Date().timeIntervalSince(startDate))")

//Testing with using a Set for the beforeList: ie Set.filter() and Array.contains()
startDate = Date()
var beforeSet = Set(beforeStringList)
var output2 = beforeSet.filter { afterStringList.contains($0) == false }
print("Set.filter() and Array.contains(): \(Date().timeIntervalSince(startDate))")

//Testing with using a Set for the afterList: Array.filter() and Set.contains()
startDate = Date()
var afterSet = Set(afterStringList)
var output3 = beforeStringList.filter { afterSet.contains($0) == false }
print("Array.filter() and Set.contains(): \(Date().timeIntervalSince(startDate))")

//Testing with using a Set for the beforeList and afterList: Set.subtract()
startDate = Date()
beforeSet = Set(beforeStringList)
afterSet = Set(afterStringList)
var output4 = beforeSet.subtracting(afterSet)
print("Set.subtract(): \(Date().timeIntervalSince(startDate))")

//Testing with using a Set for the beforeList: Set.subtract()
startDate = Date()
beforeSet = Set(beforeStringList)
var output5 = beforeSet.subtracting(afterStringList)
print("Set.subtract() on array: \(Date().timeIntervalSince(startDate))")

print("--- Verifications ---")
print("Verifying output2")
print(Set(output1) == Set(output2))
print("Verifying output 3")
print(Set(output1) == Set(output3))
print("Verifying output 4")
print(Set(output1) == Set(output4))
print("Verifying output 5")
print(Set(output1) == Set(output5))
print("--- end ---")

У меня были разные выводы, но вот один из них:

$>Array.filter() and Array.contains(): 3.4921441078186035
$>Set.filter() and Array.contains(): 2.178665041923523
$>Array.filter() and Set.contains(): 0.0021200180053710938
$>Set.subtract(): 0.002646923065185547
$>Set.subtract() on array: 0.0021669864654541016
$>--- Verifications ---
$>Verifying output2
$>true
$>Verifying output 3
$>true
$>Verifying output 4
$>true
$>Verifying output 5
$>true

Итак, давайте проанализируем исходный код:

array1.filter { array2.contains($0) } //simplified

Итак, Array.filter() выполняет итерацию для каждого элемента array1, а array2 повторяется (от 1 до array2.count или меньше, если элемент, если элемент найден ранее, считается 1 полной итерацией). Итак, Array.contains() был частью замедления вашего кода.

Затем вы продолжили использовать Set вместо array1, но операция по-прежнему требует времени, поскольку Set «оптимизирован» для Set.contains(). Итак, «обратный путь» (Array.filter() + Set.contains() быстрее).

Отсюда вы можете использовать Set, но затем полностью Set и использовать методы Set. В вашем случае вы хотите использовать Set.subtract()

Теперь, согласно этой информации, мы могли бы сказать: давайте использовать Set.subtractOnArray() (где subtractOnArray() — это Set.subtract() с параметром массива) или комбинацию Array.filter() + Set.contains(), но истина может лежать посередине. Нет? Почему Set.subtract() работает медленнее, чем если бы мы говорили ранее, что он «оптимизирован» для Set.contains() и тому подобное?

Я всегда ставлю startDate = Date() ПЕРЕД преобразованием Array в Set. Если я поставлю это позже, сосредоточив внимание только на Array.filter(), Set.filter(),Array.contains(), Set.contains(),Set.subtract(), Set.subtractOnArray(), другой пример вывода будет таким:

$>Array.filter() and Array.contains(): 3.4415539503097534
$>Set.filter() and Array.contains(): 2.1599819660186768
$>Array.filter() and Set.contains(): 0.0010540485382080078
$>Set.subtract(): 0.00034797191619873047
$>Set.subtract() on array: 0.0010139942169189453

И здесь мы видим полную победу subtract().

Действительно, в зависимости от ваших данных преобразование Array в Set может занять некоторое время. Все зависит от того, какую часть вы хотите оптимизировать, от ваших вариантов использования и особенно от того, имеет ли это значение в данном случае. Если что-то занимает «0,0003» секунды или «0,002» секунды, действительно ли я это увижу?

Теперь я предположил, что порядок не имеет значения, и в этом случае вывод будет «неупорядоченным», и в этом случае вы можете использовать NSMutableOrderedSet или версию Swift в Swift-Collections OrderedSet. Здесь нет «вычитания», но вы можете найти свой путь туда.

Отличный ответ, как обычно. Небольшая проблема: у вас опечатка в коде регистрации. Вероятно, следует читать print("Set.subtract()..., а не print("Set.substract().... (Сначала я немного растерялся.)

Duncan C 22.02.2024 02:40

Действительно. После быстрого поиска substract действительно существовал на английском языке до 18 века из-за французского влияния, а поскольку я француз, мой родной язык все еще влияет на мой английский в 21 веке ^^

Larme 22.02.2024 09:08

Хорошая мысль о включении преобразования в набор в сравнение времени. В любом случае я склонен заключать каждую часть обоих подходов, которые я сравниваю, в свои временные блоки, но я специально не задумывался о стоимости преобразования массивов в множества. Во многих случаях я ожидаю, что экономия будет перегружена, поскольку contains() — это операция O(n) над массивами, а циклическое перебор массивов с содержанием становится O(n²) (то есть производительность ОТСТАЛЬНАЯ).

Duncan C 22.02.2024 13:13

Спасибо за очень подробное объяснение, я ценю это. Я собираюсь попробовать этот подход.

ez4nick 22.02.2024 17:00
DispatchQueue.global().async {
    let afterSet = Set(afterStringList)
    var output = beforeStringList.filter { !afterSet.contains($0) }
    // Now 'output' contains items that are in 'beforeStringList' but not in 'afterStringList'
}

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