16

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?

kernelpanic
  • 2,876
  • 3
  • 34
  • 58

3 Answers3

35

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
}
vadian
  • 274,689
  • 30
  • 353
  • 361
17

(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
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 2
    returns an error ``$0 is immutable``. It is my fault. I should have been more precise :( I'm using structs instead of classes, I bet that what's make them immutable by default – kernelpanic Mar 28 '16 at 12:03
  • @kernelpanic Since you didn't show us you `Gnome` class, I had to include one of my own. Are you certain the `age` property of your class is a mutable one (as well as your array of gnomes)? The above works fine for me on Swift 2.2 and Xcode 7.3. – dfrib Mar 28 '16 at 12:05
  • @dfri I suspect that `Gnome` is a struct – vadian Mar 28 '16 at 12:07
  • 1
    Upvoted for the reference to .forEach() this is what I was looking for. – jan.vogt Jun 08 '16 at 10:58
  • @jan.vogt happy to help. – dfrib Jun 08 '16 at 19:21
2

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.

Milos
  • 2,728
  • 22
  • 24