比较 2 个数组并列出差异 - Swift
Posted
技术标签:
【中文标题】比较 2 个数组并列出差异 - Swift【英文标题】:Compare 2 arrays and list the differences - Swift 【发布时间】:2015-08-21 12:05:39 【问题描述】:我想知道如何比较 2 个布尔数组并列出不匹配的布尔值。
我已经写了一个 2 个数组的简单示例。
let array1 = [true, false, true, false]
let array2 = [true, true, true, true]
我将如何比较 array1 和 array2 并显示不匹配。我正在尝试这样做以检查问答游戏的用户结果。
谢谢!
【问题讨论】:
问题是不清楚你认为答案是什么。对于这个特定的输入,你想要/期望什么结果?你想要一个索引列表吗?数组?什么?如果一个布尔值比另一个多,答案应该是什么?如果一个是空的? 那个数组应该是什么样子的?您必须指定!你心目中的“正确答案”是什么? 嗨,对不起,马特!感谢您的帮助-我意识到我应该更清楚。一般来说,我对编程很陌生,所以请多多包涵。我希望返回一个索引列表,但在做了更多研究之后,我相信我 dont understand what I
m 试图完全实现。很抱歉浪费您的时间。
"我希望返回一个索引列表" 完全合理,但你不得不这么说。我也会修改我的答案以展示这种方法。
“我不明白我想要达到的目标” 确实如此,但这是编程的第一步。你必须给计算机非常精确的命令,所以你必须清楚地知道你想让它在每一种可能的情况下做什么。
【参考方案1】:
我发现这篇文章正在寻找与所发布的情况非常相似的解决方案。但是,我发现使用 Swift 5.1 中引入的 inferringMoves() 的有序集合差异适合我的用例。以防其他人发现它有用,我添加了我使用的内容:
let array1 = [true, false, true, true, false, true]
let array2 = [true, true, true, true, true, false]
let diff = array2.difference(from: array1).inferringMoves()
for change in diff
switch change
case .insert(let offset, let element, _):
print("insert offset \(offset) for element \(element)")
case .remove(let offset, let element, _):
print("remove offset \(offset) for element \(element)")
这会打印出值:
remove offset 4 for element false
remove offset 1 for element false
insert offset 4 for element true
insert offset 5 for element false
【讨论】:
【参考方案2】:从 Swift 源代码移植,适用于 Swift5、ios 12
用法:
let difference = newArray.differenceFrom(from: originalArray) (s1: String, s2: String) in
return s1 == s2
// or
let difference = Array<String>.difference(from: originalArray, to: newArray) (s1: String, s2: String) in
return s1 == s2
for change in diff
switch change
case let .remove(offset, oldElement, _):
print("remove:", offset, oldElement)
case let .insert(offset, newElement, _):
print("insert:", offset, newElement)
来源
数组扩展
extension Array
public func differenceFrom<C: BidirectionalCollection>(
from other: C,
by areEquivalent: (C.Element, Element) -> Bool
) -> CollectionDifference<Element>
where C.Element == Self.Element
return Array<Element>.difference(from: other, to: self, using: areEquivalent)
public static func difference<C, D>(
from old: C, to new: D,
using cmp: (C.Element, D.Element) -> Bool
) -> CollectionDifference<C.Element>
where
C: BidirectionalCollection,
D: BidirectionalCollection,
C.Element == D.Element
// Core implementation of the algorithm described at http://www.xmailserver.org/diff2.pdf
// Variable names match those used in the paper as closely as possible
func _descent(from a: UnsafeBufferPointer<C.Element>, to b: UnsafeBufferPointer<D.Element>) -> [_V]
let n = a.count
let m = b.count
let max = n + m
var result = [_V]()
var v = _V(maxIndex: 1)
v[1] = 0
var x = 0
var y = 0
iterator: for d in 0...max
let prev_v = v
result.append(v)
v = _V(maxIndex: d)
// The code in this loop is _very_ hot—the loop bounds increases in terms
// of the iterator of the outer loop!
for k in stride(from: -d, through: d, by: 2)
if k == -d
x = prev_v[k &+ 1]
else
let km = prev_v[k &- 1]
if k != d
let kp = prev_v[k &+ 1]
if km < kp
x = kp
else
x = km &+ 1
else
x = km &+ 1
y = x &- k
while x < n && y < m
if !cmp(a[x], b[y])
break;
x &+= 1
y &+= 1
v[k] = x
if x >= n && y >= m
break iterator
if x >= n && y >= m
break
//_internalInvariant(x >= n && y >= m)
return result
// Backtrack through the trace generated by the Myers descent to produce the changes that make up the diff
func _formChanges(
from a: UnsafeBufferPointer<C.Element>,
to b: UnsafeBufferPointer<C.Element>,
using trace: [_V]
) -> [CollectionDifference<C.Element>.Change]
var changes = [CollectionDifference<C.Element>.Change]()
changes.reserveCapacity(trace.count)
var x = a.count
var y = b.count
for d in stride(from: trace.count &- 1, to: 0, by: -1)
let v = trace[d]
let k = x &- y
let prev_k = (k == -d || (k != d && v[k &- 1] < v[k &+ 1])) ? k &+ 1 : k &- 1
let prev_x = v[prev_k]
let prev_y = prev_x &- prev_k
while x > prev_x && y > prev_y
// No change at this position.
x &-= 1
y &-= 1
//_internalInvariant((x == prev_x && y > prev_y) || (y == prev_y && x > prev_x))
if y != prev_y
changes.append(.insert(offset: prev_y, element: b[prev_y], associatedWith: nil))
else
changes.append(.remove(offset: prev_x, element: a[prev_x], associatedWith: nil))
x = prev_x
y = prev_y
return changes
/* Splatting the collections into contiguous storage has two advantages:
*
* 1) Subscript access is much faster
* 2) Subscript index becomes Int, matching the iterator types in the algorithm
*
* Combined, these effects dramatically improves performance when
* collections differ significantly, without unduly degrading runtime when
* the parameters are very similar.
*
* In terms of memory use, the linear cost of creating a ContiguousArray (when
* necessary) is significantly less than the worst-case n² memory use of the
* descent algorithm.
*/
func _withContiguousStorage<C: Collection, R>(
for values: C,
_ body: (UnsafeBufferPointer<C.Element>) throws -> R
) rethrows -> R
if let result = try values.withContiguousStorageIfAvailable(body)
return result
let array = ContiguousArray(values)
return try array.withUnsafeBufferPointer(body)
return _withContiguousStorage(for: old) a in
return _withContiguousStorage(for: new) b in
return CollectionDifference(_formChanges(from: a, to: b, using: _descent(from: a, to: b)))!
CollectionDifference
public struct CollectionDifference<ChangeElement>
/// A single change to a collection.
@frozen
public enum Change
/// An insertion.
///
/// The `offset` value is the offset of the inserted element in the final
/// state of the collection after the difference is fully applied.
/// A non-`nil` `associatedWith` value is the offset of the complementary
/// change.
case insert(offset: Int, element: ChangeElement, associatedWith: Int?)
/// A removal.
///
/// The `offset` value is the offset of the element to be removed in the
/// original state of the collection. A non-`nil` `associatedWith` value is
/// the offset of the complementary change.
case remove(offset: Int, element: ChangeElement, associatedWith: Int?)
// Internal common field accessors
internal var _offset: Int
get
switch self
case .insert(offset: let o, element: _, associatedWith: _):
return o
case .remove(offset: let o, element: _, associatedWith: _):
return o
internal var _element: ChangeElement
get
switch self
case .insert(offset: _, element: let e, associatedWith: _):
return e
case .remove(offset: _, element: let e, associatedWith: _):
return e
internal var _associatedOffset: Int?
get
switch self
case .insert(offset: _, element: _, associatedWith: let o):
return o
case .remove(offset: _, element: _, associatedWith: let o):
return o
/// The insertions contained by this difference, from lowest offset to
/// highest.
public let insertions: [Change]
/// The removals contained by this difference, from lowest offset to highest.
public let removals: [Change]
/// The public initializer calls this function to ensure that its parameter
/// meets the conditions set in its documentation.
///
/// - Parameter changes: a collection of `CollectionDifference.Change`
/// instances intended to represent a valid state transition for
/// `CollectionDifference`.
///
/// - Returns: whether the parameter meets the following criteria:
///
/// 1. All insertion offsets are unique
/// 2. All removal offsets are unique
/// 3. All associations between insertions and removals are symmetric
///
/// Complexity: O(`changes.count`)
private static func _validateChanges<Changes: Collection>(
_ changes: Changes
) -> Bool where Changes.Element == Change
if changes.isEmpty
return true
var insertAssocToOffset = Dictionary<Int, Int>()
var removeOffsetToAssoc = Dictionary<Int, Int>()
var insertOffset = Set<Int>()
var removeOffset = Set<Int>()
for change in changes
let offset = change._offset
if offset < 0
return false
switch change
case .remove(_, _, _):
if removeOffset.contains(offset)
return false
removeOffset.insert(offset)
case .insert(_, _, _):
if insertOffset.contains(offset)
return false
insertOffset.insert(offset)
if let assoc = change._associatedOffset
if assoc < 0
return false
switch change
case .remove(_, _, _):
if removeOffsetToAssoc[offset] != nil
return false
removeOffsetToAssoc[offset] = assoc
case .insert(_, _, _):
if insertAssocToOffset[assoc] != nil
return false
insertAssocToOffset[assoc] = offset
return removeOffsetToAssoc == insertAssocToOffset
/// Creates a new collection difference from a collection of changes.
///
/// To find the difference between two collections, use the
/// `difference(from:)` method declared on the `BidirectionalCollection`
/// protocol.
///
/// The collection of changes passed as `changes` must meet these
/// requirements:
///
/// - All insertion offsets are unique
/// - All removal offsets are unique
/// - All associations between insertions and removals are symmetric
///
/// - Parameter changes: A collection of changes that represent a transition
/// between two states.
///
/// - Complexity: O(*n* * log(*n*)), where *n* is the length of the
/// parameter.
public init?<Changes: Collection>(
_ changes: Changes
) where Changes.Element == Change
guard CollectionDifference<ChangeElement>._validateChanges(changes) else
return nil
self.init(_validatedChanges: changes)
/// Internal initializer for use by algorithms that cannot produce invalid
/// collections of changes. These include the Myers' diff algorithm,
/// self.inverse(), and the move inferencer.
///
/// If parameter validity cannot be guaranteed by the caller then
/// `CollectionDifference.init?(_:)` should be used instead.
///
/// - Parameter c: A valid collection of changes that represent a transition
/// between two states.
///
/// - Complexity: O(*n* * log(*n*)), where *n* is the length of the
/// parameter.
internal init<Changes: Collection>(
_validatedChanges changes: Changes
) where Changes.Element == Change
let sortedChanges = changes.sorted (a, b) -> Bool in
switch (a, b)
case (.remove(_, _, _), .insert(_, _, _)):
return true
case (.insert(_, _, _), .remove(_, _, _)):
return false
default:
return a._offset < b._offset
// Find first insertion via binary search
let firstInsertIndex: Int
if sortedChanges.isEmpty
firstInsertIndex = 0
else
var range = 0...sortedChanges.count
while range.lowerBound != range.upperBound
let i = (range.lowerBound + range.upperBound) / 2
switch sortedChanges[i]
case .insert(_, _, _):
range = range.lowerBound...i
case .remove(_, _, _):
range = (i + 1)...range.upperBound
firstInsertIndex = range.lowerBound
removals = Array(sortedChanges[0..<firstInsertIndex])
insertions = Array(sortedChanges[firstInsertIndex..<sortedChanges.count])
public func inverse() -> Self
return CollectionDifference(_validatedChanges: self.map c in
switch c
case .remove(let o, let e, let a):
return .insert(offset: o, element: e, associatedWith: a)
case .insert(let o, let e, let a):
return .remove(offset: o, element: e, associatedWith: a)
)
CollectionDifference 扩展
extension CollectionDifference: Collection
public typealias Element = Change
/// The position of a collection difference.
@frozen
public struct Index
// Opaque index type is isomorphic to Int
@usableFromInline
internal let _offset: Int
internal init(_offset offset: Int)
_offset = offset
public var startIndex: Index
return Index(_offset: 0)
public var endIndex: Index
return Index(_offset: removals.count + insertions.count)
public func index(after index: Index) -> Index
return Index(_offset: index._offset + 1)
public subscript(position: Index) -> Element
if position._offset < removals.count
return removals[removals.count - (position._offset + 1)]
return insertions[position._offset - removals.count]
public func index(before index: Index) -> Index
return Index(_offset: index._offset - 1)
public func formIndex(_ index: inout Index, offsetBy distance: Int)
index = Index(_offset: index._offset + distance)
public func distance(from start: Index, to end: Index) -> Int
return end._offset - start._offset
extension CollectionDifference.Index: Equatable
@inlinable
public static func ==(
lhs: CollectionDifference.Index,
rhs: CollectionDifference.Index
) -> Bool
return lhs._offset == rhs._offset
extension CollectionDifference.Index: Comparable
@inlinable
public static func <(
lhs: CollectionDifference.Index,
rhs: CollectionDifference.Index
) -> Bool
return lhs._offset < rhs._offset
extension CollectionDifference.Index: Hashable
@inlinable
public func hash(into hasher: inout Hasher)
hasher.combine(_offset)
extension CollectionDifference.Change: Equatable where ChangeElement: Equatable
extension CollectionDifference: Equatable where ChangeElement: Equatable
extension CollectionDifference.Change: Hashable where ChangeElement: Hashable
extension CollectionDifference: Hashable where ChangeElement: Hashable
extension CollectionDifference where ChangeElement: Hashable
/// Returns a new collection difference with associations between individual
/// elements that have been removed and inserted only once.
///
/// - Returns: A collection difference with all possible moves inferred.
///
/// - Complexity: O(*n*) where *n* is the number of collection differences.
public func inferringMoves() -> CollectionDifference<ChangeElement>
let uniqueRemovals: [ChangeElement: Int?] =
var result = [ChangeElement: Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
for removal in removals
let element = removal._element
if result[element] != .none
result[element] = .some(.none)
else
result[element] = .some(removal._offset)
return result.filter (_, v) -> Bool in
v != .none
()
let uniqueInsertions: [ChangeElement: Int?] =
var result = [ChangeElement: Int?](minimumCapacity: Swift.min(removals.count, insertions.count))
for insertion in insertions
let element = insertion._element
if result[element] != .none
result[element] = .some(.none)
else
result[element] = .some(insertion._offset)
return result.filter (_, v) -> Bool in
v != .none
()
return CollectionDifference(_validatedChanges: map( (change: Change) -> Change in
switch change
case .remove(offset: let offset, element: let element, associatedWith: _):
if uniqueRemovals[element] == nil
return change
if let assoc = uniqueInsertions[element]
return .remove(offset: offset, element: element, associatedWith: assoc)
case .insert(offset: let offset, element: let element, associatedWith: _):
if uniqueInsertions[element] == nil
return change
if let assoc = uniqueRemovals[element]
return .insert(offset: offset, element: element, associatedWith: assoc)
return change
))
【讨论】:
【参考方案3】:Xcode 11 已支持(仅适用于 iOS 13 或更高版本)
https://developer.apple.com/documentation/swift/array/3200716-difference
let oldNames = ["a", "b", "c", "d"]
let newNames = ["a", "b", "d", "e"]
let difference = newNames.difference(from: oldNames)
for change in difference
switch change
case let .remove(offset, oldElement, _):
print("remove:", offset, oldElement)
case let .insert(offset, newElement, _):
print("insert:", offset, newElement)
输出
remove: 2 c
insert: 3 e
【讨论】:
尝试使用差异时出现此错误。 'difference(from:)' 仅适用于 iOS 13 或更新版本【参考方案4】:最简单的方法是使用Set。集合有一个 symmetricDifference() 方法可以做到这一点,因此您只需将两个数组转换为集合,然后将结果转换回数组。
这是一个更简单的扩展:
extension Array where Element: Hashable
func difference(from other: [Element]) -> [Element]
let thisSet = Set(self)
let otherSet = Set(other)
return Array(thisSet.symmetricDifference(otherSet))
下面是一些示例代码,您可以使用它来试用:
let names1 = ["student", "class", "teacher"]
let names2 = ["class", "Teacher", "clas-s-room"]
let difference = names1.difference(from: names2)
这会将 difference 设置为 ["student", "clas-s-room"],因为它们是在任一数组中仅出现一次的两个名称。
【讨论】:
Source @DoesData 我正要发表同样的评论,然后我意识到这个答案是在 hackingwithswift 帖子之前过时的,所以也许 this 是 their 来源。 @monstermac77 很公平。但是,该链接仍提供其他详细信息并且很有帮助。 Apple Documentation 也提供了一个具体的例子。 这似乎没有在返回数组时进行正确的转换。使用这个确切的例子,我得到“无法将 'CollectionDifference这是一个的实现,但是否是你所追求的完全不可能说,因为你还没有指定你认为的答案应该是什么:
let answer = zip(array1, array2).map $0.0 == $0.1
这会给你一个布尔值列表,true
如果答案匹配正确答案,false
如果不匹配。
但是假设您想要的是那些正确答案的索引列表。那么你可以说:
let answer = zip(array1, array2).enumerated().filter()
$1.0 == $1.1
.map$0.0
如果您想要那些不正确答案的索引列表,只需将==
更改为!=
。
【讨论】:
添加了另一个实现,我们假设您想要的是正确或错误答案的索引列表。 欣赏!非常感谢。 真正很棒的是,这个答案设法让map
、filter
、zip
和enumerate
一起参与 - 您需要按顺序了解的关键事项在 Swift 中使用数组(只有 reduce
被省略 - 在这个问题中找不到它的用途)。 :)
必须 array1 和 array2 具有相同数量的元素(计数),或者它们可能不相等?
为了它,用reduce计算差异的数量...... .reduce(0) (count, item) -> Int in return count + 1 跨度>
以上是关于比较 2 个数组并列出差异 - Swift的主要内容,如果未能解决你的问题,请参考以下文章