Technically, this is not guaranteed to preserve order, but it does.
Dictionary(grouping: numbers) { $0.isMultiple(of: 3) }
https://github.com/apple/swift/blob/master/stdlib/public/core/NativeDictionary.swift
Answer from user652038 on Stack OverflowTechnically, this is not guaranteed to preserve order, but it does.
Dictionary(grouping: numbers) { $0.isMultiple(of: 3) }
https://github.com/apple/swift/blob/master/stdlib/public/core/NativeDictionary.swift
Swift 4 Solution
partition(by:)
It reorders the origin array and returns start index of subarray satisfies the predicate.
In this example it returns 7.
0..<7 elemets aren't divisible by 3 and 7..n-1 elements are divisible by 3.
var numbers = [1,2,3,4,5,6,7,8,9,10]
let partition = numbers.partition(by: { $0 % 3 == 0 })
let divisibleBy3 = Array(numbers[..<partition]) //[3,6,9]
let theRest = Array(numbers[partition...]) //[1,2,4,5,7,8,10]
You can make an extension so it can return an array of two arrays, working with Ints, Strings, etc:
extension Array {
func split() -> [[Element]] {
let ct = self.count
let half = ct / 2
let leftSplit = self[0 ..< half]
let rightSplit = self[half ..< ct]
return [Array(leftSplit), Array(rightSplit)]
}
}
let deck = ["J", "Q", "K", "A"]
let nums = [0, 1, 2, 3, 4]
deck.split() // [["J", "Q"], ["K", "A"]]
nums.split() // [[0, 1], [2, 3, 4]]
But returning a named tuple is even better, because it enforces the fact that you expect exactly two arrays as a result:
extension Array {
func split() -> (left: [Element], right: [Element]) {
let ct = self.count
let half = ct / 2
let leftSplit = self[0 ..< half]
let rightSplit = self[half ..< ct]
return (left: Array(leftSplit), right: Array(rightSplit))
}
}
let deck = ["J", "Q", "K", "A"]
let splitDeck = deck.split()
print(splitDeck.left) // ["J", "Q"]
print(splitDeck.right) // ["K", "A"]
Note: credits goes to Andrei and Qbyte for giving the first correct answer, I'm just adding info.
You can use subscript range
let deck: [String] = ["J", "Q", "K", "A"]
// use ArraySlice only for transient computation
let leftSplit: ArraySlice<String> = deck[0 ..< deck.count / 2] // "J", "Q"
let rightSplit: ArraySlice<String> = deck[deck.count / 2 ..< deck.count] // "K", "A"
// make arrays from ArraySlice
let leftDeck: [String] = Array(leftSplit) // "J", "Q"
let rightDeck: [String] = Array(rightSplit) // "K", "A"
EDIT: above code is for Swift 2, maybe for Swift 3 is a more convenient way.
I offer you to use array with tuples (to hold keys). Compose this array and then you easily can remap it to format you need:
let array = ["23.88", "24", "30", "24.16#C", "25#C", "12#C", "24.44#O", "50#O" , "31", "40" , "44#C", "55#C"]
let noModeTag = "#NoMode"
func group(array: [String]) -> [(String, [Double])]
{
var result = [(String, [Double])]()
func addNextElement(number: Double?, _ mode: String?) {
guard let number = number, mode = mode else {fatalError("input format error")}
if result.last?.0 == mode {
var array = result.last!.1
array.append(number)
result[result.count - 1] = (mode, array)
} else {
result.append((mode, [number]))
}
}
for element in array {
if element.containsString("#") {
let separated = element.componentsSeparatedByString("#")
addNextElement(Double(separated.first ?? "_"), separated.last)
} else {
addNextElement(Double(element), noModeTag)
}
}
return result
}
print(group(array))
//[("#NoMode", [23.88, 24.0, 30.0]), ("C", [24.16, 25.0, 12.0]), ("O", [24.44, 50.0]), ("#NoMode", [31.0, 40.0]), ("C", [44.0, 55.0])]
You'll need to modify the groupBy extension to group into an array of 2-tuples rather than an dictionary, where the first tuple element corresponds to a non-unique "key", and the 2nd tuple element is an array of subsequent elements in the self array that can be categorized to the given key.
Modified SequenceType extension
extension SequenceType {
func groupBy<U : Comparable>(@noescape keyFunc: Generator.Element -> U) -> [(U,[Generator.Element])] {
var tupArr: [(U,[Generator.Element])] = []
for el in self {
let key = keyFunc(el)
if tupArr.last?.0 == key {
tupArr[tupArr.endIndex-1].1.append(el)
}
else {
tupArr.append((key,[el]))
}
}
return tupArr
}
}
Note also that is now suffices that the generic U in the extension conforms to Comparable, as we only use the U elements as "fake" keys in a tuple.
Call to extension
With this modification, we can call the groupBy(..) method as
let array = ["23.88", "24", "30", "24.16#C", "25#C", "12", "24.44", "50" , "31#O", "40#O" , "44#C", "55#C"]
/* assuming we know the last character always describe the mode,
given one is included (#) */
let groupedArray: [(String,[String])] = array.groupBy {
guard $0.characters.contains("#") else { return "No mode" }
return "mode = " + String($0.characters.last!)
}
print(groupedArray)
/* [("No mode", ["23.88", "24", "30"]),
("mode = C", ["24.16#C", "25#C"]),
("No mode", ["12", "24.44", "50"]),
("mode = O", ["31#O", "40#O"]),
("mode = C", ["44#C", "55#C"])] */
Removing original mode markings (#X) from grouped array
If you'd like to remove original mode markings (#X) in the resulting array, you can apply an additional map operation following the call to groupBy.
Removing markings with resulting values as String:
let groupedArrayClean = groupedArray.map { ($0.0, $0.1.map {
String($0.characters.prefixUpTo($0.characters.indexOf("#") ?? $0.characters.endIndex))
})
}
print(groupedArrayClean)
/* [("No mode", ["23.88", "24", "30"]),
("mode = C", ["24.16", "25"]),
("No mode", ["12", "24.44", "50"]),
("mode = O", ["31", "40"]),
("mode = C", ["44", "55"])] */
Or, with resulting values as Double:
let groupedArrayClean = groupedArray.map { ($0.0, $0.1.flatMap {
Double(
String(($0.characters.prefixUpTo($0.characters.indexOf("#") ?? $0.characters.endIndex))))
})
}
print(groupedArrayClean)
/* [("No mode", [23.879999999999999, 24.0, 30.0]),
("mode = C", [24.16, 25.0]),
("No mode", [12.0, 24.440000000000001, 50.0]),
("mode = O", [31.0, 40.0]),
("mode = C", [44.0, 55.0])] */
Alternatively: group and clean up mode markings in single chained call
Or, both groupBy followed by map at once, without an intermediate assignment:
let groupedArrayClean: [(String,[String])] = array.groupBy {
guard $0.characters.contains("#") else { return "No mode" }
return "mode = " + String($0.characters.last!)
}
.map { ($0.0, $0.1.map {
String($0.characters
.prefixUpTo($0.characters.indexOf("#") ?? $0.characters.endIndex))
})
}
(Analogously for the resulting Double values case.)
// 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] changed to use Set Initializer as suggested by Hamish
[EDIT2] Swift 4 added an initializer to Dictionary that will do this more efficiently:
let numberGroups = Array(Dictionary(grouping:numbers){$0}.values)
For a list of objects to be grouped by one of their properties:
let objectGroups = Array(Dictionary(grouping:objects){$0.property}.values)
If you could use CocoaPods/Carthage/Swift Package Manager/etc. you could 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 don't want to add external dependencies, you could always write an algorithm like:
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]].
You can replace both loops with a map() operation:
extension Array {
func splitInSubArrays(into size: Int) -> [[Element]] {
return (0..<size).map {
stride(from: $0, to: count, by: size).map { self[$0] }
}
}
}
The outer map() maps each offset to the corresponding array, and the inner map() maps the indices to the array elements.
Examples:
print([0, 1, 2, 3, 4, 5, 6].splitInSubArrays(into: 3))
// [[0, 3, 6], [1, 4], [2, 5]]
print([0, 1, 2].splitInSubArrays(into: 4))
// [[0], [1], [2], []]
Just for fun a generic implementation that would work with strings as well:
extension Collection {
func every(n: Int, start: Int = 0) -> UnfoldSequence<Element,Index> {
sequence(state: dropFirst(start).startIndex) { index in
guard index < endIndex else { return nil }
defer { index = self.index(index, offsetBy: n, limitedBy: endIndex) ?? endIndex }
return self[index]
}
}
}
extension RangeReplaceableCollection {
func splitIn(subSequences n: Int) -> [SubSequence] {
(0..<n).map { .init(every(n: n, start: $0)) }
}
}
[0, 1, 2, 3, 4, 5, 6].splitIn(subSequences: 3) // [[0, 3, 6], [1, 4], [2, 5]]
[0, 1, 2].splitIn(subSequences: 4) // [[0], [1], [2], []]
"0123456".splitIn(subSequences: 3) // ["036", "14", "25"]
Just call componentsSeparatedByString method on your fullName
import Foundation
var fullName: String = "First Last"
let fullNameArr = fullName.componentsSeparatedByString(" ")
var firstName: String = fullNameArr[0]
var lastName: String = fullNameArr[1]
Update for Swift 3+
import Foundation
let fullName = "First Last"
let fullNameArr = fullName.components(separatedBy: " ")
let name = fullNameArr[0]
let surname = fullNameArr[1]
The Swift way is to use the global split function, like so:
var fullName = "First Last"
var fullNameArr = split(fullName) {$0 == " "}
var firstName: String = fullNameArr[0]
var lastName: String? = fullNameArr.count > 1 ? fullNameArr[1] : nil
with Swift 2
In Swift 2 the use of split becomes a bit more complicated due to the introduction of the internal CharacterView type. This means that String no longer adopts the SequenceType or CollectionType protocols and you must instead use the .characters property to access a CharacterView type representation of a String instance. (Note: CharacterView does adopt SequenceType and CollectionType protocols).
let fullName = "First Last"
let fullNameArr = fullName.characters.split{$0 == " "}.map(String.init)
// or simply:
// let fullNameArr = fullName.characters.split{" "}.map(String.init)
fullNameArr[0] // First
fullNameArr[1] // Last
You can use name.characters.first to get a name's initial, and build up an array of arrays by comparing them:
let names = ["Aaron", "Alice", "Bob", "Charlie", "Chelsea", "David"]
var result: [[String]] = []
var prevInitial: Character? = nil
for name in names {
let initial = name.characters.first
if initial != prevInitial { // We're starting a new letter
result.append([])
prevInitial = initial
}
result[result.endIndex - 1].append(name)
}
print(result) // [["Aaron", "Alice"], ["Bob"], ["Charlie", "Chelsea"], ["David"]]
Dictionary(grouping: names) { $0.split(separator: " ").last?.first } .values
Sort if necessary!
In Swift 3/4 this would look like the following:
let numbers = ["1","2","3","4","5","6","7"]
let chunkSize = 2
let chunks = stride(from: 0, to: numbers.count, by: chunkSize).map {
Array(numbers[$0..<min($0 + chunkSize, numbers.count)])
}
// prints as [["1", "2"], ["3", "4"], ["5", "6"], ["7"]]
As an extension to Array:
extension Array {
func chunked(by chunkSize: Int) -> [[Element]] {
return stride(from: 0, to: self.count, by: chunkSize).map {
Array(self[$0..<Swift.min($0 + chunkSize, self.count)])
}
}
}
Or the slightly more verbose, yet more general:
let numbers = ["1","2","3","4","5","6","7"]
let chunkSize = 2
let chunks: [[String]] = stride(from: 0, to: numbers.count, by: chunkSize).map {
let end = numbers.endIndex
let chunkEnd = numbers.index($0, offsetBy: chunkSize, limitedBy: end) ?? end
return Array(numbers[$0..<chunkEnd])
}
This is more general because I am making fewer assumptions about the type of the index into the collection. In the previous implementation I assumed that they could be could be compared and added.
Note that in Swift 3 the functionality of advancing indices has been transferred from the indices themselves to the collection.
With Swift 5, according to your needs, you can choose one of the five following ways in order to solve your problem.
1. Using AnyIterator in a Collection extension method
AnyIterator is a good candidate to iterate over the indices of an object that conforms to Collection protocol in order to return subsequences of this object. In a Collection protocol extension, you can declare a chunked(by:) method with the following implementation:
extension Collection {
func chunked(by distance: Int) -> [[Element]] {
precondition(distance > 0, "distance must be greater than 0") // prevents infinite loop
var index = startIndex
let iterator: AnyIterator<Array<Element>> = AnyIterator({
let newIndex = self.index(index, offsetBy: distance, limitedBy: self.endIndex) ?? self.endIndex
defer { index = newIndex }
let range = index ..< newIndex
return index != self.endIndex ? Array(self[range]) : nil
})
return Array(iterator)
}
}
Usage:
let array = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
let newArray = array.chunked(by: 2)
print(newArray) // prints: [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9"]]
2. Using stride(from:to:by:) function in an Array extension method
Array indices are of type Int and conform to Strideable protocol. Therefore, you can use stride(from:to:by:) and advanced(by:) with them. In an Array extension, you can declare a chunked(by:) method with the following implementation:
extension Array {
func chunked(by distance: Int) -> [[Element]] {
let indicesSequence = stride(from: startIndex, to: endIndex, by: distance)
let array: [[Element]] = indicesSequence.map {
let newIndex = $0.advanced(by: distance) > endIndex ? endIndex : $0.advanced(by: distance)
//let newIndex = self.index($0, offsetBy: distance, limitedBy: self.endIndex) ?? self.endIndex // also works
return Array(self[$0 ..< newIndex])
}
return array
}
}
Usage:
let array = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
let newArray = array.chunked(by: 2)
print(newArray) // prints: [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9"]]
3. Using a recursive approach in an Array extension method
Based on Nate Cook recursive code, you can declare a chunked(by:) method in an Array extension with the following implementation:
extension Array {
func chunked(by distance: Int) -> [[Element]] {
precondition(distance > 0, "distance must be greater than 0") // prevents infinite loop
if self.count <= distance {
return [self]
} else {
let head = [Array(self[0 ..< distance])]
let tail = Array(self[distance ..< self.count])
return head + tail.chunked(by: distance)
}
}
}
Usage:
let array = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
let newArray = array.chunked(by: 2)
print(newArray) // prints: [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9"]]
4. Using a for loop and batches in a Collection extension method
Chris Eidhof and Florian Kugler show in Swift Talk #33 - Sequence & Iterator (Collections #2) video how to use a simple for loop to fill batches of sequence elements and append them on completion to an array. In a Sequence extension, you can declare a chunked(by:) method with the following implementation:
extension Collection {
func chunked(by distance: Int) -> [[Element]] {
var result: [[Element]] = []
var batch: [Element] = []
for element in self {
batch.append(element)
if batch.count == distance {
result.append(batch)
batch = []
}
}
if !batch.isEmpty {
result.append(batch)
}
return result
}
}
Usage:
let array = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
let newArray = array.chunked(by: 2)
print(newArray) // prints: [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9"]]
5. Using a custom struct that conforms to Sequence and IteratorProtocol protocols
If you don't want to create extensions of Sequence, Collection or Array, you can create a custom struct that conforms to Sequence and IteratorProtocol protocols. This struct should have the following implementation:
struct BatchSequence<T>: Sequence, IteratorProtocol {
private let array: [T]
private let distance: Int
private var index = 0
init(array: [T], distance: Int) {
precondition(distance > 0, "distance must be greater than 0") // prevents infinite loop
self.array = array
self.distance = distance
}
mutating func next() -> [T]? {
guard index < array.endIndex else { return nil }
let newIndex = index.advanced(by: distance) > array.endIndex ? array.endIndex : index.advanced(by: distance)
defer { index = newIndex }
return Array(array[index ..< newIndex])
}
}
Usage:
let array = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
let batchSequence = BatchSequence(array: array, distance: 2)
let newArray = Array(batchSequence)
print(newArray) // prints: [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9"]]