簡體   English   中英

如果兩個項目相同,我如何比較 Swift 中的兩個 Arrays 並變異 arrays 之一

[英]How can I compare two Arrays in Swift and mutate one of the arrays if two Items are the same

我想將兩個 Arrays 相互比較,這意味着它們中的每一個項目。

如果此 arrays 中的兩項相同,我需要運行一些代碼。 到目前為止,我已經用兩個 For-Loops 做到了這一點,但評論中有人說這不是那么好(因為我也得到了一個錯誤。

有沒有人知道我可以使用哪個代碼來實現這一目標?

順便說一句:這是我之前使用的代碼:

   var index = 0
   
   for Item1 in Array1 {
       for Item2 in Array2 {
           if (Item1 == Item2) {
               
               // Here I want to put some code if the items are the same
   
           }
       }
    
       // Index is in this case the counter which counts on which place in the Array1 I am.
       index += 1
   }

好的,我將再次嘗試描述我的意思:我想比較兩個 Arrays。 如果有一些相同的項目,我想從陣列 A / 陣列 1 中刪除該項目。好的,如果這有效,我想添加更多語句,僅當該項目的參數有時才允許我刪除項目一個特別的價值,但我想我可以獨自完成這一步。

如果您想比較來自不同數組的項目,您需要為您的Item添加Equatable協議


例如:

struct Item: Equatable {
    let name: String
    
    static func ==(l: Item, r: Item) -> Bool {
        return l.name == r.name
    }
}

您需要決定通過哪些屬性來比較您的Item 就我而言,我按name進行比較。


let array1: [Item] = [
    .init(name: "John"),
    .init(name: "Jack"),
    .init(name: "Soer"),
    .init(name: "Kate")
]

let array2: [Item] = [
    .init(name: "John"),
]

for item1 in array1 {
    if array2.contains(item1) {
        // Array 2 contains item from the array1 and you can perform your code.
    }
}

如果你想支持這個

好的,我將再次嘗試描述我的意思:我想比較兩個 Arrays。 如果有一些相同的項目,我想從陣列 A / 陣列 1 中刪除該項目。好的,如果這有效,我想添加更多語句,僅當該項目的參數有時才允許我刪除項目一個特別的價值,但我想我可以獨自完成這一步。

我想它適合你

你需要讓你的array1可變

var array1: [Item] =...

像這樣過濾array1

let filteredArray1 = array1.filter { (item) -> Bool in
    return !array2.contains(item)
}

並使用過濾后的數組重新定義您的array1

array1 = filteredArray1


讓我知道它對你有用。

var itemsToRemove = array1.filter { array2.contains($0) }

for item in itemsToRemove {
    if let i = array1.firstIndex(of: item) {
        // run your conditional code here.
        array1.remove(at: i)
    }
}

更新

問題已被重申,元素將從 arrays 之一中刪除。

您沒有提供使index out of bounds錯誤的實際代碼,但這幾乎可以肯定是因為您沒有考慮在使用索引時刪除了元素,因此您可能正在索引一個有效的區域開始,但不再是。

我的第一個建議是不要在搜索循環中進行實際刪除。 有一個實現相同結果的解決方案,我將給出,但除了必須非常小心地索引到縮短的數組之外,還有一個性能問題:每次刪除都需要Array將所有后面的元素向下移動一個. 這意味着每次刪除都是一個 O( n ) 操作,這使得整體算法 O( n ^3)。

那么你會怎么做呢? 最簡單的方法是創建一個僅包含您希望保留的元素的新數組。 例如,假設您要從array1中刪除所有也在array2中的元素:

array1 = array1.filter { !array2.contains($0) }

我應該注意,我將原始答案保留在下面的原因之一是因為您可以使用這些方法替換array2.contains($0)在某些情況下獲得更好的性能,而原始答案描述了這些情況以及如何實現它。

此外,即使閉包用於確定是否保留元素,因此它必須返回一個Bool ,但沒有什么可以阻止您在其中添加額外的代碼來執行您可能想要的任何其他工作:

array1 = array1.filter 
{
    if array2.contains($0) 
    {
        doSomething(with: $0)
        return false // don't keep this element
    }
    return true
}

這同樣適用於以下所有閉包。

您可以只使用ArrayremoveAll(where:)方法。

array1.removeAll { array2.contains($0) }

在這種情況下,如果閉包返回true ,則表示“刪除此元素”,這與filter中使用的閉包相反,所以如果你在其中做額外的工作,你必須考慮到這一點。

我還沒有查看 Swift 庫如何實現removeAll(where:)但我假設它以有效的方式而不是幼稚的方式完成其工作。 如果您發現性能不是那么好,您可以推出自己的版本。

extension Array where Element: Equatable
{
    mutating func efficientRemoveAll(where condition: (Element) -> Bool)
    {
        guard count > 0 else { return }
        
        var i = startIndex
        var iEnd = endIndex
        
        while i < iEnd
        {
            if condition(self[i])
            {
                iEnd -= 1
                swapAt(i, iEnd)
                continue // note: skips incrementing i,iEnd has been decremented
            }
            
            i += 1
        }
        
        removeLast(endIndex - iEnd)
    }
}

array1.efficientRemoveAll { array2.contains($0) }

這不是實際刪除循環內的元素,而是通過將它們與結束元素交換,並處理何時適當增加。 這將收集最后要刪除的元素,在循環結束后,它們可以在一個 go 中刪除。 所以刪除只是最后一次 O( n ) 通過,這避免了增加循環內部刪除所需要的算法復雜性。

由於與當前“結束”元素交換,此方法不保留變異數組中元素的相對順序。

如果要保留訂單,可以這樣做:

extension Array where Element: Equatable
{
    mutating func orderPreservingRemoveAll(where condition: (Element) -> Bool)
    {
        guard count > 0 else { return }
        
        var i = startIndex
        var j = i
        
        repeat
        {
            swapAt(i, j)
            if !condition(self[i]) { i += 1 }
            j += 1
        } while j != endIndex
        
        removeLast(endIndex - i)
    }
}

array1.orderPreservingRemoveAll { array2.contains($0) }

如果我必須打賭,這將非常接近標准 Swift 的removeAll(where:) for Array的實現方式。 它保留兩個索引,一個用於當前要保留的最后一個元素i ,另一個用於要檢查的下一個元素j 這具有累積要在最后刪除的元素i那些過去的元素)的效果。

