繁体   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