En commençant par un grand [String] et une taille de sous-tableau donnée, quelle est la meilleure façon de diviser ce tableau en tableaux plus petits? (Le dernier tableau sera plus petit que la taille de sous-tableau donnée).
Exemple concret:
Fractionner ["1", "2", "3", "4", "5", "6", "7"] avec la taille de fractionnement maximale 2
Le code produirait [["1", "2"], ["3", "4"], ["5", "6"], ["7"]
Évidemment, je pourrais le faire un peu plus manuellement, mais je sens que quelque chose comme map () ou reduction () peut faire ce que je veux vraiment.
Je n'appellerais pas cela beau, mais voici une méthode utilisant map
:
let numbers = ["1","2","3","4","5","6","7"]
let splitSize = 2
let chunks = numbers.startIndex.stride(to: numbers.count, by: splitSize).map {
numbers[$0 ..< $0.advancedBy(splitSize, limit: numbers.endIndex)]
}
La méthode stride(to:by:)
vous donne les indices pour le premier élément de chaque bloc. Vous pouvez ainsi les mapper sur une tranche du tableau source à l'aide de advancedBy(distance:limit:)
.
Une approche plus "fonctionnelle" serait simplement de récidiver sur le tableau, comme ceci:
func chunkArray<T>(s: [T], splitSize: Int) -> [[T]] {
if countElements(s) <= splitSize {
return [s]
} else {
return [Array<T>(s[0..<splitSize])] + chunkArray(Array<T>(s[splitSize..<s.count]), splitSize)
}
}
Dans Swift 3/4, ceci ressemblerait à ceci:
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"]]
En tant qu'extension de 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)])
}
}
}
Ou le légèrement plus verbeux, mais plus général:
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])
}
Ceci est plus général car je fais moins de suppositions sur le type de l'index dans la collection. Dans l’implémentation précédente, j’imaginais qu’ils pouvaient être comparés et ajoutés.
Notez que dans Swift 3, la fonctionnalité de progression des index a été transférée des index eux-mêmes à la collection.
Avec Swift 4.2 et Swift 5, selon vos besoins, vous pouvez choisir l’un des cinq moyens suivants pour résoudre votre problème.
AnyIterator
dans une méthode d'extension Collection
AnyIterator
est un bon candidat pour parcourir les index d'un objet conforme au protocole Collection
afin de renvoyer des sous-séquences de cet objet. Dans une extension de protocole Collection
, vous pouvez déclarer une méthode chunked(by:)
avec l'implémentation suivante:
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"]]
stride(from:to:by:)
dans une méthode d'extension Array
Les indices Array
sont de type Int
et sont conformes au protocole Strideable
. Par conséquent, vous pouvez utiliser stride(from:to:by:)
et advanced(by:)
avec eux. Dans une extension Array
, vous pouvez déclarer une méthode chunked(by:)
avec l'implémentation suivante:
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"]]
Array
Sur la base de Nate Cook code récursif , vous pouvez déclarer une méthode chunked(by:)
dans une extension Array
avec l'implémentation suivante:
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"]]
Collection
Chris Eidhof et Florian Kugler montrent dans Swift Talk n ° 33 - Séquence et itérateur (Collections n ° 2) video comment utiliser une simple boucle for pour remplir des lots d'éléments de séquence et les ajouter ensuite à un tableau. Dans une extension Sequence
, vous pouvez déclarer une méthode chunked(by:)
avec l'implémentation suivante:
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"]]
struct
personnalisée conforme aux protocoles Sequence
et IteratorProtocol
Si vous ne souhaitez pas créer d'extensions de Sequence
, Collection
ou Array
, vous pouvez créer une struct
personnalisée conforme aux protocoles Sequence
et IteratorProtocol
. Cette struct
devrait avoir l'implémentation suivante:
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"]]
J'aime la réponse de Nate Cook, on dirait que Swift a évolué depuis sa rédaction, voici mon interprétation de ceci comme une extension de Array:
extension Array {
func chunk(chunkSize : Int) -> Array<Array<Element>> {
return 0.stride(to: self.count, by: chunkSize)
.map { Array(self[$0..<$0.advancedBy(chunkSize, limit: self.count)]) }
}
}
Remarque, il retourne [] pour les nombres négatifs et entraînera une erreur fatale comme écrit ci-dessus. Vous devrez mettre un garde si vous voulez empêcher cela.
func testChunkByTwo() {
let input = [1,2,3,4,5,6,7]
let output = input.chunk(2)
let expectedOutput = [[1,2], [3,4], [5,6], [7]]
XCTAssertEqual(expectedOutput, output)
}
func testByOne() {
let input = [1,2,3,4,5,6,7]
let output = input.chunk(1)
let expectedOutput = [[1],[2],[3],[4],[5],[6],[7]]
XCTAssertEqual(expectedOutput, output)
}
func testNegative() {
let input = [1,2,3,4,5,6,7]
let output = input.chunk(-2)
let expectedOutput = []
XCTAssertEqual(expectedOutput, output)
}
Je ne pense pas que vous voudrez utiliser la carte ou réduire. La carte sert à appliquer une fonction à chaque élément individuel d'un tableau, tandis que réduire sert à aplatir un tableau. Ce que vous voulez faire, c'est diviser le tableau en sous-tableaux d'une certaine taille. Cet extrait utilise des tranches.
var arr = ["1","2","3","4","5","6","7"]
var splitSize = 2
var newArr = [[String]]()
var i = 0
while i < arr.count {
var slice: Slice<String>!
if i + splitSize >= arr.count {
slice = arr[i..<arr.count]
}
else {
slice = arr[i..<i+splitSize]
}
newArr.append(Array(slice))
i += slice.count
}
println(newArr)
Serait agréable à exprimer la formulation de Tyler Cloutier en tant qu'extension sur Array:
extension Array {
func chunked(by chunkSize:Int) -> [[Element]] {
let groups = stride(from: 0, to: self.count, by: chunkSize).map {
Array(self[$0..<[$0 + chunkSize, self.count].min()!])
}
return groups
}
}
Cela nous donne un moyen général de partitionner un tableau en morceaux.
Nouveau dans Swift 4, vous pouvez le faire efficacement avec reduce(into:)
. Voici une extension sur la séquence:
extension Sequence {
func eachSlice(_ clump:Int) -> [[Self.Element]] {
return self.reduce(into:[]) { memo, cur in
if memo.count == 0 {
return memo.append([cur])
}
if memo.last!.count < clump {
memo.append(memo.removeLast() + [cur])
} else {
memo.append([cur])
}
}
}
}
Usage:
let result = [1,2,3,4,5,6,7,8,9].eachSlice(2)
// [[1, 2], [3, 4], [5, 6], [7, 8], [9]]
Je vais juste lancer mon chapeau ici avec une autre implémentation basée sur AnyGenerator.
extension Array {
func chunks(_ size: Int) -> AnyIterator<[Element]> {
if size == 0 {
return AnyIterator {
return nil
}
}
let indices = stride(from: startIndex, to: count, by: size)
var generator = indices.makeIterator()
return AnyIterator {
guard let i = generator.next() else {
return nil
}
var j = self.index(i, offsetBy: size)
repeat {
j = self.index(before: j)
} while j >= self.endIndex
return self[i...j].lazy.map { $0 }
}
}
}
Je préfère cette méthode car elle s’appuie exclusivement sur des générateurs pouvant avoir un impact positif non négligeable sur la mémoire lorsqu’il s’agit de tableaux de grande taille.
Pour votre exemple spécifique, voici comment cela fonctionnerait:
let chunks = Array(["1","2","3","4","5","6","7"].chunks(2))
Résultat:
[["1", "2"], ["3", "4"], ["5", "6"], ["7"]]
Ce qui précède est très coupé, mais ma tête me fait mal. J'ai dû revenir à une approche moins radicale.
Pour Swift 2.0
var chunks = [[Int]]()
var temp = [Int]()
var splitSize = 3
var x = [1,2,3,4,5,6,7]
for (i, element) in x.enumerate() {
if temp.count < splitSize {
temp.append(element)
}
if temp.count == splitSize {
chunks.append(temp)
temp.removeAll()
}
}
if !temp.isEmpty {
chunks.append(temp)
}
Playground Result [[1, 2, 3], [4, 5, 6], [7]]
Savez-vous qu’une solution avec [a ... b] le style Swift fonctionne 10 fois plus lentement que la normale?
for y in 0..<rows {
var row = [Double]()
for x in 0..<cols {
row.append(stream[y * cols + x])
}
mat.append(row)
}
Essayez et vous verrez, voici mon code brut pour le test:
let count = 1000000
let cols = 1000
let rows = count / cols
var stream = [Double].init(repeating: 0.5, count: count)
// Regular
var mat = [[Double]]()
let t1 = Date()
for y in 0..<rows {
var row = [Double]()
for x in 0..<cols {
row.append(stream[y * cols + x])
}
mat.append(row)
}
print("regular: \(Date().timeIntervalSince(t1))")
//Swift
let t2 = Date()
var mat2: [[Double]] = stride(from: 0, to: stream.count, by: cols).map {
let end = stream.endIndex
let chunkEnd = stream.index($0, offsetBy: cols, limitedBy: end) ?? end
return Array(stream[$0..<chunkEnd])
}
print("Swift: \(Date().timeIntervalSince(t2))")
et dehors:
normal: 0.0449600219726562
Swift: 0.49255496263504
Dans Swift 4 ou version ultérieure, vous pouvez également étendre Collection
et renvoyer une collection de SubSequence
afin de pouvoir l'utiliser également avec une String
. De cette façon, il retournera une collection de sous-chaînes au lieu d'une collection de plusieurs personnages:
Xcode 10.1 • Swift 4.2.1 ou version ultérieure
extension Collection {
func subSequences(limitedTo maxLength: Int) -> [SubSequence] {
precondition(maxLength > 0, "groups must be greater than zero")
var start = startIndex
return stride(from: 0, to: count, by: maxLength).map { _ in
let end = index(start, offsetBy: maxLength, limitedBy: endIndex) ?? endIndex
defer { start = end }
return self[start..<end]
}
}
}
Usage
let array = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
let slices = array.subSequences(limitedTo: 2) // [ArraySlice(["1", "2"]), ArraySlice(["3", "4"]), ArraySlice(["5", "6"]), ArraySlice(["7", "8"]), ArraySlice(["9"])]
for slice in slices {
print(slice) // prints: [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9"]]
}
// To convert from ArraySlice<Element> to Array<element>
let arrays = slices.map(Array.init) // [["1", "2"], ["3", "4"], ["5", "6"], ["7", "8"], ["9"]]
extension Collection {
var singles: [SubSequence] { return subSequences(limitedTo: 1) }
var pairs: [SubSequence] { return subSequences(limitedTo: 2) }
var triples: [SubSequence] { return subSequences(limitedTo: 3) }
var quads: [SubSequence] { return subSequences(limitedTo: 4) }
}
Tableau ou tableau de caractères
let chars = ["a","b","c","d","e","f","g","h","i"]
chars.singles // [["a"], ["b"], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["i"]]
chars.pairs // [["a", "b"], ["c", "d"], ["e", "f"], ["g", "h"], ["i"]]
chars.triples // [["a", "b", "c"], ["d", "e", "f"], ["g", "h", "i"]]
chars.quads // [["a", "b", "c", "d"], ["e", "f", "g", "h"], ["i"]]
chars.dropFirst(2).quads // [["c", "d", "e", "f"], ["g", "h", "i"]]
Éléments StringProtocol (String et SubString)
let str = "abcdefghi"
str.singles // ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
str.pairs // ["ab", "cd", "ef", "gh", "i"]
str.triples // ["abc", "def", "ghi"]
str.quads // ["abcd", "efgh", "i"]
str.dropFirst(2).quads // ["cdef", "ghi"]