В руководстве по языку не обнаружено следов понимания списка. Какой самый простой способ выполнить это в Swift? Я ищу что-то похожее на:
evens = [ x for x in range(10) if x % 2 == 0]
В руководстве по языку не обнаружено следов понимания списка. Какой самый простой способ выполнить это в Swift? Я ищу что-то похожее на:
evens = [ x for x in range(10) if x % 2 == 0]
По сравнению с Swift 2.x существует несколько коротких эквивалентов для понимания списка в стиле Python.
Самые простые адаптации формулы Python (которая читает что-то вроде "применить преобразование к последовательности, подверженной фильтру" ) включает в себя объединение методов map
и filter
, доступных для всех SequenceType
s, и начиная с a Range
:
// Python: [ x for x in range(10) if x % 2 == 0 ]
let evens = (0..<10).filter { $0 % 2 == 0 }
// Another example, since the first with 'x for x' doesn't
// use the full ability of a list comprehension:
// Python: [ x*x for x in range(10) if x % 2 == 0 ]
let evenSquared = (0..<10).filter({ $0 % 2 == 0 }).map({ $0 * $0 })
Обратите внимание, что a Range
является абстрактным - на самом деле он не создает весь список значений, о которых вы его просите, а просто конструкцию, которая лениво поставляет их по требованию. (В этом смысле это больше похоже на Python xrange
.) Однако вызов filter
возвращает Array
, поэтому вы теряете "ленивый" аспект. Если вы хотите, чтобы коллекция была ленивой на всем пути, просто скажите так:
// Python: [ x for x in range(10) if x % 2 == 0 ]
let evens = (0..<10).lazy.filter { $0 % 2 == 0 }
// Python: [ x*x for x in range(10) if x % 2 == 0 ]
let evenSquared = (0..<10).lazy.filter({ $0 % 2 == 0 }).map({ $0 * $0 })
В отличие от синтаксиса понимания списков в Python (и подобных конструкциях на некоторых других языках), эти операции в Swift соответствуют тому же синтаксису, что и другие операции. То есть, это тот же стиль синтаксиса для построения, фильтрации и работы с диапазоном чисел, поскольку он предназначен для фильтрации и работы с массивом объектов - вам не нужно использовать синтаксис функции/метода для одного вида работы и синтаксис синтаксиса списка для другого.
И вы можете передавать другие функции в вызовы filter
и map
, а цепочка - в других удобных преобразованиях, таких как sort
и reduce
:
// func isAwesome(person: Person) -> Bool
// let people: [Person]
let names = people.filter(isAwesome).sort(<).map({ $0.name })
let sum = (0..<10).reduce(0, combine: +)
В зависимости от того, что вы собираетесь, тем не менее, могут быть более сжатые способы сказать, что вы имеете в виду. Например, если вам нужен список четных целых чисел, вы можете использовать stride
:
let evenStride = 0.stride(to: 10, by: 2) // or stride(through:by:), to include 10
Как и в случае с диапазонами, это дает вам генератор, поэтому вы хотите сделать из него Array
или перебрать его, чтобы увидеть все значения:
let evensArray = Array(evenStride) // [0, 2, 4, 6, 8]
Изменить: Тяжело переработан для Swift 2.x. См. Историю изменений, если вы хотите Swift 1.x.
С Swift 3, в соответствии с вашими потребностями или вкусами, вы можете выбрать один из семи следующих кодов игровой площадки, которые эквивалентны пониманию списка Python.
stride(from:to:by:)
функцияlet sequence = stride(from: 0, to: 10, by: 2)
let evens = Array(sequence)
// let evens = sequence.map({ $0 }) // also works
print(evens) // prints [0, 2, 4, 6, 8]
CountableRange
filter(_:)
методlet range = 0 ..< 10
let evens = range.filter({ $0 % 2 == 0 })
print(evens) // prints [0, 2, 4, 6, 8]
CountableRange
flatMap(_:)
методlet range = 0 ..< 10
let evens = range.flatMap({ $0 % 2 == 0 ? $0 : nil })
print(evens) // prints [0, 2, 4, 6, 8]
sequence(first:next:)
функцияlet unfoldSequence = sequence(first: 0, next: {
$0 + 2 < 10 ? $0 + 2 : nil
})
let evens = Array(unfoldSequence)
// let evens = unfoldSequence.map({ $0 }) // also works
print(evens) // prints [0, 2, 4, 6, 8]
AnySequence
init(_:)
initializerlet anySequence = AnySequence<Int>({ () -> AnyIterator<Int> in
var value = 0
return AnyIterator<Int> {
defer { value += 2 }
return value < 10 ? value : nil
}
})
let evens = Array(anySequence)
// let evens = anySequence.map({ $0 }) // also works
print(evens) // prints [0, 2, 4, 6, 8]
var evens = [Int]()
for value in 0 ..< 10 where value % 2 == 0 {
evens.append(value)
}
print(evens) // prints [0, 2, 4, 6, 8]
var evens = [Int]()
for value in 0 ..< 10 {
if value % 2 == 0 {
evens.append(value)
}
}
print(evens) // prints [0, 2, 4, 6, 8]
Как правило, понимание в Python может быть записано в виде:
[f(x) for x in xs if g(x)]
То же, что и
map(f, filter(g, xs))
Поэтому в Swift вы можете записать его как
listComprehension<Y>(xs: [X], f: X -> Y, g: X -> Bool) = map(filter(xs, g), f)
Например:
map(filter(0..<10, { $0 % 2 == 0 }), { $0 })
С Swift 2 вы можете сделать что-то вроде этого:
var evens = [Int]()
for x in 1..<10 where x % 2 == 0 {
evens.append(x)
}
// or directly filtering Range due to default implementations in protocols (now a method)
let evens = (0..<10).filter{ $0 % 2 == 0 }
Признаюсь, я удивлен, что никто не упоминал план, поскольку я думаю, что это самая близкая вещь, которую Свифт должен перечислить (или установить или диктовать).
var evens = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].flatMap({num -> Int? in
if num % 2 == 0 {return num} else {return nil}
})
Flatmap принимает закрытие, и вы можете либо возвращать отдельные значения (в этом случае он будет возвращать массив со всеми значениями, отличными от нуля, и отбрасывать нильские), либо возвращать сегменты массива (в этом случае он будет связывать все ваши сегменты вместе и верните это.)
Плоская карта кажется в основном (всегда?) неспособной вывести возвращаемые значения. Конечно, в этом случае он не может, поэтому я указываю его как → Int? так что я могу вернуть nils и, таким образом, отбросить нечетные элементы.
Вы можете вставить плоские карты, если хотите. И я нахожу их гораздо более интуитивными (хотя, очевидно, также немного более ограниченными), чем комбинация карты и фильтра. Например, верхний ответ "квадрат эвенов", используя плоскую карту, становится,
var esquares = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].flatMap({num -> Int? in
if num % 2 == 0 {return num * num} else {return nil}
})
Синтаксис не совсем как однострочный не-совершенно-то-же-как-все-else, как python. Я не уверен, что мне это меньше (потому что для простых случаев в python это очень короткий и по-прежнему очень читаемый) или более (потому что сложные случаи могут выходить из-под контроля, а опытные программисты на питоне часто считают, что они отлично читаемый и поддерживаемый, когда новичок в одной компании может занять полчаса, чтобы разобраться в том, что он должен был делать, не говоря уже о том, что он на самом деле делает.)
Здесь - это версия flatMap, из которой вы возвращаете отдельные элементы или nil, и здесь - это версия, из которой вы возвращаете сегменты.
Возможно, стоит посмотреть и на array.map, и на array.forEach, потому что оба они также весьма удобны.
Один из способов:
var evens: Int[]()
for x in 0..<10 {
if x%2 == 0 {evens += x} // or evens.append(x)
}
Одним из аспектов понимания списка, который не упоминался в этом потоке, является тот факт, что вы можете применить его к декартовому продукту нескольких списков. Пример в Python:
[x + y for x in range(1,6) for y in range(3, 6) if x % 2 == 0]
... или Haskell:
[x+y | x <- [1..5], y <- [3..5], x `mod` 2 == 0]
В Swift эквивалентная логика с двумя списками
list0
.map { e0 in
list1.map { e1 in
(e0, e1)
}
}
.joined()
.filter(f)
.map(g)
И нам нужно увеличить уровень вложенности при увеличении количества списков на входе.
Недавно я сделал небольшую библиотеку для решения этой проблемы (если вы считаете это проблемой). Следуя моему первому примеру, с библиотекой мы получаем
Array(1...5, 3...5, where: { n, _ in n % 2 == 0}) { $0 + $1 }
Обоснование (и многое другое о понимании списка в целом) объясняется в сообщении .