Splitting an array into sub-arrays in Swift

Problem

Given an array of values , how can I split it into sub-arrays from elements that are equal?

Example

Given this array

 let numbers = [1, 1, 1, 3, 3, 4] 

I want this conclusion

 [[1,1,1], [3, 3], [4]] 

What I am NOT looking for

A possible way to resolve this issue is to create some kind of index to indicate the occurrences of each element like this.

 let indexes = [1:3, 3:2, 4:1] 

And finally, use the index to rebuild the output array.

 let subsequences = indexes.sort { $0.0.0 < $0.1.0 }.reduce([Int]()) { (res, elm) -> [Int] in return res + [Int](count: elm.1, repeatedValue: elm.0) } 

However, with this solution, I lose my original values. Of course, in this case it is not a big problem (the value of Int is still and Int even if recreated), but I would like to apply this solution to more complex data structures, such as

 struct Starship: Equatable { let name: String let warpSpeed: Int } func ==(left:Starship, right:Starship) -> Bool { return left.warpSpeed == right.warpSpeed } 

Final considerations

The function I'm looking for will be something like the inverse of flatten() , infact

 let subsequences: [[Int]] = [[1,1,1], [3, 3], [4]] print(Array(subsequences.flatten())) // [1, 1, 1, 3, 3, 4] 

I hope I made it clear, let me know if you need more information.

+8
arrays swift
source share
3 answers
  // extract unique numbers using a set, then // map sub-arrays of the original arrays with a filter on each distinct number let numbers = [1, 1, 1, 3, 3, 4] let numberGroups = Set(numbers).map{ value in return numbers.filter{$0==value} } print(numberGroups) 

[EDIT] modified to use Set Initializer proposed by Hamish

[EDIT2] Swift 4 added an initializer to the dictionary, which will do this more efficiently:

  let numberGroups = Array(Dictionary(grouping:numbers){$0}.values) 
+10
source share

If you can use CocoaPods / Carthage / Swift Package Manager / etc. you can use packages like oisdk / SwiftSequence , which provides the group() method:

 numbers.lazy.group() // should return a sequence that generates [1, 1, 1], [3, 3], [4]. 

or UsrNameu1 / TraverSwift , which provides groupBy :

 groupBy(SequenceOf(numbers), ==) 

If you do not want to add external dependencies, you can always write an algorithm such as:

 func group<S: SequenceType where S.Generator.Element: Equatable>(seq: S) -> [[S.Generator.Element]] { var result: [[S.Generator.Element]] = [] var current: [S.Generator.Element] = [] for element in seq { if current.isEmpty || element == current[0] { current.append(element) } else { result.append(current) current = [element] } } result.append(current) return result } group(numbers) // returns [[1, 1, 1], [3, 3], [4]]. 
+3
source share

Suppose you have an unsorted array of elements. You will need to sort the initial array, then you will have something like this: [1, 1, 1, 3, 3, 4]

After that, you initialize two arrays: one to store the arrays, and the other to use as the current array.

Scroll through the source array and:

  • If the current value does not differ from the last, push it into the current array
  • otherwise insert the current array into the first, then clear the current array.

Hope this helps!

+1
source share

All Articles