Учет списка в Swift

В руководстве по языку не обнаружено следов понимания списка. Какой самый простой способ выполнить это в Swift? Я ищу что-то похожее на:

evens = [ x for x in range(10) if x % 2 == 0]

Ответ 1

По сравнению с 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.

Ответ 2

С Swift 3, в соответствии с вашими потребностями или вкусами, вы можете выбрать один из семи следующих кодов игровой площадки, которые эквивалентны пониманию списка Python.


# 1. Использование 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]

# 2. Используя CountableRange filter(_:) метод

let range = 0 ..< 10
let evens = range.filter({ $0 % 2 == 0 })
print(evens) // prints [0, 2, 4, 6, 8]

# 3. Использование CountableRange flatMap(_:) метод

let range = 0 ..< 10
let evens = range.flatMap({ $0 % 2 == 0 ? $0 : nil })
print(evens) // prints [0, 2, 4, 6, 8]

# 4. Используя 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]

# 5. Использование AnySequence init(_:) initializer

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

# 6. Использование для цикла с предложением where

var evens = [Int]()
for value in 0 ..< 10 where value % 2 == 0 {
    evens.append(value)
}
print(evens) // prints [0, 2, 4, 6, 8]

# 7. Использование для цикла с условием if

var evens = [Int]()
for value in 0 ..< 10 {
    if value % 2 == 0 {
        evens.append(value)
    }
}
print(evens) // prints [0, 2, 4, 6, 8]

Ответ 3

Как правило, понимание в 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 })

Ответ 4

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

Ответ 5

Признаюсь, я удивлен, что никто не упоминал план, поскольку я думаю, что это самая близкая вещь, которую Свифт должен перечислить (или установить или диктовать).

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, потому что оба они также весьма удобны.

Ответ 7

Одним из аспектов понимания списка, который не упоминался в этом потоке, является тот факт, что вы можете применить его к декартовому продукту нескольких списков. Пример в 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 }

Обоснование (и многое другое о понимании списка в целом) объясняется в сообщении .