Combine arrays when they have similar element - ios

I have 5 arrays inside an array,
let arrays = [[1,2,3], [1,2,3,4], [4,5,6,7], [21, 34, 89], [555, 34]]
Now, I want to group them if they have atleast 1 similar element.
Expected output.
[[1,2,3,4,5,6,7], [21, 34, 89, 555]]
Since array 1, 2 and 3 has similar element, they will combine. And array 4 and 5 has similar element so they will be combined as well.
var finalSimilarArray = [[String]]()
let similararray = [[1,2,3,4,5,6,7], [21, 34, 89, 555]]
similarArray.forEach { array in
similarArray.enumerated().forEach { index, array2 in
if !Set(array2).intersection(Set(array)).isEmpty {
finalSimilarArray.append(contentsOf: [array] + [array2])
similarArray.remove(at: index)
}
}
}
I tried to looping and conditions with no luck, not even close. to the real deal.
Thank you, I hope I explained it clearly. :D

My 2 cent: Start with a function which merges a single array into a list of (mutually disjoint) arrays:
func mergeOne<T>(array: [T], into merged: [[T]]) -> [[T]]
where T: Hashable & Comparable
{
var set = Set(array)
var result = merged.compactMap { m -> [T]? in
if set.isDisjoint(with: m) {
return m
} else {
set.formUnion(m)
return nil
}
}
result.append(set.sorted()) // Or: result.append(Array(set))
return result
}
The array is converted to a set so that overlapping tests with another array and joining the elements can be done efficiently. This requires the elements to conform to the Hashable protocol. The Comparable conformance is only needed to sort the merged arrays – if that is not needed then this constraint can be removed.
The above function compares the given array with each element of the list, and joins them if there is an overlap. The important point is to use the new enlarged array (actually: set) for the subsequent comparisons. So arrays with no overlap with the new array are kept in the list, and the others are joined to a new array which is then appended to the list.
With that utility function we can merge a given list of arrays easily:
func merge<T>(arrays: [[T]]) -> [[T]]
where T: Hashable & Comparable
{
return arrays.reduce(into: [], { $0 = mergeOne(array: $1, into: $0) })
}
Starting with an empty list, the array elements are merged repeatedly.
Examples:
print(merge(arrays: [[1, 2, 3], [1, 2, 3, 4], [4, 5, 6, 7], [21, 34, 89], [555, 34]]))
// [[1, 2, 3, 4, 5, 6, 7], [21, 34, 89, 555]]
print(merge(arrays: [[1,2], [3, 4], [5, 6], [3, 5], [6, 2]]))
// [[1, 2, 3, 4, 5, 6]]

Compare each array element to every other array element to see if there is any overlap. If there is, then combine the arrays into a partial result. Next compare the partial result to each element in the results. If there is any overlap, then combine then. If not, then append the partial result to the array of results.
extension Collection where Element: Hashable {
func unique() -> [Element] {
return Array(Set(self))
}
}
extension Collection where Element: Equatable {
func containsAny(of other: Self) -> Bool {
for element in other {
if contains(element) {
return true
}
}
return false
}
}
func merge(arrays: [[Int]]) -> [[Int]] {
return arrays.reduce(into: Array<[Int]>(), { results, slice1 in
var partial = slice1
for slice2 in arrays {
if slice2.containsAny(of: partial) {
partial.append(contentsOf: slice2)
}
}
if let index = results.firstIndex(where: { $0.containsAny(of: partial) }) {
results[index] = (results[index] + partial).unique().sorted()
} else {
results.append(partial.unique().sorted())
}
})
}
print(merge(arrays: [[1,2,3], [1,2,3,4], [4,5,6,7], [21, 34, 89], [555, 34]]))
print(merge(arrays: [[1,2], [3, 4], [1, 3]]))
Results are:
[[1, 2, 3, 4, 5, 6, 7], [21, 34, 89, 555]]
[[1, 2, 3, 4]]

