Как разбить массив на несколько подмассивов [duplicate]

Я разработал решение для этого, если кто-то заинтересован. Выполнение следующего эквивалентно и не приводит к исключению в вопросе:

var res = ctx
    .Bs
    .GroupBy(b => b.A)
    .Where(g => g.All(b => names.Contains(b.SomeName)))
    .Select(g => g.Key);

Я не знаю, является ли это лучшим способом, хотя!?

36
задан Honey 16 August 2016 в 18:44
поделиться

10 ответов

Я бы не назвал это красивым, но вот метод, использующий 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)]
}

Метод stride(to:by:) дает вам индексы для первого элемента каждого фрагмента, так что вы можете сопоставьте эти индексы с срезом исходного массива, используя advancedBy(distance:limit:).

Более «функциональный» подход просто состоял бы в том, чтобы перезаписать массив, например:

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)
    }
}
26
ответ дан CVertex 16 August 2018 в 04:16
поделиться
  • 1
    Swift 2.0 let chunks = stride (from: 0, to: numbers.count, by: splitSize) .map ({numbers [$ 0 .. & lt; advance ($ 0, splitSize, numbers.count)]}) – DogCoffee 19 August 2015 в 08:29
  • 2
    который теперь недействителен с новой версией XC 7 Beta 6 – DogCoffee 25 August 2015 в 05:35

Было бы неплохо выразить формулировку Тайлера Клути как расширение на 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
    }
}

Это дает нам общий способ разбиения массива на куски.

3
ответ дан Community 16 August 2018 в 04:16
поделиться

Я не думаю, что вы захотите использовать карту или уменьшить ее. Карта предназначена для применения функции к каждому отдельному элементу в массиве, а сокращение - для выравнивания массива. То, что вы хотите сделать, - это разрезать массив на подмассивы определенного размера. Этот фрагмент использует срезы.

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)
4
ответ дан connor 16 August 2018 в 04:16
поделиться
  • 1
    Это решение работает в быстром 2.2 до 3.0, что является плюсом! И id утверждают, что это более читаемо, пока мы все не узнаем последний вкус «нового-говорить». Я имею в виду стремительный. – eonist 8 January 2017 в 23:57

Я просто брошу свою шляпу на ринг здесь с другой реализацией, основанной на 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 }
        }
    }
}

. Я предпочитаю этот метод, поскольку он полагается исключительно на генераторы, которые могут иметь безразличный, положительное влияние памяти при работе с большими массивами.

Для вашего конкретного примера, вот как это будет работать:

let chunks = Array(["1","2","3","4","5","6","7"].chunks(2))

Результат:

[["1", "2"], ["3", "4"], ["5", "6"], ["7"]]
2
ответ дан Dan Loewenherz 16 August 2018 в 04:16
поделиться

Вышеупомянутое очень клеверное, но у меня болит голова. Мне пришлось вернуться к менее осторожному подходу.

Для 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]]

2
ответ дан DogCoffee 16 August 2018 в 04:16
поделиться

С Swift 4.1, в соответствии с вашими потребностями, вы можете выбрать один из пяти способов, чтобы решить вашу проблему.


1. Использование AnyIterator в методе расширения Collection

AnyIterator является хорошим кандидатом для итерации по индексам объекта, который соответствует протоколу Collection, чтобы возвратные подпоследовательности этого объекта. В расширении протокола Collection вы можете объявить метод chunked(by:) со следующей реализацией:

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

}

Использование:

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. Использование функции stride(from:to:by:) в методах расширения Array

Array имеет тип Int и соответствует протоколу Strideable. Поэтому вы можете использовать с ними stride(from:to:by:) и advanced(by:) . В расширении Array вы можете объявить метод chunked(by:) со следующей реализацией:

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
    }

}

Использование:

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. Использование рекурсивного подхода в методе расширения Array

На основе рекурсивного кода Nate Cook вы можете объявить метод chunked(by:) в расширении Array со следующей реализацией :

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

}

Использование:

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. Использование цикла for и партий в способе расширения Sequence

. Крис Эйдхоф и Флориан Куглер показывают в Swift Talk # 33 - Sequence & amp; Iterator (Collections # 2) как использовать простой цикл для заполнения партий элементов последовательности и добавления их к завершению массиву. В расширении Sequence вы можете объявить метод chunked(by:) со следующей реализацией:

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
    }

}

Использование:

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. Использование пользовательской функции struct, которая соответствует протоколам Sequence и IteratorProtocol

Если вы не хотите создавать расширения Sequence, Collection или Array, вы можете создать custom struct, который соответствует протоколам Sequence и IteratorProtocol. Этот struct должен иметь следующую реализацию:

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

}

Использование:

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"]]
29
ответ дан Imanou Petit 16 August 2018 в 04:16
поделиться
  • 1
    Привет, у вас есть версия Swift 3 этого метода расширения? – Isuru 8 December 2016 в 08:33
  • 2
    Этот ответ прекрасен! – fpg1503 29 January 2017 в 00:45
  • 3
    Отличный ответ, спасибо! Обратите внимание, что опция 4 имеет то, что я считаю нечетным, если массив, который был помечен, пуст. Он возвращает [] вместо [[]]. Вариант 3 ведет себя так, как я ожидал. – Mike Taverne 13 March 2018 в 16:50

Мне нравится ответ Нейта Кука, похоже, что Swift двинулся дальше, так как он был написан, вот мой пример: расширение / чисел и приведет к фатальной ошибке, как указано выше. Вам нужно будет включить охрану, если вы хотите это предотвратить.

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)
}
7
ответ дан Julian 16 August 2018 в 04:16
поделиться

Новое в Swift 4, вы можете сделать это эффективно с помощью reduce(into:). Вот расширение на Sequence:

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

Использование:

let result = [1,2,3,4,5,6,7,8,9].eachSlice(2)
// [[1, 2], [3, 4], [5, 6], [7, 8], [9]]
2
ответ дан matt 16 August 2018 в 04:16
поделиться

В Swift 3/4 это выглядело бы следующим образом:

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

В качестве расширения для массива:

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

Или несколько более подробный, но более общий :

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

Это более общее, потому что я делаю меньше предположений о типе индекса в коллекции. В предыдущей реализации я предположил, что их можно было бы сравнить и добавить.

Обратите внимание, что в Swift 3 функциональность продвигающихся индексов была перенесена из самих индексов в коллекцию.

43
ответ дан Tyler Cloutier 16 August 2018 в 04:16
поделиться
0
ответ дан Андрей Первушин 16 August 2018 в 04:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: