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 SequenceType
s, 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
Color Attribute Is Ignored in Nsattributedstring with Nslinkattributename
Implicitly Unwrapped Optional Made Immutable
Querying Below Autoid's in Firebase
Swift Make Method Parameter Mutable
Arkit - How to Export Obj from Iphone/iPad with Lidar
Split Now Complains About Missing "Isseparator"
How to Append a Tuple to an Array Object in Swift Code
Remove Default Padding from List in Swiftui
Firebase Query Ordering Not Working Properly
Swift Custom Context Menu Previewprovider Can Not Click Any View Inside(Using Tapgesture)
Please Help Me Intepret This Code Swift
Returning a Value from a Function with Alamofire and Swiftyjson
Firebase: How to Update Multiple Nodes Transactionally? Swift 3