I have 2 sections in my tableview. Section 0 has data from an array and section 1 has data from an api. Now I want to check if the data from section 0 has a matching data to that of section 1 then I want to remove that particular entry from section 1. Not sure how to do that…
I have 2 arrays which populate data in each of the sections:
filteredSelectedProductList —> Section 0
productsList —> Section 1
This is the code I have:
if self.filteredSelectedProductList.isEmpty == false {
for prod in self.filteredSelectedProductList { //section 0
for filtProd in productsList { //section 1
if prod.productId == filtProd.productId {
//Here I want to remove the matching entry from productsList array. How can I do that..?
}
}
}
}
You can use filter where not contains,
something like this should work,
var arr1 = [1,3,4,5,6] // array 1 section 0
var arr2 = [2,34,5,6] // array 2 section 1
print(arr2.filter{!arr1.contains($0)}) // [2,34]
In your case you are using custom model, you can confirm to Equatable and do the following with it, then you can simple use it as i showed you above.
struct MyCustomObject {
var id: Int // some unique id you can compare things to
}
extension MyCustomObject: Equatable {
static func == (lhs: MyCustomObject, rhs: MyCustomObject) -> Bool {
return lhs.id == rhs.id
}
}
Usage :
var arra1: [MyCustomObject] = [] // section 0
var arra2: [MyCustomObject] = [] // section 1
print(arra2.filter{!arra1.contains($0)})
Swift 4.1
You can do by this way, You just have to put your array instead of this
var array1 = ["45","34","67"] //section 0
var array2 = ["45", "23"] // section 1
array2.removeAll(where: { array1.contains($0) }) // finalArray = ["23"]
You can use removeAll:
productsList.removeAll(where: { filteredSelectedProductList.contains($0) })
To use the contains, your models must conform to Equatable, otherwise you should do like this:
productsList.removeAll(where: { item in filteredSelectedProductList.contains(where: { $0.productId == item.productId }) })
Array.contains is an O(n) operation. Meaning that each element in the first array, you may have to search all of the second array. Resulting in O(m * n), m and n being the counts of the arrays, which is not efficient.
A better way would be to:
build a dictionary of the occurrences of elements in the first array,
then loop through the second array,
Append the elements in the second array that don't belong to the first one.
Here a function that does the above steps with a time complexity of O(m + n):
func intersect<T: Hashable>(_ a1: [T], with a2: [T]) -> [T] {
var result = [T]()
result.reserveCapacity(a2.count)
var dict1: [T: Int] = [T: Int].init(minimumCapacity: a1.count)
for x in a1 {
dict1[x, default: 0] += 1
}
for y in a2 {
if dict1[y] == nil || dict1[y] == 0 {
result.append(y)
} else {
dict1[y]! -= 1
}
}
return result
}
Note that the elements of the arrays have to conform to Hashable in order to use a dictionary. If an element occurs in the second array more than in the first, the extra duplicates are the ones kept. Which is not handled in all other answers
Here are some use cases :
let array1 = [1, 2, 3, 4, 5, 6]
let array2 = [1, 2, 2, 4, 7, 6]
intersect(array1, with: array2) //[2, 7]
struct MyStruct: Hashable { var id: Int }
let array3 = [MyStruct(id: 0), MyStruct(id: 2), MyStruct(id: 3)]
let array4 = [MyStruct(id: 1), MyStruct(id: 2), MyStruct(id: 2)]
intersect(array3, with: array4) //[{id 1}, {id 2}]
Related
I have two arrays:
let a = ["apple","banana","orange","pomelo","kiwi","melon"]
let b = [1, 2, 4]
a contains all the items and b contains the indexes of the ones I'm interested in.
So I would like to create a function to extract the items at the indexes specified in array b.
I can do tis with for loops:
for i in 0...a.count-1{
if i == b[i]{
print(a[i])
}
}
To make it clear, the desired output would be:
banana orange kiwi
The problem is that with big numbers the for loop would be too slow.
I would like to know if there exists something with a lower complexity.
You can simply map the indices and return the associated elements:
let aa = ["apple","banana","orange","pomelo","kiwi","melon"]
let bb = [1, 2, 4]
let elements = bb.map { aa[$0] }
print(elements) // ["banana", "orange", "kiwi"]
Or extending RandomAccessCollection protocol:
extension RandomAccessCollection {
func elements(at indices: [Index]) -> [Element] { indices.map { self[$0] } }
}
let a = ["apple","banana","orange","pomelo","kiwi","melon"]
let b = [1, 2, 4]
let elements = a.elements(at: b) // ["banana", "orange", "kiwi"]
I have code like below
let myNums = getXYZ(nums: [1,2,3,4,5])
func getXYZ(nums: [Int]) -> [Int] {
let newNum = nums.map { (num) -> Int in
if num == 2 {
//do something and continue execution with next element in list like break/fallthrough
return 0
}
return num
}
return newNum
}
print(myNums)`
This prints [1,0,3,4,5]
but i want the output to be [1,3,4,5]. How can I exclude 2? I want to alter the if statement used so as to not include in array when it sees number 2
I have to use .map here but to exclude 2..is there any possibility
Please let me know
I'd simply do a filter as described as your problem, you want to filter the numbers by removing another number.
var myNums = [1, 2, 3, 4, 5]
let excludeNums = [2]
let newNum = myNums.filter({ !excludeNums.contains($0) })
print(newNum) //1, 3, 4, 5
If you need to do a map, you could do a map first then filter.
let newNum = myNums.map({ $0*2 }).filter({ !excludeNums.contains($0) })
print(newNum) //4, 6, 8, 10
This maps to multiplying both by 2 and then filtering by removing the new 2 from the list. If you wanted to remove the initial 2 you would have to filter first then map. Since both return a [Int] you can call the operations in any order, as you deem necessary.
As suggested by #koropok, I had to make below changes
nums.compactMap { (num) -> Int? in
....
if num == 2 {
return nil
}
I suggest you to use filter instead of map:
let myNums = [1,2,3,4,5]
let result1 = myNums.filter{ return $0 != 2 }
print(result1) // This will print [1,3,4,5]
If you must definitely use map, then use compactMap:
let result2 = myNums.compactMap { return $0 == 2 ? nil : $0 }
print(result2) // This will print [1,3,4,5]
Hope this helps
filter is more appropriate than map for your use case.
If you want to exclude only 1 number:
func getXYZ(nums: [Int]) -> [Int] {
return nums.filter { $0 != 2 }
}
If you want to exclude a list of numbers, store those exclusions in a Set since Set.contains runs in O(1) time, whereas Array.contains runs in O(n) time.
func getXYZ(nums: [Int]) -> [Int] {
let excluded: Set<Int> = [2,4]
return nums.filter { !excluded.contains($0) }
}
My solution is based on enumerated() method:
let elements = nums.enumerated().compactMap { (index, value) in
( index == 0 ) ? nil : value
}
enumerated() add element's index as first closure argument
I have an array like:
var arr = [4,1,5,5,3]
I want to fetch subset from the array based on the occurrence of elements in it.
For example:
Elements with frequency 1 is {4,1,3}
Elements with frequency 2 is {5,5}
I followed this StackOverflow question but unable to figure out how to do the above thing.
Is there any way I can do this?
You can use an NSCountedSet to get the count of all elements in arr, then you can build a Dictionary, where the keys will be the number of occurencies for the elements and the values will be Arrays of the elements with key number of occurences. By iterating through Set(arr) rather than simply arr to build the Dictionary, you can make sure that repeating elements are only added once to the Dictionary (so for instance with your original example, 5 wouldn't be added twice as having a frequency of 2).
For the printing, you just need to iterate through the keys of the Dictionary and print the keys along with their corresponding values. I just sorted the keys to make the printing go in ascending order of number of occurences.
let arr = [4,1,5,5,3,2,3,6,2,7,8,2,7,2,8,8,8,7]
let counts = NSCountedSet(array: arr)
var countDict = [Int:[Int]]()
for element in Set(arr) {
countDict[counts.count(for: element), default: []].append(element)
}
countDict
for freq in countDict.keys.sorted() {
print("Elements with frequency \(freq) are {\(countDict[freq]!)}")
}
Output:
Elements with frequency 1 are {[4, 6, 1]}
Elements with frequency 2 are {[5, 3]}
Elements with frequency 3 are {[7]}
Elements with frequency 4 are {[2, 8]}
Swift 3 version:
let arr = [4,1,5,5,3,2,3,6,2,7,8,2,7,2,8,8,8,7]
let counts = NSCountedSet(array: arr)
var countDict = [Int:[Int]]()
for element in Set(arr) {
if countDict[counts.count(for: element)] != nil {
countDict[counts.count(for: element)]!.append(element)
} else {
countDict[counts.count(for: element)] = [element]
}
}
for freq in countDict.keys.sorted() {
print("Elements with frequency \(freq) are {\(countDict[freq]!)}")
}
You just need to get the occurrences of the elements and filter the elements that only occurs once or more than once as shown in this answer:
extension Array where Element: Hashable {
// Swift 4 or later
var occurrences: [Element: Int] {
return reduce(into: [:]) { $0[$1, default: 0] += 1 }
}
// // for Swift 3 or earlier
// var occurrences: [Element: Int] {
// var result: [Element: Int] = [:]
// forEach{ result[$0] = (result[$0] ?? 0) + 1}
// return result
// }
func frequencies(where isIncluded: (Int) -> Bool) -> Array {
return filter{ isIncluded(occurrences[$0] ?? 0) }
}
}
Playground Testing:
let arr = [5, 4, 1, 5, 5, 3, 5, 3]
let frequency1 = arr.frequencies {$0 == 1} // [4, 1]
let frequency2 = arr.frequencies {$0 == 2} // [3, 3]
let frequency3orMore = arr.frequencies {$0 >= 3} // [5, 5, 5, 5]
This is it:
func getSubset(of array: [Int], withFrequency frequency: Int) -> [Int]
{
var counts: [Int: Int] = [:]
for item in array
{
counts[item] = (counts[item] ?? 0) + 1
}
let filtered = counts.filter{ $0.value == frequency}
return Array(filtered.keys)
}
This is pure Swift (not using good old Next Step classes) and is using ideas from the SO link you supplied.
The counts dictionary contains the frequencies (value) of each of the int-values (key) in your array: [int-value : frequency].
var mentions = ["#alex", "#jason", "#jessica", "#john"]
I want to limit my array to 3 items, so I want to splice it:
var slice = [String]()
if mentions.count > 3 {
slice = mentions[0...3] //alex, jason, jessica
} else {
slice = mentions
}
However, I'm getting:
Ambiguous subscript with base type '[String]' and index type 'Range'
Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.31)
Target: x86_64-apple-macosx10.9
The problem is that mentions[0...3] returns an ArraySlice<String>, not an Array<String>. Therefore you could first use the Array(_:) initialiser in order to convert the slice into an array:
let first3Elements : [String] // An Array of up to the first 3 elements.
if mentions.count >= 3 {
first3Elements = Array(mentions[0 ..< 3])
} else {
first3Elements = mentions
}
Or if you want to use an ArraySlice (they are useful for intermediate computations, as they present a 'view' onto the original array, but are not designed for long term storage), you could subscript mentions with the full range of indices in your else:
let slice : ArraySlice<String> // An ArraySlice of up to the first 3 elements
if mentions.count >= 3 {
slice = mentions[0 ..< 3]
} else {
slice = mentions[mentions.indices] // in Swift 4: slice = mentions[...]
}
Although the simplest solution by far would be just to use the prefix(_:) method, which will return an ArraySlice of the first n elements, or a slice of the entire array if n exceeds the array count:
let slice = mentions.prefix(3) // ArraySlice of up to the first 3 elements
We can do like this,
let arr = [10,20,30,40,50]
let slicedArray = arr[1...3]
if you want to convert sliced array to normal array,
let arrayOfInts = Array(slicedArray)
You can try .prefix().
Returns a subsequence, up to the specified maximum length, containing the initial elements of the collection.
If the maximum length exceeds the number of elements in the collection, the result contains all the elements in the collection.
let numbers = [1, 2, 3, 4, 5]
print(numbers.prefix(2)) // Prints "[1, 2]"
print(numbers.prefix(10)) // Prints "[1, 2, 3, 4, 5]"
General solution:
extension Array {
func slice(size: Int) -> [[Element]] {
(0...(count / size)).map{Array(self[($0 * size)..<(Swift.min($0 * size + size, count))])}
}
}
Can also look at dropLast() function:
var mentions:[String] = ["#alex", "#jason", "#jessica", "#john"]
var slice:[String] = mentions
if mentions.count > 3 {
slice = Array(mentions.dropLast(mentions.count - 3))
}
//print(slice) => ["#alex", "#jason", "#jessica"]
I came up with this:
public extension Array {
func slice(count: Int) -> [some Collection] {
let n = self.count / count // quotient
let i = n * count // index
let r = self.count % count // remainder
let slices = (0..<n).map { $0 * count }.map { self[$0 ..< $0 + count] }
return (r > 0) ? slices + [self[i..<i + r]] : slices
}
}
You can also slice like this:
//Generic Method
func slice<T>(arrayList:[T], limit:Int) -> [T]{
return Array(arrayList[..<limit])
}
//How to Use
let firstThreeElements = slice(arrayList: ["#alex", "#jason", "#jessica", "#john"], limit: 3)
Array slice func extension:
extension Array {
func slice(with sliceSize: Int) -> [[Element]] {
guard self.count > 0 else { return [] }
var range = self.count / sliceSize
if self.count.isMultiple(of: sliceSize) {
range -= 1
}
return (0...range).map { Array(self[($0 * sliceSize)..<(Swift.min(($0 + 1) * sliceSize, self.count))]) }
}
}
I have 2 Arrays. Say, array1 = [1,2,3,4,5] and array2 = [2,3]. How could I check in swift if array1 contains at least one item from array2?
You can do this by simply passing in your array2's contains function into your array1's contains function (or vice versa), as your elements are Equatable.
let array1 = [2, 3, 4, 5]
let array2 = [20, 15, 2, 7]
// this is just shorthand for array1.contains(where: { array2.contains($0) })
if array1.contains(where: array2.contains) {
print("Array 1 and array 2 share at least one common element")
} else {
print("Array 1 doesn't contains any elements from array 2")
}
This works by looping through array 1's elements. For each element, it will then loop through array 2 to check if it exists in that array. If it finds that element, it will break and return true – else false.
This works because there are actually two flavours of contains. One takes a closure in order to check each element against a custom predicate, and the other just compares an element directly. In this example, array1 is using the closure version, and array2 is using the element version. And that is the reason you can pass a contains function into another contains function.
Although, as correctly pointed out by #AMomchilov, the above algorithm is O(n2). A good set intersection algorithm is O(n), as element lookup is O(1). Therefore if your code is performance critical, you should definitely use sets to do this (if your elements are Hashable), as shown by #simpleBob.
Although if you want to take advantage of the early exit that contains gives you, you'll want to do something like this:
extension Sequence where Iterator.Element : Hashable {
func intersects<S : Sequence>(with sequence: S) -> Bool
where S.Iterator.Element == Iterator.Element
{
let sequenceSet = Set(sequence)
return self.contains(where: sequenceSet.contains)
}
}
if array1.intersects(with: array2) {
print("Array 1 and array 2 share at least one common element")
} else {
print("Array 1 doesn't contains any elements from array 2")
}
This works much the same as the using the array's contains method – with the significant difference of the fact that the arraySet.contains method is now O(1). Therefore the entire method will now run at O(n) (where n is the greater length of the two sequences), with the possibility of exiting early.
With Swift 5, you can use one of the following paths in order to find if two arrays have common elements or not.
#1. Using Set isDisjoint(with:) method
Set has a method called isDisjoint(with:). isDisjoint(with:) has the following declaration:
func isDisjoint(with other: Set<Element>) -> Bool
Returns a Boolean value that indicates whether the set has no members in common with the given sequence.
In order to test if two arrays have no common elements, you can use the Playground sample code below that implements isDisjoint(with:):
let array1 = [1, 3, 6, 18, 24]
let array2 = [50, 100, 200]
let hasNoCommonElement = Set(array1).isDisjoint(with: array2)
print(hasNoCommonElement) // prints: true
#2. Using Set intersection(_:) method
Set has a method called intersection(_:). intersection(_:) has the following declaration:
func intersection<S>(_ other: S) -> Set<Element> where Element == S.Element, S : Sequence
Returns a new set with the elements that are common to both this set and the given sequence.
In order to test if two arrays have no common elements or one or more common elements, you can use the Playground sample code below that implements intersection(_:):
let array1 = [1, 3, 6, 18, 24]
let array2 = [2, 3, 18]
let intersection = Set(array1).intersection(array2)
print(intersection) // prints: [18, 3]
let hasCommonElement = !intersection.isEmpty
print(hasCommonElement) // prints: true
An alternative way would be using Sets:
let array1 = [1,2,3,4,5]
let array2 = [2,3]
let set1 = Set(array1)
let intersect = set1.intersect(array2)
if !intersect.isEmpty {
// do something with the intersecting elements
}
Swift 5
Just make an extension
public extension Sequence where Element: Equatable {
func contains(anyOf sequence: [Element]) -> Bool {
return self.filter { sequence.contains($0) }.count > 0
}
}
Use:
let someArray = ["one", "two", "three"]
let string = "onE, Cat, dog"
let intersects = string
.lowercased()
.replacingOccurrences(of: " ", with: "")
.components(separatedBy: ",")
.contains(anyOf: someArray)
print(intersects) // true
let a1 = [1, 2, 3]
let a2 = [2, 3, 4]
Option 1
a2.filter { a1.contains($0) }.count > 1
Option 2
a2.reduce(false, combine: { $0 || a1.contains($1) })
Hope this 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
}
}