You can use a Set which guarantees unique values, when using .union which merges the values of two sets into one.
let givenArray: [Set<Int>] = [[1,2,3], [1,2,3,4], [4,5,6,7], [21, 34, 89], [555, 34]]
let expectedArray: [Set<Int>] = [[1,2,3,4,5,6,7], [21, 34, 89, 555]]
let groupedArray: [Set<Int>] = givenArray.reduce([]) { (result, numbers) -> [Set<Int>] in
var mutableResult = result
if let indexOfNumbersToMergeWith = mutableResult.firstIndex(where: { (set) -> Bool in
!set.isDisjoint(with: numbers) // Returns: `true` if the set has no elements in common with `other`
}) {
mutableResult[indexOfNumbersToMergeWith] = mutableResult[indexOfNumbersToMergeWith].union(numbers)
} else {
mutableResult.append(numbers)
}
return mutableResult
}
print(groupedArray == expectedArray) // prints: true
EDIT:
I've updated the answer to support the following given array [[1,2], [3,4], [1,3]]
let givenArray: [Set<Int>] = [[1,2], [3,4], [1,3]]
let expectedArray: [Set<Int>] = [[1,2,3,4]]
let groupedArray: [Set<Int>] = givenArray.reduce([]) { (result, numbers) -> [Set<Int>] in
var mutableResult = result
let indexesOfNumbersToMergeWith: [Int] = mutableResult.enumerated().reduce([], { (result, arguments) -> [Int] in
var mutableResult = result
let (index, numbersToCompare) = arguments
// Returns: `true` if the set has no elements in common with `other`
if !numbersToCompare.isDisjoint(with: numbers) {
mutableResult.append(index)
}
return mutableResult
})
if !indexesOfNumbersToMergeWith.isEmpty {
// Note that I've sorted the indexes in descending order. This is because everytime you remove an element, the indexes of the elements beyond is reduce by one.
let numbersToMergeWith = indexesOfNumbersToMergeWith.sorted(by: { $1 < $0 }).map { (index) -> Set<Int> in
return mutableResult.remove(at: index) // removes and returns number set
}
mutableResult.append(numbersToMergeWith.reduce(into: numbers, { $0 = $0.union($1) }))
} else {
mutableResult.append(numbers)
}
return mutableResult
}
print(groupedArray == expectedArray) // prints: true

Related

Improving on the efficiency of my filtering of arrays with potentially hundreds of thousands of elements

I have an array of numbers. I want to find those in array1 that aren't also in array2, like this:
var array1 = [1, 2, 3, 4]
var array2 = [2, 4, 5, 6]
var result = [1, 3]
I've solved the problem by looping through all the numbers in the array2 and adding them to a dictionary. Then I loop through array1 and add those that aren't in the dictionary to the result array:
var result: [Int] = []
var numbersDict: [Int : Bool] = [:]
for element in array2 {
numbersDict[element] = true
}
for element in array1 {
if numbersDict[element] == nil {
result.append(element)
}
}
I also want to find those in array2 that aren't in array1
var array1 = [1, 2, 3, 4]
var array2 = [2, 4, 5, 6]
var result = [5, 6]
I've solved this like this:
var result: [Int] = []
var numbersDict: [Int : Bool] = [:]
for element in array1 {
numbersDict[element] = true
}
for element in array2 {
if numbersDict[element] == nil {
result.append(element)
}
}
How can I do this in the most efficient way? Assuming that these arrays could potentially be tens if not hundreds of thousands of numbers long. Should I be using sorting?
Just use Set.
Example which gets elements in array1 but not in array2:
let array1: Set = [1, 2, 3, 4]
let array2: Set = [2, 4, 5, 6]
let result = array1.subtracting(array2)
print(result)
// Prints: [1, 3] <- Order may vary since it is a set
Just switch the two sets around to get the opposite result, of in array2 but not in array1.
There are lots of Set operations, another one is intersection(_:):
let result = array1.intersection(array2)
print(result)
// Prints: [2, 4] <- Again, no order

How to get the items of an array based on the indexes stored in another array

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"]

how to change the subscript in an array of arrays

I want to change subscript coordinates in an Array of Arrays [[Int]] in order to be able to address each value in a way similar to a spreadsheet, so assigning values, formulas across cells, etc.
Currently to address each value I need to subscript like table[2][1] = 12 or similar. I would like to subscript like a spreadsheet table[a][2] = 12, so that I can adapt long and complicated formulas in bigger speadsheets to similar tables using the same system used in spreadsheets.
The question is: how to change the subscript system in an efficient way? I send a simplified example to ilustrate the point
class ViewController: UIViewController {
var table = [[0, 1, 2, 3],
[1, 32, 44, 25],
[2, 12, 66, 43],
[3, 3, 4, 5]]
override func viewDidLoad() {
super.viewDidLoad()
print(table[2][1]) // 12
table[1][1] = 100
table[3][3] = table[1][1] * table[3][1] * 10
print(table[3][3])
printArray(table: table, j: 3)
}
// MARK: - Function
func printArray(table: [[Int]], j:Int) {
for i in 0...j {
print(table[i])
}
}
}
The closest solution I found is to use enums, like here:
enum Column:Int {
case a
case b
case c
// and so on
}
extension Array {
subscript(col:Column) -> Element {
get {
return self[col.rawValue]
}
set(newValue) {
self[col.rawValue] = newValue
}
}
}
var table = [[0, 1, 2, 3],
[1, 32, 44, 25],
[2, 12, 66, 43],
[3, 3, 4, 5]]
table[.a][2] = 12
let val = table[.a][2]
print (val)
Nevertheless, there are two little drawbacks:
you first have hard-code all the "column" letters you want to access to the Column enum.
then, something like table[5][.c] and table[.a][.b] will also be allowed - so you could access the rows via letter, not only the columns

Get Subset of array based on the occurrence of elements

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].

Swift - Determine if Array1 contains at least one object from Array2

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
}
}

Resources