List Comprehension in Swift

List comprehension in Swift

As of Swift 2.x, there are a few short equivalents to your Python-style list comprehension.

The most straightforward adaptations of Python's formula (which reads something like "apply a transform to a sequence subject to a filter") involve chaining the map and filter methods available to all SequenceTypes, and starting from 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 })

Note that a Range is abstract — it doesn't actually create the whole list of values you ask it for, just a construct that lazily supplies them on demand. (In this sense it's more like Python's xrange.) However, the filter call returns an Array, so you lose the "lazy" aspect there. If you want to keep the collection lazy all the way through, just say so:

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

Unlike the list comprehension syntax in Python (and similar constructs in some other languages), these operations in Swift follow the same syntax as other operations. That is, it's the same style of syntax to construct, filter, and operate on a range of numbers as it is to filter and operate on an array of objects — you don't have to use function/method syntax for one kind of work and list comprehension syntax for another.

And you can pass other functions in to the filter and map calls, and chain in other handy transforms like sort and 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: +)

Depending on what you're going for, though, there may be more concise ways to say what you mean. For example, if you specifically want a list of even integers, you can use stride:

let evenStride = 0.stride(to: 10, by: 2) // or stride(through:by:), to include 10

Like with ranges, this gets you a generator, so you'll want to make an Array from it or iterate through it to see all the values:

let evensArray = Array(evenStride) // [0, 2, 4, 6, 8]

Edit: Heavily revised for Swift 2.x. See the edit history if you want Swift 1.x.

Can the concept of list comprehension be implemented in Swift?

As a general rule, list comprehensions are a syntactic feature that a language either has or doesn't have. Swift doesn't have them. For some concise ways of achieving similar results, see this question.

Swift dictionary comprehension

The map method simply transforms each element of an array into a new element. The result is, however, still an array. To transform the array into a dictionary you can use the reduce method.

let x = ["a","b","c"]
let y = x.reduce([String: String]()) { (var dict, arrayElem) in
dict[arrayElem] = "this is the value for \(arrayElem)"
return dict
}

This will generate the dictionary

["a": "this is the value for a",
"b": "this is the value for b",
"c": "this is the value for c"]

Some explanation: The first argument of reduce is the initial value which in this case is the empty dictionary [String: String](). The second argument of reduce is a callback for combining each element of the array into the current value. In this case, the current value is the dictionary and we define a new key and value in it for every array element. The modified dictionary also needs to be returned in the callback.


Update: Since the reduce approach can be heavy on memory for large arrays (see comments) you could also define a custom comprehension function similar to the below snippet.

func dictionaryComprehension<T,K,V>(array: [T], map: (T) -> (key: K, value: V)?) -> [K: V] {
var dict = [K: V]()
for element in array {
if let (key, value) = map(element) {
dict[key] = value
}
}
return dict
}

Calling that function would look like this.

let x = ["a","b","c"]
let y = dictionaryComprehension(x) { (element) -> (key: String, value: String)? in
return (key: element, value: "this is the value for \(element)")
}

Update 2: Instead of a custom function you could also define an extension on Array which would make the code easier to reuse.

extension Array {
func toDict<K,V>(map: (T) -> (key: K, value: V)?) -> [K: V] {
var dict = [K: V]()
for element in self {
if let (key, value) = map(element) {
dict[key] = value
}
}
return dict
}
}

Calling the above would look like this.

let x = ["a","b","c"]
let y = x.toDict { (element) -> (key: String, value: String)? in
return (key: element, value: "this is the value for \(element)")
}

Swift List Product

A possible solution (now updated for Swift 2):

let rows = ["a", "b", "c"]
let cols = ["1", "2", "3"]

let squares = rows.flatMap {
row in
cols.map {
col in
row + col
}
}
print(squares)
// [a1, a2, a3, b1, b2, b3, c1, c2, c3]

The inner map() maps the cols array to an array with the
row number prepended to each entry. The outer flatMap() maps
the rows array to an array of arrays (one for each row) and flattens the result.

Slightly more general, one could define the "product" of two
sequences as an array of tuples (pairs) with all combinations:

func product<S : SequenceType, T : SequenceType>(lseq : S, _ rseq : T) -> [(S.Generator.Element, T.Generator.Element)] {
return lseq.flatMap { left in
rseq.map { right in
(left, right)
}
}
}

and then use it as

let squares = product(rows, cols).map(+)

Split a list into two based on the filters in swift

You can use sorted(by:) to achieve your goals. You simply need to check if the current and the next element start with your search term using the prefix operator and in case the current element does start with it, but the next one doesn't, sort the current element before the next one, otherwise preserve current order.

extension Array where Element == String {
/// Sorts the array based on whether the elements start with `input` or not
func searchSort(input: Element) -> Array {
sorted(by: { current, next in current.hasPrefix(input) && !next.hasPrefix(input) })
}
}

["Austria", "Belgium", "Brazil", "Denmark", "Belarus"].searchSort(input: "B")

Output: ["Belgium", "Brazil", "Belarus", "Austria", "Denmark"]

Is there a Swift equivalent to the 'Filter' function in Python?

Yes, there is an equivalant Filter function in Swift:

Filter

The filter method takes a function (includeElement) which, given an
element in the array, returns a Bool indicating whether the element
should be included in the resulting array. For example, removing all
the odd numbers from the numbers array could be done like this:

let numbers = [ 10000, 10303, 30913, 50000, 100000, 101039, 1000000 ]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
// [ 10000, 50000, 100000, 1000000 ]

More about Map, Filter and Reduce in Swift

Recreate the code without list comprehension

Let's say our input is this...

userinput = 'foo,bar'

Using the list comprehension code...

items=[x for x in userinput.split(",")]
items.sort()
print (items)
Output becomes: `['bar', 'foo']`

However, if we use your recreated code..

userinput = userinput.split(',')
words = []
for i in range (len(userinput)):
words.append(userinput)
words.sort()
print (words)
Output becomes: `[['foo', 'bar']]

Why is this?

When the line userinput = userinput.split(',') is run, userinput now becomes ['foo', 'bar'].
Therefore, when words.append(userinput) is run, what it is actually doing is saying words.append(['foo', 'bar']), and thus you are appending a list into a list meaning that words = [['foo', 'bar']].

words.sort() will not sort nested lists within itself, therefore, your list isnt sorted.

Therefore the fix is to append each element of userinput into words instead of appending userinput as a list into words.

userinput = userinput.split(',')
words = []
for i in range (len(userinput)):
words.append(userinput[i])
words.sort()
print (words)
Output becomes: ['bar', 'foo']


Related Topics



Leave a reply



Submit