I want to be able to modify my array of objects using map in Swift of the fly, without looping through each element.
Before here were able to do something like this (Described in more details here:
gnomes = gnomes.map { (var gnome: Gnome) -> Gnome in
gnome.age = 140
return gnome
}
Thanks for Erica Sadun and others, new proposals have gone through and we're now getting rid of C-style loops and using var inside the loop.
In my case I'm first getting a warning to remove the var in then an error my gnome is a constant (naturally)
My question is : How do we alter arrays inside a map or the new styled loops for that matter to be fully prepared for Swift 3.0?
If you want to keep that syntax, just use a (mutable) temporary variable
gnomes = gnomes.map { (gnome: Gnome) -> Gnome in
var mutableGnome = gnome
mutableGnome.age = 140
return mutableGnome
}
(Below follows the case where Gnome is a reference type; a class -- since you haven't showed us how you've defined Gnome. For the case where Gnome as value type (a struct), see #vadian:s answer)
The removal of var will not effect using .map to mutate mutable members of an array of reference type objects. I.e., you could simply use your old approach (omitting however, the var in the .map closure signature).
class Gnome {
var age = 42
}
var gnomes = [Gnome(), Gnome(), Gnome()]
gnomes = gnomes.map {
$0.age = 150
return $0
}
/* result */
gnomes.forEach { print($0.age) } // 3x 150
However, in case you just want to modify your original array rather than assigning the result of .map to a new array, .forEach might be a more appropriate choice than .map.
gnomes.forEach { $0.age = 140 }
/* result */
gnomes.forEach { print($0.age) } // 3x 140
Given:
struct Gnome {
var age: Int = 0
}
var gnomes = Array(count: 5, repeatedValue: Gnome())
... there are two decent options. The first is as #vadian put it:
gnomes = gnomes.map{
var gnome = $0
gnome.age = 70
return gnome
}
Whilst the second keeps control over "ageing" private and simplifies mapping at the point of call:
struct Gnome {
private(set) var age: Int = 0
func aged(age: Int) -> Gnome {
var gnome = self
gnome.age = age
// any other ageing related changes
return gnome
}
}
gnomes = gnomes.map{ $0.aged(140) }
Of course, reference types still have their place in programming, which may well be a better fit in this case. The friction we are experiencing here suggests that we are trying to treat these structures as if they were objects. If that is the behaviour you need, then you should consider implementing Gnome as a class.
Related
I wrote an extension to Array that allows me to pop the last element and instantly add it to another array:
extension Array {
mutating func popLast(to otherArray: inout [Element]) -> Element? {
guard self.count > 0 else { return nil }
return otherArray.appendAndReturn(self.popLast()!)
}
mutating func appendAndReturn(_ element: Element) -> Element {
self.append(element)
return element
}
}
This simple example in playground works like a charm:
var newNumbers = [1,2,3,4,5,6,7,8,9]
var usedNumbers: [Int] = []
newNumbers.popLast(to: &usedNumbers)
print(usedNumbers) // [9]
for _ in newNumbers {
newNumbers.popLast(to: &usedNumbers)
}
print(usedNumbers) // [9, 8, 7, 6, 5, 4, 3, 2, 1]
But using the extension inside of a struct (code after the warning) gives me this warning:
Simultaneous accesses to parameter 'self', but modification requires
exclusive access; consider copying to a local variable
struct Test {
var newNumbers = [1,2,3,4,5,6,7,8,9]
var usedNumbers: [Int] = []
mutating func getNewNumber() -> Int? {
return newNumbers.popLast(to: &usedNumbers)
}
}
It is only a warning and my app runs just fine with the expected behavior, but I'm curious if there really is a danger here. Looking at SE-0176, I understand the goal of the warning, if I were to use it to pop the last element from the same array I append it to, because copy-on-write could mess that up. And so I guess it's related to the struct. But using it on two different arrays inside the same struct, I see no danger. Am I missing something and is there a way to write the extension that would circumvent the potential problem?
Update:
Your code now works without modification.
As #Hamish noted in the comments below, this was a bug that is now fixed in the version of Swift which shipped with Xcode 9 beta 3. I also verified that it works at the IBM Swift Sandbox which is using Linux x86_64 build Swift Dev. 4.0 (Jul 13, 2017).
As you figured out in the comments, the problem is that you need exclusive access to the struct in order to mutate it, but you're passing a reference to part of the struct to the inout parameter. This is apparently supposed to work since you're accessing different parts of the structure, but due to a bug the compiler is too strict here.
The warning suggests copying to a local variable. Since your return is complicated, I have used a defer statement to return the copy of newNumbers to newNumbers to avoid having to store the result of the call in a temporary variable:
struct Test {
var newNumbers = [1,2,3,4,5,6,7,8,9]
var usedNumbers: [Int] = []
mutating func getNewNumber() -> Int? {
var newNumbersCopy = newNumbers
defer { newNumbers = newNumbersCopy }
return newNumbersCopy.popLast(to: &usedNumbers)
}
}
You could also choose to fix it by making a copy of the usedNumbers:
mutating func getNewNumber() -> Int? {
var usedNumbersCopy = usedNumbers
defer { usedNumbers = usedNumbersCopy }
return newNumbers.popLast(to: &usedNumbersCopy)
}
I am currently learning swift and am experimenting with data structures. In may code I have certain routines with a name(String) and several tasks(Array of Strings). These values are in a structure.
So I am trying to add another value to the array after it has been initialized. My code is actually working, however I really think it very weird and odd and DO NOT think, that it is the way it should be done.
var routineMgr: routineManager = routineManager();
struct routine{
var name = "Name";
var tasks = [String]();
}
class routineManager: NSObject {
var routines = [routine]();
func addTask(name: String, desc: String){
//init routines with name and an array with certain values, here "Hallo" & "Moin"
routines.append(routine(name: name, tasks: ["Hallo","Moin"]));
//so i just put this part here to make the example shorter, but it would be in ad different function to make more sense
//adding a new value ("Salut") to the tasks array in the first routine
//getting current array
var tempArray = routines[0].tasks;
//appending new value to current value
tempArray.append("Salut");
//replacing old routine with a copy (same name), but the new array (with the appended salut)
routines[0] = routine(name: routines[0].name, tasks: tempArray);
}
}
I have tried some (to me) "more correct" ways, like:
routines[0].tasks.append("Salut");
But I always got tons of errors, which I also did not understand.
So my question now: How is it actually done correctly? And why does the second way not work?
Your help and advice is really appreciated!
You can create a function to append the values in the struct (that is what I would do). You can even use it to validade values or anything else you need to do before append, it can also return a boolean to let your code know if the value was successfully appended or not
var routineMgr: routineManager = routineManager();
struct routine{
var name = "Name";
var tasks = [String]();
mutating func addTask(task: String){
tasks.append(task)
}
}
class routineManager: NSObject {
var routines = [routine]();
func addTask(name: String, desc: String){
routines.append(routine(name: name, tasks: ["Hallo","Moin"]));
routines[0].addTask("Salut")
}
}
I hope that helps
I am learning how to build apps and working with Swift for this project.
I had a buddy help me pull data in from a website and it looks like he created classes with variables and mapped them to certain extensions (IE "Username") so when I call the variable data such as profile I would call it. The below uses luck_30 able to store "Stats.luck_30"
luck_30.text = profile.luck_30
So inside one of my variables that is in this "Profile" class is setup into an array. I can pull the array out of the class, but I can't seem to do for while statement replacing the [#] with a variable from the for command.
func aliveWorkers(profile: Profile) -> NSNumber{
var myworkers : Array = profile.workers!
//this test works and returns the proper value
var testworker: NSNumber = myworkers[0].alive!
println("The satus of the test worker is " + testworker.description)
/* This code is giving error "Could not find member alive" it does not ifor var
for ifor in myworkers{
var thisworker: NSNumber = myworkers[ifor].alive! as NSNumber
}
*/
return 42
}
Your variable ifor is not a counter, it is an actual object. You could do something like this:
for worker in myWorkers {
let workerIsAlive = worker.alive!
}
Alternatively, if you need the index,
for i in 0 ..< myWorkers.count {
let worker = myWorkers[i]
let workerIsAlive = worker.alive!
}
If you need both:
for (i, worker) in enumerate(myWorkers) {
let workerIsAlive = worker.alive!
}
And as a matter of style, I would stay away from NSNumber and use Int or Bool or whatever the data actually is. Also, it looks like the alive variable should not be optional, as you're unwrapping it everywhere. To avoid "mysterious" crashes later, you may want to think about making it a non-optional type.
when using a for in loop, your loop variable isn't an index, its the objects you're looping through. so..
func aliveWorkers() {
var myworkers = [1, 2, 3]
//this test works and returns the proper value
let testworker = myworkers[0]
print("The satus of the test worker is \(testworker)")
for ifor in myworkers {
print(ifor)
}
}
Notice a few things... you don't need to use + to concatenate those strings. you can just use string interpolation. \(variable) inserts the value of variable in the string.
Try to use let instead of var when you don't change the variable. You don't need to explicitly define type on variables either.
i use the following function to retrieve a random person from an array:
func getRandomPerson() -> String{
if(personArray.isEmpty){
return ""
} else {
var tempArray: [String] = []
for person in personArray{
tempArray += [person.getName()]
}
var unsignedArrayCount = UInt32(tempArray.count)
var unsignedRandomNumber = arc4random_uniform(unsignedArrayCount)
var randomNumber = Int(unsignedRandomNumber)
if tempArray.isEmpty {
return ""
} else {
return tempArray[randomNumber]
}
}
}
I would like to use this function inside an array of strings, Like this:
var theDares: [String] = ["Dare1 \(getRandomPerson())", "Dare2", "Dare3", "Dare4", "Dare5"]
But when i use the functions, it only runs the function once. Can you make the function run everytime you use the "Dare1" in this instance.
Thanks in advance
I think you are asking if you can set up your array so every time you fetch the object at index 0, it re-builds the value there.
The short answer is no. Your code is creating an array of strings, and the item at index 0 is built ONCE using a function call.
However, it is possible to make a custom class implement the subscript operator. You could create a custom object that looks like an array and allows you to index into it using an Int index. In response to the index operator you could run custom code that built and returned a random string.
Since it sounds like you're a beginning programmer creating a custom class the implements the subscript operator might be beyond your current abilities however.
Try like this:
let personArray = ["John", "Steve", "Tim"]
var randomPerson: String {
return personArray.isEmpty ? "" : personArray[Int(arc4random_uniform(UInt32(personArray.count)))]
}
println(randomPerson) // "Steve"
If have an Array and want to convert it to a ByteArray, how should I go about it? The following for instance fails:
var srcArray = Array<Byte>(10, { 0 })
var tgtArray: ByteArray = srcArray as ByteArray
I do realize though that specialized classes such as ByteArray are:
... not related to the Array class and are compiled down to Java’s primitive arrays for maximum performance.
So, the fact that my approach fails shouldn't surprise me - but what is the canonical way to make the conversion? Simply iterate through srcArray and populate tgtArray one index at a time - or is there a more elegant solution I'm missing?
I don't see any built-in functions apart from the obvious loop-based approach. But you could define an extension function like this yourself:
fun Array<Byte>.toPrimitive(): ByteArray {
val tgtArray: ByteArray = ByteArray(this.size())
for (i in this.indices) {
tgtArray[i] = this[i]
}
return tgtArray
}
fun test() {
val srcArray = Array<Byte>(10, { 0 })
val tgtArray: ByteArray = srcArray.toPrimitive()
}
Kotlin has this in the stdlib as an extension function Array<Byte>.toByteArray()
val srcArray = Array<Byte>(10, { 0 })
val tgtArray = srcArray.toByteArray()
(Note: I changed your var to val which is more common in Kotlin to use read-only values)
You will see similar ones for other primitive data types that have array implementations. You can see them all in the Kotlin documentation for Array extension functions.