I want to refactor this Swift code with a closure syntax
var station: Station!
var allStations = [Station]()
var favoriteStationIds = [Int]()
for favoriteStationId in favoriteStationIds {
for station in allStations {
if station.stationId == favoriteStationId {
station.isFavorite = true
continue
}
}
}
You can use forEach, which have a trailing closure syntax instead of normal for ... in loops.
Moreover, you don't need to manually iterate through both arrays, you can use index(where:), that accepts a closure to find the station with the specified id as favoriteStationId.
favoriteStationIds.forEach{ id in
allStations[allStations.index(where: {$0.stationId == id})!].isFavorite = true
}
Bear in mind that above piece of code assumes all elements in favoriteStationIds are valid ids that are present in allStations (if this is not the case, use optional binding for the index instead of force unwrapping).
Related
Consider the following code:
struct Card {
var name0: String
var name1: String
}
var cards = [Card]()
cards.append(Card(name0: "hello", name1: "world"))
// Need to perform array index access,
// every time I want to mutate a struct property :(
cards[0].name0 = "good"
cards[0].name1 = "bye"
// ...
// ...
// "good bye"
print(cards[0].name0 + " " + cards[0].name1)
Instead of having to perform multiple array index accessing every time I want to mutate a property in struct, is there a technique to avoid such repeating array index accessing operation?
// Ok. This is an invalid Swift statement.
var referenceToCardStruct = &(cards[0])
referenceToCardStruct.name0 = "good"
referenceToCardStruct.name1 = "bye"
// ...
// ...
There are a lot of good answers here, and you should not think of value types as "a limitation." The behavior of value types is very intentional and is an important feature. Generally, I'd recommend inout for this problem, like matt suggests.
But it is also certainly possible to get the syntax you're describing. You just need a computed variable (which can be a local variable).
let index = 0 // Just to show it can be externally configurable
var referenceToCardStruct: Card {
get { cards[index] }
set { cards[index] = newValue }
}
referenceToCardStruct.name0 = "good"
referenceToCardStruct.name1 = "bye"
print(cards[0].name0 + " " + cards[0].name1)
struct Card {
var name0: String
var name1: String
}
var cards = [Card]()
// every time I want to mutate a struct property :(
cards[0].name0 = "good"
cards[0].name1 = "bye"
Instead of having to perform multiple array index accessing every time I want to mutate a property in struct, is there a technique to avoid such repeating array index accessing operation?
No. When you have an array of struct, then in order to make a change to a struct within the array, you must refer to that struct by index.
If you don't want to see the repeated use of the index, you can hide it in a function using inout:
func mutate(card: inout Card) {
card.name0 = "good"
card.name1 = "bye"
}
for index in cards.indices {
mutate(card:&cards[index])
}
Some day, Swift may include for inout which will allow you to cycle through an array of struct and mutate each struct instance directly. But that day is not yet here.
In answer to the implied question whether it is worth switching to a class just to avoid this repeated use of the index, my answer would be No. There is a good reason for using structs — they are much easier to reason about than classes, and are one of Swift's best features — and I would keep using them if that reason matters to you.
struct is a value type you can't get a reference to it's object with assignment , you should go that way , use a mutating method like https://stackoverflow.com/a/52497495/5820010 or use a class instead
If you don't want to repeat the index, then create a variable from the value you want.
var cards = [Card]()
cards.append(Card(name0: "hello", name1: "world"))
var card = cards[0]
card.name0 = "good"
card.name1 = "bye"
// ...
// ...
cards[0] = card // update the array with the updated card
// "good bye"
print(card.name0 + " " + card.name1)
I think the mutating method is the way to go, as Sh_Khan points out.
In your case, I would do something like:
1> struct Card {
2. var name0: String
3. var name1: String
4. }
5.
6. var cards = [Card]()
7. cards.append(Card(name0: "hello", name1: "world"))
cards: [Card] = 1 value {
[0] = {
name0 = "hello"
name1 = "world"
}
}
8> extension Card {
9. mutating func setNames(name0: String, name1: String) {
10. self.name0 = name0
11. self.name1 = name1
12. }
13. }
14> cards[0].setNames(name0: "x", name1: "y")
15> cards
$R0: [Card] = 1 value {
[0] = {
name0 = "x"
name1 = "y"
}
}
Another approach is a wholesale update of cards[0].
Kind of makes you wish for record updating syntax (a la Haskell or Elm) or dictionary merging-type functionality. But look on the bright side. Maybe Swift's lack of making this easy is testament to the fact that it has static-typing-and-value-semantics-while-allowing-mutation and that combination of features, I think, makes the mutating func or full array element update all but required. I'm not sure Swift has a syntactic feature for updating multiple properties in one shot (without writing your own function or method).
In my model, I have some arrays:
var thisArray = [Object]
var thatArray = [Object]
var anotherArray = [Object]
In my view controller, I want to switch on a value to determine which array I will append to:
var whichArray: [Object]!
switch someValue {
case .thisArray: whichArray = thisArray
case .thatArray: whichArray = thatArray // "He went thatArray!"
case .anotherArray: whichArray = anotherArray
}
whichArray.append(object)
But of course this won't work because Array is a value type.
Is there a way to do this? Of course I could do the following:
switch someValue {
case .thisArray: thisArray.append(object)
case .thatArray: thatArray.append(object)
case .anotherArray: anotherArray.append(object)
}
But that is so inelegant and redundant! And if there's other more complex things going on in the surrounding code, then it's especially so.
Is there a solution here? Is it possible to create a reference to a value type?
PS. Even better, though really its own question, is if I could use the name of the case (e.g., "thisArray" for someValue = .thisArray) to set the array, by name (i.e., avoid the whole switch statement and just say objectName.append(object) or something like that) but as far as I know this isn't a thing. Or maybe this IS possible? And maybe it's my birthday?
Since Arrays are value types - as you have said yourself - they can't be passed around (or assigned) as a reference. One solution would be to create a wrapper class for the Array which itself would then be a reference type. You can then assign this wrapper class instead of the arrays themselves.
Now, given that you also said you might prefer to access the Arrays by the name and completely get rid of the switch you could change your design to storing thisArray, thatArray and anotherArray in a Dictionary, with the keys being the different values for someValue.
This way you could simply append to the desired array with:
arrayDict[someValue]?.append(object)
(Given that you've properly set up the dictionary beforehand)
Like this for example:
enum Value {
case thisArray
case thatArray
case anotherArray
}
var arrayDict = [
Value.thisArray : [String](),
Value.thatArray : [String](),
Value.anotherArray : [String]()
]
arrayDict[.thatArray]?.append("Some String.")
For the sake of creating a short working example I've replaced Object with String but that obviously doesn't matter.
I would typically recommend solving this with closures. It's more powerful and safer. For example:
let append: (Object) -> Void
switch someValue {
case .thisArray: append = { thisArray.append($0) }
case .thatArray: append = { thatArray.append($0) }
case .anotherArray: append = { anotherArray.append($0) }
}
append(object)
(It would be ideal here to just say append = thisArray.append, but you can't do that in Swift today. It's a "partial application of a mutating function" and that's not currently legal.)
Even though Swift was designed to reduce pointer operations, pointers are still available:
var thisArray = [1,2,3]
var thatArray = [4,5,6]
var anotherArray = [7,8,9]
var ptr: UnsafeMutablePointer<[Int]>
let someValue = 2
switch someValue {
case 1: ptr = UnsafeMutablePointer(&thisArray)
case 2: ptr = UnsafeMutablePointer(&thatArray)
default: ptr = UnsafeMutablePointer(&anotherArray)
}
ptr.pointee.append(42)
print(thatArray) // [4,5,6,42]
A minor annoyance with this is that you have to call ptr.pointee to access the target array. If you assign the pointee to another variable (i.e. let whichArray = ptr.pointee), any modification to whichArray won't be reflected in the original array.
(I had to change your Object type to Int so that it runs in the IBM Swift Sandbox)
I have the following code that attempts to consolidate redundant elements of an array:
var items : [String] = ["hello", "world", "!", "hello"]
var mutableSet = Set<String>()
items.reduce(mutableSet, combine: { (set: Set<String>, element: String) in
return set.insert(element)
})
set.insert(element) gives me the error Cannot use mutating member on immutable value: 'set' is a 'let' constant. What's wrong and how can I fix it?
The problem with the OP's code is that the accumulator in the reduce is immutable so it won't allow you to use the mutating function insert().
A tidy solution is to define an non-mutating equivalent to insert() called inserting() in an extension to Set as follows.
extension Set {
//returns a new set with the element inserted
func inserting(_ element: Element) -> Set<Element> {
var set = self
set.insert(element)
return set
}
}
Now we can write the reduce as follows
var items : [String] = ["hello", "world", "!", "hello"]
let set = items.reduce(Set<String>()){ accumulator, element in
accumulator.inserting(element)
}
In Swift, collections are value types. Value-typed variables declared with let (as implicitly are function parameters) cannot be modified. Additionally, your closure returns nothing, so reduce will probably not succeed.
I believe that reduce is not the best-suited tool for this task. Consider this for loop instead:
var set = Set<String>()
for element in items { set.insert(element) }
Another even simpler option would be to use the unionInPlace method:
var set = Set<String>()
set.unionInPlace(items)
Even better perhaps, create the set straight from the collection:
var set = Set<String>(items)
The 'set' value returned is a constant. This is important as it is the accumulator, which represents the values that have accumulated, thus far. It should not change in your closure.
Here is an example from a project I'm working on at the moment, where I want to find all of the unique performers, across many theatrical performances. Notice how I am using union, which does not modify the constant value 'performers', but instead consumes it to produce a new value.
let uniquePerformers = performances.reduce(Set<Performer>(), { (performers: Set<Performer>, performance) -> Set<Performer> in
return performers.union(Set(performance.performers))
})
In this application there is the statement:
var instanceCount = { globalHappinessInstanceCount++ }()
In trying to understand the above statement I have found that, as far as I've tested, the statement below achieves the same result:
var instanceCount = globalHappinessInstanceCount++
Q1. What's the first statement achieving that the second doesn't?
Q2. Are the () braces following the closure expression signifying an empty tuple, initialiser syntax, ... or what? I.e how should one read the first statement?
Q1. What's the first statement achieving that the second doesn't?
AFAIK it just creates an unnecessary closure which doesn't add any value...
Q2. Are the () braces following the closure expression signifying an empty tuple, initialiser syntax, ... or what? I.e how should one read the first statement?
It is method call. Just like
let foo = { globalHappinessInstanceCount++ }
foo()
Update:
I just read the code in your link, in the context of class scope, it is different.
class HappinessViewController
{
var instanceCount = { globalHappinessInstanceCount++ }()
}
defines a property instanceCount: Int that get assigned value of globalHappinessInstanceCount++
It is not that much different than var instanceCount = globalHappinessInstanceCount++
However in Swift 3, ++ operator will be removed, which you may want to change it to globalHappinessInstanceCount += 1. But the issue is the result type of += operator is Void instead Int. So you have to write it like
class HappinessViewController
{
var instanceCount: Int = {
let instanceCount = globalInstanceCount
globalInstanceCount += 1
return instanceCount
}()
}
I would like to learn and use more functional programming in Swift. So, I've been trying various things in playground. I don't understand Reduce, though. The basic textbook examples work, but I can't get my head around this problem.
I have an array of strings called "toDoItems". I would like to get the longest string in this array. What is the best practice for handling the initial nil value in such cases? I think this probably happens often. I thought of writing a custom function and use it.
func optionalMax(maxSofar: Int?, newElement: Int) -> Int {
if let definiteMaxSofar = maxSofar {
return max(definiteMaxSofar, newElement)
}
return newElement
}
// Just testing - nums is an array of Ints. Works.
var maxValueOfInts = nums.reduce(0) { optionalMax($0, $1) }
// ERROR: cannot invoke 'reduce' with an argument list of type ‘(nil, (_,_)->_)'
var longestOfStrings = toDoItems.reduce(nil) { optionalMax(count($0), count($1)) }
It might just be that Swift does not automatically infer the type of your initial value. Try making it clear by explicitly declaring it:
var longestOfStrings = toDoItems.reduce(nil as Int?) { optionalMax($0, count($1)) }
By the way notice that I do not count on $0 (your accumulator) since it is not a String but an optional Int Int?
Generally to avoid confusion reading the code later, I explicitly label the accumulator as a and the element coming in from the serie as x:
var longestOfStrings = toDoItems.reduce(nil as Int?) { a, x in optionalMax(a, count(x)) }
This way should be clearer than $0 and $1 in code when the accumulator or the single element are used.
Hope this helps
Initialise it with an empty string "" rather than nil. Or you could even initialise it with the first element of the array, but an empty string seems better.
Second go at this after writing some wrong code, this will return the longest string if you are happy with an empty string being returned for an empty array:
toDoItems.reduce("") { count($0) > count($1) ? $0 : $1 }
Or if you want nil, use
toDoItems.reduce(nil as String?) { count($0!) > count($1) ? $0 : $1 }
The problem is that the compiler cannot infer the types you are using for your seed and accumulator closure if you seed with nil, and you also need to get the optional type correct when using the optional string as $0.