Dictionaries [String: [String]] in Swift - arrays

I have a dictionary that looks like this:
var dict = [String: [String]]()
I want to be able to add multiple arrays for a single key. This works fine:
dict["hello"] = ["item 1"]
But when I assign a new array the previous value is obviously overwritten - we want to avoid that:
dict["hello"] = ["item 2"] // overwrites item 1 – how to avoid overwriting?
So I tried to use the append method, but this returns nil:
dict["hello"]?.append("test") // does nothing? output: ()
How can I append strings to the array (value) of a certain key in Swift?

First of all...
... you don't really want this
I want to be able to add multiple arrays for a single key.
Instead I think you want...
... to add a string to the array associated to a given string
Example
In other words you want to go from this
["hello":["item 1"]]
to this
["hello":["item 1", "item 2"]]]
So, how to do it?
Let's begin with your dictionary
var dict = [String: [String]]()
dict["hello"] = ["item 1"]
Now you need to extract the array associated to the hello key
var list = dict["hello"] ?? []
adding a string to it
list.append("item 2")
and finally adding the updated array back into the dictionary
dict["hello"] = list
That's it

This is what your code does
dict["hello"] = ["item 1"] - This sets hello to ["item 1"]
dict["hello"] = ["item 2"] - This sets hello to ["item 2"]
This is just like a variable, for example:
var hello = Array<String>()
hello = ["item 1"] // prints out ["item 1"]
hello = ["item 2"] // prints out ["item 2"]
This is what is happening with your dictionary. You are overriding any stored data with new data.
The problem with appending. This only works if there is already an array at that key.
dict["hello"]?.append("test") This wouldn't work.
But this would.
dict["hello"] = ["test 1"]
dict["hello"]?.append("test")
print(dict) // prints out ["dict":["test 1","test"]]
What you need to do
var dict = Dictionary<String,Array<String>>()
func add(string:String,key:String) {
if var value = dict[key] {
// if an array exist, append to it
value.append(string)
dict[key] = value
} else {
// create a new array since there is nothing here
dict[key] = [string]
}
}
add(string: "test1", key: "hello")
add(string: "test2", key: "hello")
add(string: "test3", key: "hello")
print(dict) // ["hello": ["test1", "test2", "test3"]]
Dictionary Extension
extension Dictionary where Key == String, Value == Array<String> {
mutating func append(_ string:String, key:String) {
if var value = self[key] {
// if an array exist, append to it
value.append(string)
self[key] = value
} else {
// create a new array since there is nothing here
self[key] = [string]
}
}
}
How to use
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var dict = Dictionary<String,Array<String>>()
dict.append("first", key: "hello")
dict.append("second", key: "hello")
dict.append("thrid", key: "hello")
dict.append("one", key: "goodbye")
dict.append("two", key: "goodbye")
print(dict) // ["hello": ["first", "second", "thrid"], "goodbye": ["one", "two"]]
}

Please try this thing and let me know if this is what you require
import UIKit
var dict = [String: [String]]()
if var value = dict["hello"]{
value.append("Hi")
dict["hello"] = value
}else{
dict["hello"] = ["item 1"]
}

Other people have the correct solution. Here is a quick shorthand for the same answer.
var dict = [String: [String]]()
dict["hello"] = (dict["hello"] ?? []) + ["item 1"]
dict["hello"] = (dict["hello"] ?? []) + ["item 2"]
In Swift 4, this will be
var dict = [String: [String]]()
dict["hello"] = dict["hello", default: []] + ["item 1"]
dict["hello"] = dict["hello", default: []] + ["item 2"]

Related

Cannot assign value of type '[String]' to type '[[String : Any]]'