原始答案

以前的解決方案適用於小型(但仍然非常大)arrays,但它們是 O( n ^2) 解決方案。

如果您沒有改變 arrays,它們可以更簡潔地表達

array1.filter { array2.contains($0) }.forEach { doSomething($0) }

其中doSomething實際上不必是 function 調用 - 只需使用$0 (通用元素)做任何你想做的事情。

通過將較小的數組放在filter中,通常可以獲得更好的性能。

排序后的特殊情況 arrays

如果您的 arrays 之一已排序,那么您可能會在二進制搜索而不是contains中獲得更好的性能,盡管這將要求您的元素符合Comparable Swift 標准庫中沒有二進制搜索,我在新的swift-algorithms package 中也找不到,所以你需要自己實現它:

extension RandomAccessCollection where Element: Comparable, Index == Int
{
    func sortedContains(_ element: Element) -> Bool
    {
        var range = self[...]
        while range.count > 0
        {
            let midPoint = (range.startIndex + range.endIndex) / 2
            if range[midPoint] == element { return true }
            range = range[midPoint] > element
                ? self[..<midPoint]
                : self[index(after: midPoint)...]
        }
        return false
    }
}

unsortedArray.filter { sortedArray.sortedContains($0) }.forEach { doSomething($0) }

這將提供 O( n log n ) 性能。 但是,如果您真的需要性能,您應該針對您的實際用例對其進行測試,因為二進制搜索對 CPU 的分支預測器並不是特別友好,而且與僅進行線性搜索相比,不會發生太多錯誤預測導致性能降低.

如果兩個arrays 都已排序,則可以通過利用它獲得更好的性能,盡管您必須再次實現該算法,因為它不是由 Swift 標准庫提供的。

extension Array where Element: Comparable
{
    // Assumes no duplicates
    func sortedIntersection(_ sortedOther: Self) -> Self
    {
        guard self.count > 0, sortedOther.count > 0 else { return [] }
        
        var common = Self()
        common.reserveCapacity(Swift.min(count, sortedOther.count))
        
        var i = self.startIndex
        var j = sortedOther.startIndex
        
        var selfValue = self[i]
        var otherValue = sortedOther[j]
        
        while true
        {
            if selfValue == otherValue
            {
                common.append(selfValue)
                i += 1
                j += 1
                if i == self.endIndex || j == sortedOther.endIndex { break }
                selfValue = self[i]
                otherValue = sortedOther[j]
                continue
            }
            
            if selfValue < otherValue
            {
                i += 1
                if i == self.endIndex { break }
                selfValue = self[i]
                continue
            }
            
            j += 1
            if j == sortedOther.endIndex { break }
            otherValue = sortedOther[j]
        }
        
        return common
    }
}

array1.sortedIntersection(array2).forEach { doSomething($0) }

這是 O( n ) 並且是我所包含的所有解決方案中最有效的,因為它恰好通過 arrays。 這是唯一可能的,因為 arrays 都已排序。

大型Hashable元素數組的特殊情況

但是,如果您的 arrays 滿足某些條件,您可以通過Set獲得 O( n ) 性能。 具體來說,如果

  1. arrays 很大
  2. 數組元素符合Hashable
  3. 你沒有改變 arrays

將兩個 arrays 中較大的一個Set

let largeSet = Set(largeArray)
smallArray.filter { largeSet.contains($0) }.forEach { doSomething($0) }

為了便於分析,如果我們假設 arrays 都有n 個元素,則 Set 的初始值設定項將為 O( n )。 intersection將涉及測試smallArray元素的每個元素是否屬於largeSet 這些測試中的每一個都是 O(1),因為 Swift Set實現為 hash 表。 將有n個這些測試,所以這是 O( n )。 那么forEach的最壞情況將是 O( n )。 所以總體上是 O( n )。

當然,創建Set有開銷,其中最糟糕的是涉及到 memory 分配,因此對於小型 arrays 來說不值得。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM