swift: modifying arrays inside dictionaries - arrays

How can I easily add elements to an array inside a dictionary?
It's always complaining with could not find member 'append' or could not find an overload for '+='
var dict = Dictionary<String, Array<Int>>()
dict["key"] = [1, 2, 3]
// all of these fail
dict["key"] += 4
dict["key"].append(4) // xcode suggests dict["key"].?.append(4) which also fails
dict["key"]!.append(4)
dict["key"]?.append(4)
// however, I can do this:
var arr = dict["key"]!
arr.append(4) // this alone doesn't affect dict because it's a value type (and was copied)
dict["key"] = arr
if I just assign the array to a var, modify it and then reassign it to the dict, won't I be copying everything? that wouldn't be efficient nor elegant.

Swift beta 5 has added this functionality, and you've nailed the new method in a couple of your attempts. The unwrapping operators ! and ? now pass through the value to either operators or method calls. That is to say, you can add to that array in any of these ways:
dict["key"]! += [4]
dict["key"]!.append(4)
dict["key"]?.append(4)
As always, be careful about which operator you use -- force unwrapping a value that isn't in your dictionary will give you a runtime error:
dict["no-key"]! += [5] // CRASH!
Whereas using optional chaining will fail silently:
dict["no-key"]?.append(5) // Did it work? Swift won't tell you...
Ideally you'd be able to use the new null coalescing operator ?? to address this second case, but right now that's not working.
Answer from pre-Swift beta 5:
It's a quirk of Swift that it's not possible to do what you're trying to do. The issue is that the value of any Optional variable is in fact a constant -- even when forcibly unwrapping. If we just define an Optional array, here's what we can and can't do:
var arr: Array<Int>? = [1, 2, 3]
arr[0] = 5
// doesn't work: you can't subscript an optional variable
arr![0] = 5
// doesn't work: constant arrays don't allow changing contents
arr += 4
// doesn't work: you can't append to an optional variable
arr! += 4
arr!.append(4)
// these don't work: constant arrays can't have their length changed
The reason you're having trouble with the dictionary is that subscripting a dictionary returns an Optional value, since there's no guarantee that the dictionary will have that key. Therefore, an array in a dictionary has the same behavior as the Optional array, above:
var dict = Dictionary<String, Array<Int>>()
dict["key"] = [1, 2, 3]
dict["key"][0] = 5 // doesn't work
dict["key"]![0] = 5 // doesn't work
dict["key"] += 4 // uh uh
dict["key"]! += 4 // still no
dict["key"]!.append(4) // nope
If you need to change something in an array in the dictionary you'll need to get a copy of the array, change it, and reassign, like this:
if var arr = dict["key"] {
arr.append(4)
dict["key"] = arr
}
ETA: Same technique works in Swift beta 3, though constant arrays no longer allow changes to contents.

The accepted answer bypasses the following much simpler possibility, which also works for older Swift versions:
var dict = Dictionary<String, Array<Int>>()
dict["key"] = [1, 2, 3]
print(dict)
dict["key", default: [Int]()].append(4)
print(dict)
This will print:
["key": [1, 2, 3]]
["key": [1, 2, 3, 4]]
And this:
var dict = Dictionary<String, Array<Int>>()
dict["key", default: [Int]()].append(4)
print(dict)
will print:
["key": [4]]

As a simple workaround you can use a NSMutableArray:
import Foundation
var dict = Dictionary<String, NSMutableArray>()
dict["key"] = [1, 2, 3] as NSMutableArray
dict["key"]!.addObject(4)
I am using effectively such simple solution in my project:
https://github.com/gui-dos/Guigna/blob/5c02f7e70c8ee3b2265f6916c6cbbe5cd3963fb5/Guigna-Swift/Guigna/GuignaAppDelegate.swift#L1150-L1157

Here is what I was telling Nate Cook, in the comments for his quality answer. This is what I consider "easily [adding] elements to an array inside a dictionary":
dict["key"] = dict["key"]! + 4
dict["key"] = dict["key"] ? dict["key"]! + 4 : [4]
For now, we need to define the + operator ourselves.
#infix func +<T>(array: T[], element: T) -> T[] {
var copy = array
copy += element
return copy
}
I think this version removes too much safety; maybe define it with a compound operator?
#infix func +<T>(array: T[]?, element: T) -> T[] {
return array ? array! + element : [element]
}
dict["key"] = dict["key"] + 4
Finally, this is the cleanest I can get it, but I'm confused about how array values/references work in this example.
#assignment func +=<T>(inout array: T[]?, element: T) {
array = array + element
}
dict["key"] += 5

Use 'for in', for getting values from dictionary's inside array. Here's an example to help you
var a = ["x":["a","b","c"], "y":["d"]]
for b in a {
print(b)
}
Output:
("x", ["d"])
("y", ["a", "b", "c"])

Related

How do I fetch the i'th element from a Swift ArraySlice?

Below I am trying to fetch the i'th element of the ArraySlice draggignFan. The code builds fine (no warnings) but the program dies at runtime on the line where I try to index the slice like a normal array:
var draggingFan : ArraySlice<Card>?
...
if let draggingFan = draggingFan {
for i in 1 ..< draggingFan.count {
let card = draggingFan[i] // EXECUTION ERROR HERE
...
}
}
According to the docs there is a first and last method (which I use elsewhere with no problem). So how do I index an ArraySlice in Swift? (Note: I am intentionally skipping the 0'th index in the slice -- that's needed elsewhere).
The indices of the ArraySlice still match those of the original array. In your case, you are accessing index 1 which is not in your slice. If you offset the index by draggingFan.startIndex it will work:
if let draggingFan = draggingFan {
for i in 1 ..< draggingFan.count {
let card = draggingFan[draggingFan.startIndex + i]
...
}
}
Alternatively:
if let draggingFan = draggingFan {
for i in draggingFan.startIndex + 1 ..< draggingFan.endIndex {
let card = draggingFan[i]
...
}
}
This will access the values from the second element in the slice to the last element in the slice:
let original = [1,2,3,4,5,6] // Int array to demonstrate
var draggingFan : ArraySlice<Int>?
draggingFan = original[1...4] // create the slice
if let draggingFan = draggingFan {
// so there's no errors just slice the slice and iterate over it
for i in draggingFan[(draggingFan.startIndex+1)..<draggingFan.endIndex] {
print(i, terminator: ", ")
}
}
Output:
3, 4, 5,
The reason you are having this problem is that the slice maintains the original index numbers of the sequence you got it from. Thus, element 1 is not in this slice.
For example, consider this code:
let arr = [1,2,3,4,5,6,7,8,9]
let slice = arr[2...5]
Now what is slice[1]? It isn't 4, even though that is the second thing in the slice. It's 2, because the slice still points into the original array. In other words, slice[1] is out of the slice's range! That is why you're getting a runtime error.
What to do? Well, the actual indexes of the slice are its indices. That is what you want to cycle thru. But... You don't want the first element pointed to by the slice. So you need to advance the startIndex of the range you're going to iterate through. Thus:
if let draggingFan = draggingFan {
var ixs = draggingFan.indices
ixs.startIndex = ixs.startIndex.advancedBy(1)
for i in ixs {
// ... now your code will work ...
}
}
However, in my view, there's no need to index the slice at all, and you shouldn't be doing so. You should cycle through the slice itself, not thru its indexes. You have this:
for i in 1 ..< draggingFan.count
But that is much like saying
for aCard in draggingFan
...except that you want to drop the first element of the slice. Then drop it! Say this:
for aCard in draggingFan.dropFirst()
To see that this will work, try this in a playground:
let arr = [1,2,3,4,5,6,7,8,9]
let slice = arr[2...5]
for anInt in slice.dropFirst() {
print(anInt) // 4, 5, 6
}
As you can see, we are cycling through exactly the desired elements, with no reference to index numbers at all.
To iterate over the elements in the slice:
draggingFan?.forEach({ (element)
...
})
As far as I know, the get a specific element, it needs to be converted back to an array e.g.
let draggingFanArray = Array(draggingFan!)
Here's the playground code I used to toy around with various scenarios:
import Cocoa
var a: Array<Int>?
var b: ArraySlice<Int>?
a = [1, 2, 3, 4, 5, 6, 7]
b = a![3...5]
let count = b!.count
b!.forEach({ (element) in
print("\(element)")
})
let c = Array(b!)
print(c[2])
edit ArraySlice extension though:
extension ArraySlice {
func elementAtIndex(index: Int)->AnyObject?{
return Array(self)[index] as? AnyObject
}
}
If I have an array:
var arr = [1, 2, 3, 4, 5, 6, 7] // [1, 2, 3, 4, 5, 6, 7]
And I take a slice of the array:
let slice = arr[3..<arr.count] // [4, 5, 6, 7]
This slice will have a startIndex of 3, which means that indexing starts at 3 and ends at 6.
Now if I want a slice containing everything but the first element, I can use the dropFirst() method:
let sliceMinusFirst = slice.dropFirst() // [5, 6, 7]
And at this point, sliceMinusFirst has a startIndex of 4, which means my indexes range from 4 to 6.
Now if I wish to iterate over these to do something with the items, I can do the following:
for item in sliceMinusFirst {
print(item)
}
Alternatively, I can do it with forEach:
sliceMinusFirst.forEach { item in
print(item)
}
By using these forms of iteration, the fact that the startIndex is nonzero doesn't even matter, because I don't use the indices directly. And it also doesn't matter that, after taking a slice, I wanted to drop the first item. I was able to do that easily. I could have even done that at the time I wanted to do the iteration:
slice.dropFirst().forEach { item in
print(item)
}
Here I dropped the first item from the original slice, without creating any intermediate variables.
Remember that if you need to actually use the index, you're probably doing something wrong. And if you genuinely do need the index, make sure you understand what's going on.
Also if you want to get back to zero-based indexing once you make a slice, you can create an array from your slice:
let sliceArray = Array(slice) // [4, 5, 6, 7]
sliceArray.startIndex // 0

Check if value is in the array as dictionary value?

I am looking for a way to see if a value (stored in a dictionary) is in an array. The array looks like this: var array = Array<Dictionary<String, Int>>()
I looked around here, on stackoverflow, and found this: How to check if an element is in an array. Only problem is I can't seem to use the contains method by writing arrray.contains.... What am I doing wrong?
You can use contains within contains to check if any of the dictionaries in an array contains the value you are looking for.
For example, to search array a for the value 1:
let a: [[String: Int]] = [["a": 1, "b": 2], ["c": 3], ["d": 4]]
Swift 1.2:
if contains(a, {contains($0.values, 1)}) {
println("it is in there")
}
Swift 2.0:
/* Thanks to #Qbyte for providing this solution */
if a.contains({ $0.values.contains(1) }) {
print("it is in there")
}
This works because each member of array a gets evaluated with the closure {contains($0.values), 1} to see if it is true. The closure takes the dictionary it is passed, gets the array of values from it and then uses contains to see if the value is in there.
You can use this extension:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
}
Which you can test like:
var array1 = Array<Dictionary<String, Int>>()
array1 = [["zero": 0],["one": 1], ["two": 2]]
array1.contains(["zero": 0]) //true
array1.contains(["five": 5]) //false

Populate Array with a set of Strings from a for-in loop for Swift

I am kinda stumped on figuring this out. I want to populate an array with the string values that comes from a for-in loop.
Here's an example.
let names = ["Anna", "Alex", "Brian", "Jack"]
for x in names {
println(x)
}
The current x value would generate 4 string values (Anna, Alex, Brian, Jack).
However I need some advice in going about getting these four values back into an array. Thank you in advance.
Whatever is on the right side of a for - in expression must be a SequenceType. Array, as it happens, can be initialised with any SequenceType. So if you're just doing something like this:
var newArray: [String] = []
for value in exoticSequence {
newArray.append(value)
}
The same thing can be accomplished (faster), by doing this:
let newArray = Array(exoticSequence)
And it doesn't matter what type exoticSequence is: if the for-in loop worked, Array() will work.
However, if you're applying some kind of transformation to your exoticSequence, or you need some kind of side effect, .map() might be the way to go. .map() over any SequenceType can return an array. Again, this is faster, and more clear:
let exoticSequence = [1, 2, 3]
let newArray = exoticSequence.map {
value -> Int in
// You can put whatever would have been in your for-in loop here
print(value)
// In a way, the return statement will replace the append function
let whatYouWouldHaveAppended = value * 2
return whatYouWouldHaveAppended
}
newArray // [2, 4, 6]
And it's equivalent to:
let exoticSequence = [1, 2, 3]
var newArray: [Int] = []
for value in exoticSequence {
print(value)
let whatYouWouldHaveAppended = value * 2
newArray.append(whatYouWouldHaveAppended)
}
newArray // [2, 4, 6]

swift getting an array from something like array[0..<10]

I want to get a range of objects from an array. Something like this:
var array = [1,3,9,6,3,4,7,4,9]
var newArray = array[1...3] //[3,9,6]
The above would access elements from index 1 to 3.
Also this:
newArray = array[1,5,3] // [3,4,6] would be cool
This would retrieve elements from index 1, 5 and 3 respectively.
That last example can be achieved using PermutationGenerator:
let array = [1,3,9,6,3,4,7,4,9]
let perms = PermutationGenerator(elements: array, indices: [1,5,3])
// perms is now a sequence of the values in array at indices 1, 5 and 3:
for x in perms {
// iterate over x = 3, 4 and 6
}
If you really need an array (just the sequence may be enough for your purposes) you can pass it into Array's init method that takes a sequence:
let newArray = Array(perms)
// newArray is now [3, 4, 6]
For your first example - with arrays, that will work as-is. But it looks from your comments like you're trying it with strings as well. Strings in Swift are not random-access (for reasons relating to unicode). So you can't use integers, they have an String-specific bidirectional index type:
let s = "Hello, I must be going"
if let i = find(s, "I") {
// prints "I must be going"
println(s[i..<s.endIndex])
}
This works :
var n = 4
var newArray = array[0..<n]
In any case in
Slicing Arrays in Swift you'll find a very nice sample of the Python slice using a extension to Arrays in Swift.

swift optional array subscripts

Simple playground code like below:
var array :[Int?]
array = [1, 2, 3]
array![1] = 4
Got an error from Playground
Playground execution failed: error: :8:1: error: '#lvalue $T6' is not identical to 'Int?'
array![1] = 4
Any suggestion guy?
You are defining a non-optional array of optional Ints. When you try to access it you are trying to force unwrap the array itself which is not necessary. You can simply do:
array[1] = 4
An optional array of non-optional Int would look like this:
var array: [Int]?
OK, I got the answer by myself
var array :[Int]?
array = [1, 2, 3]
if var tmpArray = array {
tmpArray[1] = 4
array = tmpArray
}
How could I say this, Cons&Pros

Resources