Swift Xcode version 13.2.1
Here we have two Arrays,(1)var dicSearch=String and (2)var searchingDic: [[String: Any]]=[] I want to assign searchingDic to dicSearch when i implement it than it show error like, Cannot assign value of type '[String]' to type '[[String : Any]]'
here's my code, please anyone help!
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchingDic = dicSearch.filter{filtering in
let filterService = filtering["fName"] as? String
return((filterService?.lowercased().contains(searchText.lowercased()))!)
}
It looks like you are trying to create a filtered list based on a search term, but your dicSearch type is an array of strings (i.e: [String]), while your searchingDic is an array of dictionaries (i.e: [[String : Any]]).
This might be confusing when coming from a different language, but in Swift, the following declaration is a dictionary:
var dict: [String: Any] = [
"key1": "value1",
"key2": "value2",
]
so the following:
var arrayOfdicts: [[String: Any]] = [
["foo": "bar"],
["apples": "oranges"],
dict
]
is actually an array, containing a list of dictionaries, notice how I've put the dict declared above in the second array.
The compiler is telling you that you cannot assign a '[String]' to type '[[String : Any]]'
because this:
// example to an array of strings
var fullList: [String] = [
"apples",
"bananas",
"cucumbers"
]
// is not the same as
var arrayOfdicts: [[String: Any]] = [
["foo": "bar"],
["apples": "oranges"],
dict
]
The Array#filter method, iterates the array itself, and returns a new array with only the elements that return true in the return statement.
so either both your arrays need to be [String] or both your arrays need to be [[String:Any]]
example for String arrays:
// array
var fullList: [String] = [
"apples",
"bananas",
"cucumbers"
]
var filteredList: [String] = []
var searchTerm = "b"
filteredList = fullList.filter{ item in
let value = item
return value.lowercased().contains(searchTerm)
}
print(filteredList) // prints ["bananas", "cucumbers"]
an example for filtering with array of dictionaries:
var people: [[String: Any]] = [
["name": "Joe"],
["name": "Sam"],
["name": "Natalie"],
["name": "Eve"]
]
var filteredPeople: [[String: Any]] = []
var nameFilter = "a"
filteredPeople = people.filter{ item in
let value = item["name"] as! String
return value.lowercased().contains(nameFilter)
}
print(filteredPeople) // prints [["name": "Sam"], ["name": "Natalie"]]
Hope this helps :)

Getting values in double array from index. Swift

I have a collection view made up of 4 sections.
Each section is an array of strings, like this:
var picList: [String] = ["Bird", "Cat", "Cow", "Dog", "Duck", "Elephant", "Fish", "Giraffe", "Lion", "Mouse", "Sheep", "Snake" ]
var vehiclesList: [String] = ["Airplane", "Ambulance", "Boat", "Bus", "Car", "Fire_Engine", "Helicopter", "Motorcycle", "Tank", "Tractor", "Train", "Truck" ]
var fruitsList: [String] = ["Apple", "Banana", "Grapes", "Mango", "Orange", "Peach", "Pineapple", "Strawberry", "Watermelon" ]
var bodyPartsList: [String] = ["Arm", "Ear", "Eye", "Face", "Feet", "Hand", "Hair", "Legs", "Mouth", "Nose" ]
I created a UITapGestureRecognizer for the cell, that when I click on the cell, I get the index.
Here is the tapMethod
func handleTap(sender: UITapGestureRecognizer) {
print("tap")
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell = collectionView?.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PicViewCell
print("index path: + \(indexPath)")
} else {
print("")
}
}
Now the index prints like this [0,0] (if I press on the first item, ie bird). [2,4] if I press on the 4th item in the 2nd secion (ie mango). But I don't know how to translate the index, to get the corresponding string value. I mean something like this:
var itemName: String = [2,4] (and itemName would be mango)
I'm new to swift, so any help would be great.
Thanks so much.
What you need is an array of arrays, aka a 2D array - [[String]].
var array = [picList, vehicleList, fruitsList, bodyPartsList]
Then, you can access this with 2 indices like this:
var itemName = array[2][4]
You can create a two dimensional array, and add there your lists, indexpath.section will be the index of list and indexpath.item will be the index in thst list

Swift 3: Array to Dictionary?

