Related
NSArray has - (NSUInteger)indexOfObject:(id)obj inSortedRange:(NSRange)r options:(NSBinarySearchingOptions)opts usingComparator:(NSComparator)cmp to determine the insert position of a new object in a sorted array.
What is the best and high-performance way to do this in pure Swift?
Something along the lines of:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
myArray.append("c")
myArray.sort { $0 < $1 }
// myArray is now [a, b, c, d, e]
Instead of appending the new element and then sorting the array, I would like to figure out the correct position and insert the element:
let index = [... how to calculate this index ??? ...]
myArray.insert("c", atIndex: index)
Here is a possible implementation in Swift using binary search (from
http://rosettacode.org/wiki/Binary_search#Swift with slight modifications):
extension Array {
func insertionIndexOf(_ elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int {
var lo = 0
var hi = self.count - 1
while lo <= hi {
let mid = (lo + hi)/2
if isOrderedBefore(self[mid], elem) {
lo = mid + 1
} else if isOrderedBefore(elem, self[mid]) {
hi = mid - 1
} else {
return mid // found at position mid
}
}
return lo // not found, would be inserted at position lo
}
}
As with indexOfObject:inSortedRange:options:usingComparator: it is assumed that
the array is sorted with respect to the comparator.
It returns either (any) index of the element if the element is already present in the
array, or the index where it can be inserted while preserving the order. This
corresponds to the NSBinarySearchingInsertionIndex of the NSArray method.
Usage:
let newElement = "c"
let index = myArray.insertionIndexOf(newElement) { $0 < $1 } // Or: myArray.indexOf(c, <)
myArray.insert(newElement, at: index)
In swift 3 you can use index(where:):
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
if let index = myArray.index(where: { $0 > newElement }) {
myArray.insert(newElement, at: index)
}
Note that in this case you need to reverse the condition inside the closure (i.e. > instead of < for increasing elements in array), because the index you are interested in is the first element that does NOT match the predicate. Also, this method will return nil if the newly inserted element is going to be the last in the array (newElement = "z" in the example above.
For convenience, this can be wrapped to a separate function that will handle all these issues:
extension Collection {
func insertionIndex(of element: Self.Iterator.Element,
using areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> Index {
return index(where: { !areInIncreasingOrder($0, element) }) ?? endIndex
}
}
Usage:
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
let index = myArray.insertionIndex(of: newElement, using: <)
myArray.insert(newElement, at: index)
According to WWDC 2018 Session 406: Swift Generics (Expanded) the binary search can be performed in a more efficient and even more generic way by slicing the collection object.
There are two considerable benefits of Slice:
A slice is always a subset of the original object without allocating additional memory.
All indices of the slice refer to the original object.
For example if you slice an array of 5 objects let slice = array[2..<4] then slice.startIndex is 2 not 0.
RandomAccessCollection is a protocol (inherited from BidirectionalCollection) which a variety of structs / classes conform to
extension RandomAccessCollection where Element : Comparable {
func insertionIndex(of value: Element) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex
}
}
Example:
let array = [1, 2, 4, 7, 8]
let index = array.insertionIndex(of: 6) // 3
You can extend the function to check against a predicate closure instead of a single value
extension RandomAccessCollection { // the predicate version is not required to conform to Comparable
func insertionIndex(for predicate: (Element) -> Bool) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if predicate(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Example:
struct Person { let name : String }
let array = [Person(name: "Adam"), Person(name: "Cynthia"), Person(name: "Nancy"), Person(name: "Tom")]
let index = array.insertionIndex{ $0.name < "Bruce" } // 1
If you know your array is sorted, you can use this method -- it will work with arrays of any type. It will traverse the whole array each time, so don't use this with large arrays - go for another data type if you have larger needs!
func insertSorted<T: Comparable>(inout seq: [T], newItem item: T) {
let index = seq.reduce(0) { $1 < item ? $0 + 1 : $0 }
seq.insert(item, atIndex: index)
}
var arr = [2, 4, 6, 8]
insertSorted(&arr, newItem: 5)
insertSorted(&arr, newItem: 3)
insertSorted(&arr, newItem: -3)
insertSorted(&arr, newItem: 11)
// [-3, 2, 3, 4, 5, 6, 8, 11]
Building on #vadian's and #Martin R's answers, I noticed some minor discrepancies, mostly with the insertion index either not matching the index of an equivalent element in the collection, or of it not matching the first index of a sequence of equivalent elements.
For instance:
If you wanted to find an insertion index for 5 in [4, 5, 6], the index 2 would be returned, which may be problematic if you want to simply search for the value.
In [5, 5, 5], searching once again for 5 returns the index 1, which is not the first insertion index.
This doesn't match with the behavior of NSArray's implementation and its various options, so here is yet another solution that tries to take this into account:
extension RandomAccessCollection {
/// Get the index of or an insertion index for a new element in
/// a sorted collection in ascending order.
/// - Parameter value: The element to insert into the array.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element
) -> Index where Element: Comparable {
sortedInsertionIndex(of: element, by: <)
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
/// - Parameters:
/// - value: The element to insert into the array.
/// - areInIncreasingOrder:
/// A closure that determines if the first element should
/// come before the second element. For instance: `<`.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index {
try sortedInsertionIndex { try areInIncreasingOrder($0, element) }
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
///
/// This variation is useful when comparing an element that
/// is of a different type than those already in the array.
/// - Parameter isOrderedAfter:
/// Return `true` if the new element should come after the one
/// provided in the closure, or `false` otherwise. For instance
/// `{ $0 < newElement }` to sort elements in increasing order.
/// - Returns: The index suitable for inserting the new element into
/// the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
where isOrderedAfter: (Element) throws -> Bool
) rethrows -> Index {
var slice: SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count/2)
if try isOrderedAfter(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Since sometimes you don't care about the insertion index, but instead the first or last index that matches a given element, here are variations on the above that satisfy those requirements as well:
extension RandomAccessCollection {
#inlinable
public func sortedFirstIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedFirstIndex(of: element, by: <)
}
#inlinable
public func sortedFirstIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element, by: areInIncreasingOrder)
guard insertionIndex < endIndex, self[insertionIndex] == element else { return nil }
return insertionIndex
}
#inlinable
public func sortedLastIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedLastIndex(of: element, by: <)
}
#inlinable
public func sortedLastIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element) { try areInIncreasingOrder($1, $0) }
let finalIndex = index(insertionIndex, offsetBy: -1)
guard finalIndex >= startIndex, self[finalIndex] == element else { return nil }
return finalIndex
}
}
A binary search tree here is the way to go.
On an ordered array, take the element in the middle and see if the object at that position is greater than your new object. That way you can forget the half of the array elements with one single comparison.
Repeat that step with the remaining half. Again, with a single comparison you can forget the half of the remaining objects. Your target element count is now a quarter of the array size at the beginning with only two comparisons.
Repeat that until you found the correct position to insert the new element.
Here is a a good article on binary search trees with swift
In swift 5:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
let newElement = "c"
let index = myArray.firstIndex(where: { newElement < $0 })
myArray.insert(newElement, at: index ?? myArray.endIndex)
If you'd like to make the call-site more pretty:
extension Array where Element: Comparable {
/// Insert element in the correct location of a sorted array
mutating func insertSorted(_ element: Element) {
let index = firstIndex(where: { element < $0 })
insert(element, at: index ?? endIndex)
}
}
myArray.insertSorted("c")
Slight update from binary search:
extension Array {
mutating func binaryAppend(_ item:Element, sortBy: (Element,Element) -> Bool) {
var start:Int = 0
var end:Int = self.count - 1
while start <= end{
let mid = (start + end) / 2
if sortBy(self[mid], item){
start = start + 1
} else{
end = end - 1
}
}
self.insert(item, at: start)
}
}
You can use it like:
var arr = [1,3,5,7,9]
arr.binaryAppend(2, sortBy: {$0 < $1}) //[1,2,3,5,7,9]
NSArray has - (NSUInteger)indexOfObject:(id)obj inSortedRange:(NSRange)r options:(NSBinarySearchingOptions)opts usingComparator:(NSComparator)cmp to determine the insert position of a new object in a sorted array.
What is the best and high-performance way to do this in pure Swift?
Something along the lines of:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
myArray.append("c")
myArray.sort { $0 < $1 }
// myArray is now [a, b, c, d, e]
Instead of appending the new element and then sorting the array, I would like to figure out the correct position and insert the element:
let index = [... how to calculate this index ??? ...]
myArray.insert("c", atIndex: index)
Here is a possible implementation in Swift using binary search (from
http://rosettacode.org/wiki/Binary_search#Swift with slight modifications):
extension Array {
func insertionIndexOf(_ elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int {
var lo = 0
var hi = self.count - 1
while lo <= hi {
let mid = (lo + hi)/2
if isOrderedBefore(self[mid], elem) {
lo = mid + 1
} else if isOrderedBefore(elem, self[mid]) {
hi = mid - 1
} else {
return mid // found at position mid
}
}
return lo // not found, would be inserted at position lo
}
}
As with indexOfObject:inSortedRange:options:usingComparator: it is assumed that
the array is sorted with respect to the comparator.
It returns either (any) index of the element if the element is already present in the
array, or the index where it can be inserted while preserving the order. This
corresponds to the NSBinarySearchingInsertionIndex of the NSArray method.
Usage:
let newElement = "c"
let index = myArray.insertionIndexOf(newElement) { $0 < $1 } // Or: myArray.indexOf(c, <)
myArray.insert(newElement, at: index)
In swift 3 you can use index(where:):
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
if let index = myArray.index(where: { $0 > newElement }) {
myArray.insert(newElement, at: index)
}
Note that in this case you need to reverse the condition inside the closure (i.e. > instead of < for increasing elements in array), because the index you are interested in is the first element that does NOT match the predicate. Also, this method will return nil if the newly inserted element is going to be the last in the array (newElement = "z" in the example above.
For convenience, this can be wrapped to a separate function that will handle all these issues:
extension Collection {
func insertionIndex(of element: Self.Iterator.Element,
using areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> Index {
return index(where: { !areInIncreasingOrder($0, element) }) ?? endIndex
}
}
Usage:
var myArray = ["a", "b", "d", "e"]
let newElement = "c"
let index = myArray.insertionIndex(of: newElement, using: <)
myArray.insert(newElement, at: index)
According to WWDC 2018 Session 406: Swift Generics (Expanded) the binary search can be performed in a more efficient and even more generic way by slicing the collection object.
There are two considerable benefits of Slice:
A slice is always a subset of the original object without allocating additional memory.
All indices of the slice refer to the original object.
For example if you slice an array of 5 objects let slice = array[2..<4] then slice.startIndex is 2 not 0.
RandomAccessCollection is a protocol (inherited from BidirectionalCollection) which a variety of structs / classes conform to
extension RandomAccessCollection where Element : Comparable {
func insertionIndex(of value: Element) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex
}
}
Example:
let array = [1, 2, 4, 7, 8]
let index = array.insertionIndex(of: 6) // 3
You can extend the function to check against a predicate closure instead of a single value
extension RandomAccessCollection { // the predicate version is not required to conform to Comparable
func insertionIndex(for predicate: (Element) -> Bool) -> Index {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if predicate(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Example:
struct Person { let name : String }
let array = [Person(name: "Adam"), Person(name: "Cynthia"), Person(name: "Nancy"), Person(name: "Tom")]
let index = array.insertionIndex{ $0.name < "Bruce" } // 1
If you know your array is sorted, you can use this method -- it will work with arrays of any type. It will traverse the whole array each time, so don't use this with large arrays - go for another data type if you have larger needs!
func insertSorted<T: Comparable>(inout seq: [T], newItem item: T) {
let index = seq.reduce(0) { $1 < item ? $0 + 1 : $0 }
seq.insert(item, atIndex: index)
}
var arr = [2, 4, 6, 8]
insertSorted(&arr, newItem: 5)
insertSorted(&arr, newItem: 3)
insertSorted(&arr, newItem: -3)
insertSorted(&arr, newItem: 11)
// [-3, 2, 3, 4, 5, 6, 8, 11]
Building on #vadian's and #Martin R's answers, I noticed some minor discrepancies, mostly with the insertion index either not matching the index of an equivalent element in the collection, or of it not matching the first index of a sequence of equivalent elements.
For instance:
If you wanted to find an insertion index for 5 in [4, 5, 6], the index 2 would be returned, which may be problematic if you want to simply search for the value.
In [5, 5, 5], searching once again for 5 returns the index 1, which is not the first insertion index.
This doesn't match with the behavior of NSArray's implementation and its various options, so here is yet another solution that tries to take this into account:
extension RandomAccessCollection {
/// Get the index of or an insertion index for a new element in
/// a sorted collection in ascending order.
/// - Parameter value: The element to insert into the array.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element
) -> Index where Element: Comparable {
sortedInsertionIndex(of: element, by: <)
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
/// - Parameters:
/// - value: The element to insert into the array.
/// - areInIncreasingOrder:
/// A closure that determines if the first element should
/// come before the second element. For instance: `<`.
/// - Returns: The index suitable for inserting the new element
/// into the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index {
try sortedInsertionIndex { try areInIncreasingOrder($0, element) }
}
/// Get the index of or an insertion index for a new element in
/// a sorted collection that matches the rule defined by the predicate.
///
/// This variation is useful when comparing an element that
/// is of a different type than those already in the array.
/// - Parameter isOrderedAfter:
/// Return `true` if the new element should come after the one
/// provided in the closure, or `false` otherwise. For instance
/// `{ $0 < newElement }` to sort elements in increasing order.
/// - Returns: The index suitable for inserting the new element into
/// the array, or the first index of an existing element.
#inlinable
public func sortedInsertionIndex(
where isOrderedAfter: (Element) throws -> Bool
) rethrows -> Index {
var slice: SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count/2)
if try isOrderedAfter(slice[middle]) {
slice = slice[index(after: middle)...]
} else {
slice = slice[..<middle]
}
}
return slice.startIndex
}
}
Since sometimes you don't care about the insertion index, but instead the first or last index that matches a given element, here are variations on the above that satisfy those requirements as well:
extension RandomAccessCollection {
#inlinable
public func sortedFirstIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedFirstIndex(of: element, by: <)
}
#inlinable
public func sortedFirstIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element, by: areInIncreasingOrder)
guard insertionIndex < endIndex, self[insertionIndex] == element else { return nil }
return insertionIndex
}
#inlinable
public func sortedLastIndex(
of element: Element
) -> Index? where Element: Comparable {
sortedLastIndex(of: element, by: <)
}
#inlinable
public func sortedLastIndex(
of element: Element,
by areInIncreasingOrder: (Element, Element) throws -> Bool
) rethrows -> Index? where Element: Comparable {
let insertionIndex = try sortedInsertionIndex(of: element) { try areInIncreasingOrder($1, $0) }
let finalIndex = index(insertionIndex, offsetBy: -1)
guard finalIndex >= startIndex, self[finalIndex] == element else { return nil }
return finalIndex
}
}
A binary search tree here is the way to go.
On an ordered array, take the element in the middle and see if the object at that position is greater than your new object. That way you can forget the half of the array elements with one single comparison.
Repeat that step with the remaining half. Again, with a single comparison you can forget the half of the remaining objects. Your target element count is now a quarter of the array size at the beginning with only two comparisons.
Repeat that until you found the correct position to insert the new element.
Here is a a good article on binary search trees with swift
In swift 5:
var myArray = ["b", "e", "d", "a"]
myArray.sort { $0 < $1 }
// myArray is now [a, b, d, e]
let newElement = "c"
let index = myArray.firstIndex(where: { newElement < $0 })
myArray.insert(newElement, at: index ?? myArray.endIndex)
If you'd like to make the call-site more pretty:
extension Array where Element: Comparable {
/// Insert element in the correct location of a sorted array
mutating func insertSorted(_ element: Element) {
let index = firstIndex(where: { element < $0 })
insert(element, at: index ?? endIndex)
}
}
myArray.insertSorted("c")
Slight update from binary search:
extension Array {
mutating func binaryAppend(_ item:Element, sortBy: (Element,Element) -> Bool) {
var start:Int = 0
var end:Int = self.count - 1
while start <= end{
let mid = (start + end) / 2
if sortBy(self[mid], item){
start = start + 1
} else{
end = end - 1
}
}
self.insert(item, at: start)
}
}
You can use it like:
var arr = [1,3,5,7,9]
arr.binaryAppend(2, sortBy: {$0 < $1}) //[1,2,3,5,7,9]
I am creating a function that will filter an array, e.g.
x = [10,20,30,40,50]
filter(x,10,20)
output should be 30,40,50.
I am getting an index out of bounds error .
Here's my code:
func filterArray( _ x: [Int], _ nums: Int...) -> [Int]{
var arrayX = x
for i in 0...arrayX.count-1{
for j in 0...nums.count-1 {
if arrayX[i] == nums[j]{//Changed arrayX to x because x was never changed
if let index = arrayX.index(of: nums[j]) {
arrayX.remove(at: index) //error is here
}
else{
}
}
}
}
return arrayX
}
var mArray = [10,20,30,40,50]
filterArray(mArray,10)
The way you are doing it is not correct, you are altering an array while looping through it. When you remove an object from the array, the array count changes but the loop still run using the previously calculated array.count value.
There is a much simpler way of doing this, you just need to combine filter and contains functions together for achieving this:
func filterArray( _ x: [Int], _ nums: Int...) -> [Int]
{
let filteredArray = x.filter({ !nums.contains($0)})
return filteredArray
}
once you remove one element from array its size change, but your loop is still going till previous count that is causing the issue try taking different array to go through the loop and for storing the result and you don't need to find the index its already there, value of 'i' is the index of the element.
You function can be faster if you use a Set.
func filter(list: [Int], _ remove: Int...) -> [Int] {
let removeSet = Set(remove)
return list.filter { removeSet.contains($0) }
}
I have a dictionary that's updated from another class. I have a property observer on the dictionary so I know when a value has been added or removed.
I create a sorted array based on the values of the dictionary. I need to keep this array updated and retain the index associated with the update for use with a UITableView. My UI is as such that a wholesale reloading of data isn't possible - I need to directly insert or remove rows based on what the update was.
I have simplified this into a playground:
func dictionaryUpdated() {
print("dictionary updated")
// Add or remove string at index depending on order.
}
var myDictionary : [Int:String] = ["Bob".hashValue:"Bob","Dave".hashValue:"Dave","Yoda".hashValue:"Yoda","Windu".hashValue:"Windu","Obi Wan".hashValue:"Obi Wan","Qui-gon".hashValue:"Qui-gon","Anakin".hashValue:"Anakin"] { didSet { dictionaryUpdated() } }
func addEntry(entry: String) {
myDictionary[entry.hashValue] = entry
}
func removeEntry(entry: String) {
myDictionary.removeValueForKey(entry.hashValue)
}
// sort the keys alphabetically while creating the array
var valuesArray = myDictionary.values.sort { (lhs, rhs) -> Bool in
return lhs < rhs
}
I have tried using an NSMutableOrderedSet but the keys can only be Strings.
Just playing around in playground. Can be much more elegant though...
var valuesArray: [String] = [] { didSet { valuesArray.sortInPlace { $0 < $1 } } }
func dictionaryUpdated(old: [Int: String]) {
let added = myDictionary.count > old.count
let item: [String] = added ? myDictionary.values.filter { !old.values.contains($0) } : old.values.filter { !myDictionary.values.contains($0) }
valuesArray += item
let index = valuesArray.indexOf(item[0])!
print("item " + (added ? "added" : "removed") + ": \(item) at index \(index)")
}
var myDictionary: [Int: String] = ["Yoda".hashValue: "Yoda", "Windu".hashValue: "Windu", "Obi Wan".hashValue: "Obi Wan"] {
didSet {
dictionaryUpdated(oldValue)
}
}
addEntry("Darth Vader")
print(valuesArray)
Output:
item added: ["Darth Vader"] at index 0
["Darth Vader", "Obi Wan", "Windu", "Yoda"]
Assuming you have the sorted array before and after the property change (which can be achieved via another instance variable), what you need to do is to compare the old and the new array, and detect which which indexes changed.
An elegant solution to this problem would be to add a diff method to the Array class which computes the difference. The method might look something like this:
extension Array where Element: Equatable {
func diff(other: [Element]) -> (added: [Int], deleted: [Int], moved: [(from: Int, to: Int)]) {
var added: [Int] = []
var deleted: [Int] = []
var moved: [(from: Int, to: Int)] = []
for (i, item) in enumerate() {
if let j = other.indexOf({$0 == item}) {
if i != j {
moved.append((from: i, to: j))
}
} else {
deleted.append(i)
}
}
for (i, item) in other.enumerate() {
if indexOf({$0 == item}) == nil {
added.append(i)
}
}
return (added: added, deleted: deleted, moved: moved)
}
}
You would then use like this: valuesArray.diff(oldValuesArray).
I have 2 arrays:
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
I want to determine if list Array contains all findList elements.
By the way, elements might be String as well or other type.
How to do that?
I know that Swift provides contains method that works with one item.
Instead of iterating through arrays and doing filtering yourself, you can use NSSet to do all the work for you.
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
let listSet = NSSet(array: list)
let findListSet = NSSet(array: findList)
let allElemtsEqual = findListSet.isSubsetOfSet(otherSet: listSet)
NSSet is a lot faster than arrays at checking if it contains any object. In fact it's what it's designed for.
Edit: Using Swift's built-in Set.
let list = [1,2,3,4,5]
let findList = [1,3,5]
let listSet = Set(list)
let findListSet = Set(findList)
//**Swift 4.2 and Above**
let allElemsContained = findListSet.isSubset(of: listSet)
//below versions
//let allElemsContained = findListSet.isSubsetOf(listSet)
allSatisfy seems to be what you want, assuming you can't conform your elements to Hashable and use the set intersection approach others have mentioned:
let containsAll = subArray.allSatisfy(largerArray.contains)
Since Swift 4.2 you can write:
extension Array where Element: Equatable {
func satisfy(array: [Element]) -> Bool {
return self.allSatisfy(array.contains)
}
}
Otherwise for Swift 3, Swift 4 you can write this:
extension Array where Element: Equatable {
func contains(array: [Element]) -> Bool {
for item in array {
if !self.contains(item) { return false }
}
return true
}
}
You can see the:
contains method here
allSatisfy method here
This is just a simple extension that check if the array that you give is in the current array (self)
You can use the filter method to return all elements of findList which are not in list:
let notFoundList = findList.filter( { contains(list, $0) == false } )
then check if the length of the returned array is zero:
let contained = notFoundList.count == 0
Note that his solution traverses the entire findList array, so it doesn't stop as soon as a non contained element is found. It should be used if you also want to know which elements are not contained.
If you just need a boolean stating whether all elements are contained or not, then the solution provided by Maxim Shoustin is more efficient.
Consider following generic method:
func arrayContainsArray<S : SequenceType where S.Generator.Element : Equatable>
(src:S, lookFor:S) -> Bool{
for v:S.Generator.Element in lookFor{
if contains(src, v) == false{
return false
}
}
return true
}
The advantage - method stops after 1st fail and do not continue over findList
Tests
var listAsInt:Array<Int> = [1,2,3,4,5]
var findListAsInt:Array<Int> = [1,3,5]
var result = arrayContainsArray(listAsInt, findListAsInt) // true
listAsInt:Array<Int> = [1,2,3,4,5]
findListAsInt:Array<Int> = [1,3,5,7,8,9]
result = arrayContainsArray(listAsInt, findListAsInt) // false
var listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
var findListOfStr:Array<String> = ["bbb","ccc","eee"]
result = arrayContainsArray(listOfStr, findListOfStr) // true
listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
findListOfStr:Array<String> = ["bbb","ccc","eee","sss","fff","ggg"]
result = arrayContainsArray(listOfStr, findListOfStr) // false
(tested on Beta7)
As a complement to Sequence.contains(element) handling multiple elements, add this extension:
public extension Sequence where Element : Hashable {
func contains(_ elements: [Element]) -> Bool {
return Set(elements).isSubset(of:Set(self))
}
}
Used:
list.contains(findList)
Since this uses Set/Hashable it performs much better than Equatable alternatives.
Right now, I'd probably use something like:
let result = list.reduce(true, { $0 ? contains(findList, $1) : $0 })
...but then I did just read this article, which might be biasing me towards this kind of solution. You could probably make this more efficient without making it completely unreadable, but it's early and I've not had my coffee.
Extend the Array with the following methods:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
func isEqualTo< T : Equatable> (comparingArray : [T]) -> Bool {
if self.count != comparingArray.count {
return false
}
for e in comparingArray {
if !self.contains(e){
return false
}
}
return true
}
}
An example of how you can use it like this:
if selectedDates.isEqualTo(originalDates) {
//Arrays the same hide save button
} else {
//Arrays not the same, show Save & Discard Changes Button (if not shown)
}
Shout out to #David Berry for the contain method.
None of the previous answers seem to be right.
consider:
let a = [2,2]
let b = [1,2,3]
we wouldn't say that b actually "contains" a, but if your algorithm is based on for-loop & swift's built-in contains(element:) or a set, the above case would pass.
I use this set of extended methods myself. I hope this code snippet helps:
// Array + CommonElements.swift
import Foundation
public extension Array where Element: Hashable {
func set() -> Set<Array.Element> {
return Set(self)
}
func isSubset(of array: Array) -> Bool {
self.set().isSubset(of: array.set())
}
func isSuperset(of array: Array) -> Bool {
self.set().isSuperset(of: array.set())
}
func commonElements(between array: Array) -> Array {
let intersection = self.set().intersection(array.set())
return intersection.map({ $0 })
}
func hasCommonElements(with array: Array) -> Bool {
return self.commonElements(between: array).count >= 1 ? true : false
}
}
This is Maxim Shoustin's answer updated for Swift 3:
func arrayContainsArray<S : Sequence>
(src:S, lookFor:S) -> Bool where S.Iterator.Element : Equatable{
for v:S.Iterator.Element in lookFor{
if src.contains(v) == false{
return false
}
}
return true
}
If you need to determine, that one array is subArray of another.
public extension Array where Element: Equatable {
func isSuperArray(of array: Array<Element>) -> Bool {
guard
count >= array.count,
let indexes = array.first.flatMap(indexes(of:)),
!indexes.isEmpty else {
return false
}
let arraysForComparison = indexes
.compactMap { index -> [Element]? in
guard index + (array.count - 1) <= count else { return nil }
return Array(self[index..<(index + array.count)])
}
return arraysForComparison.contains(array)
}
func isSubArray(of array: Array<Element>) -> Bool {
array.isSuperArray(of: self)
}
private func indexes(of element: Element) -> [Index] {
enumerated()
.filter { element == $0.1 }
.map { index, _ in index }
}
}
Example of usage:
let array1 = [1, 2, 3, 4]
let array2 = [2, 3]
print(array1.isSuperArray(of: array2)) // true
print(array2.isSubArray(of: array1)) // true
print(array2.isSuperArray(of: array1)) // false
print(array1.isSubArray(of: array2)) // false