0

I have a dictionary -> var dictionary = [String : [String]]() and I want to append string values in the array of the dictionary. This is how I'm doing it

 for (key, value) in dictionary {
  dictionary.updateValue(value.append(nameText),forKey: "name")
}

Here, nameText is a string, I'm getting an error saying,

Cannot use mutating member on immutable value: 'value' is a 'let' constant.

What am I doing wrong? Help would be much appreciated.

Nirav D
  • 71,513
  • 12
  • 161
  • 183
shravan.sukumar
  • 109
  • 2
  • 11

4 Answers4

1

Your first issue is that value is a let constant within your loop body. You must declare it as var in order to mutate it.

Your second issue is that you're trying to use value.append(nameText) as the value to set for the key. However, append() mutates the array in place, and returns Void.

Thirdly, don't use updateValue(forKey:). There's really no point. Use subscripting instead.

var dictionary = [
    "a" : ["a"],
    "b" : ["b", "b"],
    "c" : ["c", "c", "c"],
]

let nameText = "foo"
for (key, var value) in dictionary {
    value.append(nameText)
    dictionary["name"] = value
}

Now, this gets your code to compile, but I'm highly skeptical this is really what you want to do. You'll be overwriting the value for the "name" key on every iteration, meaning only the last iteration's value will persist. Furthermore, because Dictionary doesn't have a defined ordering, this code has indeterminate behaviour. What are you actually trying to do?

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • What my objective is to build a dictionary something like this -> `[name: [all different names], address: [all different address], gender: [all genders]]`. My dictionary looks something like that -> ` var dictionary = [String : [String]]()` – shravan.sukumar Dec 15 '16 at 16:08
  • Firstly I have to ask, why are you using a dictionary to store data with static keys? – Alexander Dec 15 '16 at 16:09
  • What would you suggest? – shravan.sukumar Dec 15 '16 at 16:12
  • Using structs or classes. Dictionaries are really clunky for storing staticky keyed data – Alexander Dec 15 '16 at 16:15
  • Would this `var value` approach induce an extra copy overhead as compared to mutating the the inner array in-place (`dictionary.keys.forEach { dictionary[$0]?.append(nameText) }`)? The latter might need to move the array, but maybe there's an equivalent copy "overhead" in that approach as well. – dfrib Dec 16 '16 at 07:28
  • This would almost certainly cause a copy, unless the compiler optimized it. However, your approach is equally susceptible, as ultimately it's just a facade over a read-modify-write operation, in which the "modify" entails "copy" due to value semantics. – Alexander Dec 16 '16 at 07:36
  • That sounds plausible. Btw, don't forget tagging (@) when answering comments (except when commenting to the author of an answer or question that owns the comments, in which case they're redundant), I almost missed your answer to my comment as SO didn't notify me about it :) – dfrib Dec 16 '16 at 07:53
  • @dfri Whoops. Nice catch. But yeah, If you have the time and want to fire up SIL inspector, or manually compile to assembly, I'd be curious to see a comparison of the 2 techniques – Alexander Dec 16 '16 at 07:54
  • 1
    Might look into after work, or to the weekend. If so, will let you know (Y) – dfrib Dec 16 '16 at 08:43
  • 1
    Interesting and very related [Q&A](http://stackoverflow.com/questions/41079687/dictionary-in-swift-with-mutable-array-as-value-is-performing-very-slow-how-to) (especially the answer). In light of RobNapiers answer, I belive both our methods are probably to be avoided in performance tight applications, in favour of actually removing the inner value (array) prior to mutating and re-adding it to the dictionary. – dfrib Dec 16 '16 at 14:42
  • @dfri Great post, and great find! – Alexander Dec 16 '16 at 14:49
0

Try this:

for (key, value) in dictionary {
    dictionary.updateValue(value + [nameText], forKey: key)
}
Code Different
  • 90,614
  • 16
  • 144
  • 163
0

Think about it for a second; value.append(nameText) is an action. It returns Void (the type for ... nothing!).

You want to update the value to something upon which an action has been performed.

Instead of manually making a temporary copy, modifying that, and then using it to update the value for some key, you can simply use subscripts and extensions:

What you want is:

extension Dictionary
{
    public subscript(forceUnwrapping key: Key) -> Value
    {
        get
        {
            return self[key]!
        }

        set
        {
            self[key] = newValue
        }
    }
}

So, for a dictionary named dictionary:

for key in dictionary.keys
{
    dictionary[forceUnwrapping: key].append(nameText)
}

Specifically, dictionary[forceUnwrapping: key].append(nameText).

Vatsal Manot
  • 17,695
  • 9
  • 44
  • 80
0
/* example setup */
var dictionary: [String: [String]] = ["foo": [], "bar": []]
let nameText = "foobar"

/* append the value of the 'nameText' immutable to each inner array */
dictionary.keys.forEach { dictionary[$0]?.append(nameText) }

/* ok! */
print(dictionary) // ["bar": ["foobar"], "foo": ["foobar"]]

As described in the following Q&A, however

..., it is good to be aware of the overhead of mutating "in place", especially if working performance tight applications. Taking the advice from the answer in the linked thread above, an alternative, more sensible and less copy-wasteful approach would be e.g.:

var dictionary: [String: [String]] = ["foo": [], "bar": []]
let nameText = "foobar"

dictionary.keys.forEach { 
    var arr = dictionary.removeValue(forKey: $0) ?? []
    arr.append(nameText)
    dictionary[$0] = arr
}

print(dictionary) // ["bar": ["foobar"], "foo": ["foobar"]]
Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Would it possible to add it to one the `foo` key? Rather than adding it to both the `foo` and the `bar` keys? – shravan.sukumar Dec 15 '16 at 15:41
  • @shravan.sukumar if you only want to update the inner array for _one_ specific key (say `foo`), you can e.g. simply use `dictionary["foo"]?.append(nameText)`. – dfrib Dec 16 '16 at 07:21