I have a large array and need to access it by a key (a lookup) so I need to create Dictionary. Is there a built in function in Swift 3.0 to do so, or do I need to write it myself?
First I will need it for a class with key "String" and later on maybe I will be able to write a template version for general purpose (all types of data and key).
Note for 2019. This is now simply built-in to Swift 5, uniqueKeysWithValues and similar calls.
Is that it (in Swift 4)?
let dict = Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Note:
As mentioned in the comment, using uniqueKeysWithValues would give a fatal error (Fatal error: Duplicate values for key: 'your_key':) if you have duplicated keys.
If you fear that may be your case, then you can use init(_:uniquingKeysWith:) e.g.
let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] // or `let pairsWithDuplicateKeys = array.map{ ($0.key, $0) }`
let firstValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (first, _) in first })
print(firstValues)
//prints ["a": 1, "b": 2]
let lastValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (_, last) in last })
print(lastValues)
//prints ["a": 3, "b": 4]
On Swift 4, you can achieve this by using Dictionary's grouping:by: initializer
For ex:
You have class named A
class A {
var name: String
init(name: String) {
self.name = name
}
// .
// .
// .
// other declations and implementions
}
Next, you have an array of objects of type A
let a1 = A(name: "Joy")
let a2 = A(name: "Ben")
let a3 = A(name: "Boy")
let a4 = A(name: "Toy")
let a5 = A(name: "Tim")
let array = [a1, a2, a3, a4, a5]
Let's say you want to create a Dictionary by grouping all the names by their first letter. You use Swifts Dictionary(grouping:by:) to achieve this
let dictionary = Dictionary(grouping: array, by: { $0.name.first! })
// this will give you a dictionary
// ["J": [a1], "B": [a2, a3], "T": [a4, a5]]
source
Note however that the resulting Dictionary "dictionary" is of type
[String : [A]]
it is not of type
[String : A]
as you may expect. (Use #uniqueKeysWithValues to achieve the latter.)
I think you're looking for something like this:
extension Array {
public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
var dict = [Key:Element]()
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
You can now do:
struct Person {
var name: String
var surname: String
var identifier: String
}
let arr = [Person(name: "John", surname: "Doe", identifier: "JOD"),
Person(name: "Jane", surname: "Doe", identifier: "JAD")]
let dict = arr.toDictionary { $0.identifier }
print(dict) // Result: ["JAD": Person(name: "Jane", surname: "Doe", identifier: "JAD"), "JOD": Person(name: "John", surname: "Doe", identifier: "JOD")]
If you'd like your code to be more general, you could even add this extension on Sequence instead of Array:
extension Sequence {
public func toDictionary<Key: Hashable>(with selectKey: (Iterator.Element) -> Key) -> [Key:Iterator.Element] {
var dict: [Key:Iterator.Element] = [:]
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
Do note, that this causes the Sequence to be iterated over and could have side effects in some cases.
As others already said, we need to understand which are the keys.
However I am trying to provide a solution to my interpretation of your question.
struct User {
let id: String
let firstName: String
let lastName: String
}
Here I am assuming that 2 users with the same id cannot exist
let users: [User] = ...
let dict = users.reduce([String:User]()) { (result, user) -> [String:User] in
var result = result
result[user.id] = user
return result
}
Now dict is a dictionary where the key is the user id and the value is the user value.
To access a user via its id you can now simply write
let user = dict["123"]
Update #1: General approach
Given an array of a given type Element, and a closure that determine the key of an Element, the following generic function will generate a Dictionary of type [Key:Element]
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
return elms.reduce([Key:Element]()) { (dict, elm) -> [Key:Element] in
var dict = dict
dict[extractKey(elm)] = elm
return dict
}
}
Example
let users: [User] = [
User(id: "a0", firstName: "a1", lastName: "a2"),
User(id: "b0", firstName: "b1", lastName: "b2"),
User(id: "c0", firstName: "c1", lastName: "c2")
]
let dict = createIndex(elms: users) { $0.id }
// ["b0": {id "b0", firstName "b1", lastName "b2"}, "c0": {id "c0", firstName "c1", lastName "c2"}, "a0": {id "a0", firstName "a1", lastName "a2"}]
Update #2
As noted by Martin R the reduce will create a new dictionary for each iteration of the related closure. This could lead to huge memory consumption.
Here's another version of the createIndex function where the space requirement is O(n) where n is the length of elms.
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
var dict = [Key:Element]()
for elm in elms {
dict[extractKey(elm)] = elm
}
return dict
}
let pills = ["12", "34", "45", "67"]
let kk = Dictionary(uniqueKeysWithValues: pills.map{ ($0, "number") })
["12": "number", "67": "number", "34": "number", "45": "number"]
swift5 swift4
The following converts an array to a dictionary.
let firstArray = [2,3,4,5,5]
let dict = Dictionary(firstArray.map { ($0, 1) } , uniquingKeysWith: +)
Swift 5
extension Array {
func toDictionary() -> [Int: Element] {
self.enumerated().reduce(into: [Int: Element]()) { $0[$1.offset] = $1.element }
}
}
This extension works for all sequences (including arrays) and lets you select both key and value:
extension Sequence {
public func toDictionary<K: Hashable, V>(_ selector: (Iterator.Element) throws -> (K, V)?) rethrows -> [K: V] {
var dict = [K: V]()
for element in self {
if let (key, value) = try selector(element) {
dict[key] = value
}
}
return dict
}
}
Example:
let nameLookup = persons.toDictionary{($0.name, $0)}
Just do it simply,
let items = URLComponents(string: "https://im.qq.com?q=13&id=23")!.queryItems!
var dic = [String: Any?]()
items.foreach {
dic[$0.name] = $0.value
}
reduce is not very suitable,
let dic: [String: Any?] = items.reduce([:]) { (result: [String: Any?], item: URLQueryItem) -> [String: Any?] in
var r = result
r[item.name] = item.value // will create an copy of result!!!!!!
return r
}
As i understand from you're question you would like to convert to Array to Dictionary.
In my case i create extension for the Array and keys for the dictionary will be indexes of the Array.
Example:
var intArray = [2, 3, 5, 3, 2, 1]
extension Array where Element: Any {
var toDictionary: [Int:Element] {
var dictionary: [Int:Element] = [:]
for (index, element) in enumerate() {
dictionary[index] = element
}
return dictionary
}
}
let dic = intArray.toDictionary
Compatible with Swift 5 Standard Library (Xcode 10.2+ , iOS 12.2).
Here's an example of usage of an initializer init(uniqueKeysWithValues:)
The input let array: [String] = Locale.isoRegionCodes is an array of ISO31661-2 codes represented by a string.
let countryCodeAndName: [String: String] = Dictionary(uniqueKeysWithValues: Locale.isoRegionCodes.map { ($0, Locale.current.localizedString(forRegionCode: $0) ?? "")} )
Returned dictionary, will list all regions with ISO31661-2 code as a key and a localized region name as a value.
Output:
...
"PL":"Poland"
"DE":"Germany"
"FR":"France"
"ES":"Spain"
...
Example 2:
let dictionary: [String: String] = Dictionary(uniqueKeysWithValues: [ ("key1", "value1"), ("key2", "value2")] )
Output:
["key1": "value1", "key2": "value2"]
Important:
Precondition: The sequence must not have duplicate keys.
Code below will crash an app:
let digitWords = ["one", "two", "three", "four", "five", "five"]
let wordToValue = Dictionary(uniqueKeysWithValues: zip(digitWords, 1...6))
with:
Fatal error: Duplicate values for key: 'five'
If you want to follow the pattern set out by map and reduce in swift you could do something nice and functional like this:
extension Array {
func keyBy<Key: Hashable>(_ keyFor: (Element) -> Key) -> [Key: Element] {
var ret = [Key: Element]()
for item in self{
ret[keyFor(item)] = item
}
return ret
}
}
Usage:
struct Dog {
let id: Int
}
let dogs = [Dog(id: 1), Dog(id: 2), Dog(id: 3), Dog(id: 4)]
let dogsById = dogs.keyBy({ $0.id })
// [4: Dog(id: 4), 1: Dog(id: 1), 3: Dog(id: 3), 2: Dog(id: 2)]
Swift way:
extension Sequence {
func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key: Element] {
reduce(into: [:]) { $0[selectKey($1)] = $1 }
}
}
// let arr = [Person(id: 1, name: "Alan")]
// arr.toDictionary { $0.id }
// ==
// [1: Person(id: 1, name: "Alan")]

Swift - Updating values of a multidimensional NSMutableDictionary

In the Playground example below I'm trying to modify a multidimensional NSMutableDictionary. Can someone please explain the correct way to modify a multidimensional mutable dictionary?
import Cocoa
let player = "Player 1"
let characterName = "Magic Glop"
let strength = 23
let defense = 220
let type = "fire"
var example: NSMutableDictionary = ["id":1,
"player":player,
"characters":
["character-name":characterName,
"stats":
["strength":strength,
"defense":defense,
"type":type
]
]
]
// My first attempt to update character-name.
example["characters"]!["character-name"] = "New Name"
// Error: Cannot assign to immutable expression of type 'AnyObject?!'
// Next I tried updating the value of "characters">"stats">"type" with .setObject
example["characters"]!["stats"]!!.setObject("water", forKey: "type")
// Documentation: .setObject adds a given key-value pair to the dictionary. If the key already exists in the dictionary, the object takes its place.
// Error: Execution was interrupted, reason: EXC_BAD INSTRUCTION (code=EXC_i386_INVOP, suncode=0x0).
Thanks in advance!
Class approach: You can update var to let for non-editable parameters.
class Player {
let id: Int
var name: String
var characters = [Character]()
init(id: Int, name: String) {
self.id = id
self.name = name
}
/**
Add new char to Player
*/
func addNewCharacter(new: Character) {
self.characters.append(new)
}
}
class Character {
var name: String
var strength: Int
var defense: Int
var type: String
init(name: String, strength: Int, defense: Int, type: String) {
self.name = name
self.strength = strength
self.defense = defense
self.type = type
}
}
func createPlayer() {
let player1 = Player(id: 1, name: "Bodrum")
// create new char and add to the player1 named Bodrum
let char1 = Character(name: "Magic Glop", strength: 23, defense: 220, type: "fire")
player1.addNewCharacter(char1)
print("old name:\(player1.characters[0].name), old type:\(player1.characters[0].type)")
// update char1's parameters
player1.characters[0].name = "Yalikavak"
player1.characters[0].type = "water"
print("new name:\(player1.characters[0].name), new type:\(player1.characters[0].type)")
}
// old name:Magic Glop, old type:fire
// new name:Yalikavak, new type:water
check this answer.
let player = "Player 1"
let characterName = "Magic Glop"
let strength = 23
let defense = 220
let type = "fire"
var example: [String: AnyObject] = ["id":1,
"player":player,
"characters":
["character-name":characterName,
"stats":
["strength":strength,
"defense":defense,
"type":type
]
]
]
print(example)
var charDic = example["characters"] as! [String: AnyObject]
charDic["character-name"] = "New Name" // change value for key character-name
var statsDic = charDic["stats"] as! [String: AnyObject]
statsDic["type"] = "water" // change the value for type
charDic["stats"] = statsDic // assign updated dic for key stats
example["characters"] = charDic // assign updated dic for key chacracters
print(example)

value into dictionary with array data - swift

I am really not getting this - why is this not working?
var listOfFruit = ["Apple", "Banana","Lemon"]
var emptyDict = [String: String]()
var key = ["Name of Fruit","Name of Fruit","Name of Fruit"]
func createDictionary(){
var index: Int
index = listOfFruit.count
for index in listOfFruit {
emptyDict = [key[index]:listOfFruit[index]]
print (emptyDict)
}
}
I am getting the usual :
I'm trying to guess what you need because your question is pretty unclear.
IF given this input
var fruits = ["Apple", "Banana","Lemon"]
var keys = ["Name of Fruit", "Name of Fruit", "Name of Fruit"]
you want this output
["Name of Fruit 2": "Lemon", "Name of Fruit 0": "Apple", "Name of Fruit 1": "Banana"]
Then you can use this code
let dict = zip(fruits, keys).enumerate().reduce([String:String]()) { (var result, elm) -> [String:String] in
let key = "\(elm.element.1) \(elm.index)"
let value = elm.element.0
result[key] = value
return result
}
or this code
assert(keys.count == fruits.count)
var dict = [String:String]()
for i in 0..<fruits.count {
let key = "\(keys[i]) \(i)"
let value = fruits[i]
dict[key] = value
}

Resources