Given an array of any kind and the wanted number of subarray, i need this output :
print([0, 1, 2, 3, 4, 5, 6].splitInSubArrays(into: 3))
// [[0, 3, 6], [1, 4], [2, 5]]
Output must contain the correct number of subarrays even if there is not "enough" elements to fill those :
print([0, 1, 2].splitInSubArrays(into: 4))
// [[0], [1], [2], []]
I have this working implementation for now but is there a better (more elegant) way of achieving this output :
extension Array {
func splitInSubArrays(into size: Int) -> [[Element]] {
var output: [[Element]] = []
(0..<size).forEach {
var subArray: [Element] = []
for elem in stride(from: $0, to: count, by: size) {
subArray.append(self[elem])
}
output.append(subArray)
}
return output
}
}
let str = "Hello! Swift String." ["Hello!", "Swift", "String."] In the above example, we use a space "" as a separator, but a separator doesn't need to be a single character. We can use a string as a separator with components (separatedBy:). You can also use a set of characters ( CharacterSet) as separators.
An array can hold multiple elements of a given type. We can use them to store numbers, strings, classes, but in general elements can be anything. With the Any type you can actually express this and you can put anything into this random access collection. There are quite many ways to create an array in Swift.
To split a string into an array by using a separator string, call the String.components (separatedBy:) function. Here the argument separatedBy can be either one of the following:
If not provided, there is no limit on the number of splits. omittingEmptySubsequences (optional) - specifies whether to omit empty string elements or to include them Note: If maxSplits is specified, the array will have a maximum of maxSplits + 1 items. var text = "Swift is awesome. Swift is fun." // split at period "."
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"]
The most intuitive way to do this is dead simple:
So it's really nothing more than this:
arrays[i%n].append(item i)
Example code per @LeoDabus comment below
extension RangeReplaceableCollection {
func moduloishtrancheization(n: Int) -> [SubSequence] {
var r: [SubSequence] = .init(repeating: .init(), count: n)
var i = 0
forEach {
r[i%n].append($0)
i += 1
}
return r
}
}
That's the whole thing.
For completeness, here's a reduce
-based solution that works on all Collection
types:
extension Collection {
func splitInSubArrays(_ size: Int) -> [[Element]] {
enumerated().reduce(into: [[Element]](repeating: [], count: size)) {
$0[$1.offset % size].append($1.element)
}
}
}
How the function works: it creates a an empty array of [Element]
entries, and appends each element of the original array to the corresponding sub-array. We're using here of reduce
just to carry the result array, to avoid explicitly creating a local variable (though internally reduce
is doing that for us).
Usage:
print([0, 1, 2, 3, 4, 5, 6].splitInSubArrays(3)) // [[0, 3, 6], [1, 4], [2, 5]]
print([0, 1, 2].splitInSubArrays(4)) // [[0], [1], [2], []]
print("ABCDEF".splitInSubArrays(3)) // ["A", "D"], ["B", "E"], ["C", "F"]]
Note that, as Leo Dabus pointed out, in the last example above the 2-D array is not a string-based one, it's a 2-D character array [[Character]]
. To generate a array of substrings instead, RangeReplaceableCollection
can be extended, and the result type can be changed to [SubSequence]
.
'Twould be good to allow it to be used on all sequences.
stride(from: 0, through: 6, by: 1).splitInSubArrays(into: 3)
(Put this into a public extension too, if it's useful across many apps, like the one below is.)
extension Sequence {
func splitInSubArrays(into size: Int) -> [[Element]] {
enumerated()
.grouped { $0.offset % size }
.map { $0.map(\.element) }
}
}
/// Group the elements by a transformation into an `Equatable`.
/// - Note: Similar to `Dictionary(grouping values:)`,
/// but preserves "key" ordering, and doesn't require hashability.
func grouped<Equatable: Swift.Equatable>(
by equatable: (Element) throws -> Equatable
) rethrows -> [[Element]] {
try reduce(into: [(equatable: Equatable, elements: [Element])]()) {
let equatable = try equatable($1)
if let index = ( $0.firstIndex { $0.equatable == equatable } ) {
$0[index].elements.append($1)
} else {
$0.append((equatable, [$1]))
}
}.map(\.elements)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With