Я разработал решение для этого, если кто-то заинтересован. Выполнение следующего эквивалентно и не приводит к исключению в вопросе:
var res = ctx
.Bs
.GroupBy(b => b.A)
.Where(g => g.All(b => names.Contains(b.SomeName)))
.Select(g => g.Key);
Я не знаю, является ли это лучшим способом, хотя!?
Я бы не назвал это красивым, но вот метод, использующий 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)
}
}
Было бы неплохо выразить формулировку Тайлера Клути как расширение на 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
}
}
Это дает нам общий способ разбиения массива на куски.
Я не думаю, что вы захотите использовать карту или уменьшить ее. Карта предназначена для применения функции к каждому отдельному элементу в массиве, а сокращение - для выравнивания массива. То, что вы хотите сделать, - это разрезать массив на подмассивы определенного размера. Этот фрагмент использует срезы.
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)
Я просто брошу свою шляпу на ринг здесь с другой реализацией, основанной на 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"]]
Вышеупомянутое очень клеверное, но у меня болит голова. Мне пришлось вернуться к менее осторожному подходу.
Для 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]]
С Swift 4.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"]]
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"]]
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"]]
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"]]
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"]]
[]
вместо [[]]
. Вариант 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)
}
Новое в 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]]
В 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 функциональность продвигающихся индексов была перенесена из самих индексов в коллекцию.