Swift - how to map array to dictionary values? - arrays

I imagine code similar to this:
var someDict: [Int:Bool] = { (0...100).map { someInt -> [Int: String] in (someInt:false) } }
but it does not work :(
How to properly map array of some value to dictionary?

The least syntax you can use involves AnyIterator to repeat a value indefinitely.
Dictionary(uniqueKeysWithValues: zip(0...100, AnyIterator { false }))

You could use reduce like this:
let someDict = (0...100).reduce(into: [Int: Bool]()) { $0[$1] = false }

Answer based on answer of Jessy, dillon-mce and Joakim Danielson.Thanks a lot!
It's needed because of horrible syntax of init of set of keys with default values
extension Dictionary {
init<S: Sequence>(_ keys: S, withVal defaultVal: Value) where S.Element == Key {
self = Dictionary( uniqueKeysWithValues: zip(keys, AnyIterator { defaultVal }) )
}
}
usage:
//enum FileFilterMode: CaseIterable
let a = Dictionary(FileFilterMode.allCases, withVal: false)
let b = Dictionary(0...100, withVal: false)
Another way:
public extension Sequence {
func toDict<Key: Hashable, Value>(block: (Element)->(Value)) -> [Key:Value] where Key == Self.Element {
self.toDict(key: \.self, block: block)
}
func toDict<Key: Hashable, Value>(key: KeyPath<Element, Key>, block: (Element)->(Value)) -> [Key:Value] {
var dict: [Key:Value] = [:]
for element in self {
let key = element[keyPath: key]
let value = block(element)
dict[key] = value
}
return dict
}
}
will give you ability to do magic like:
// dict's keys 0...100 will have value "false"
let a = (0...100).toDict() { _ in false }
// set of tuples -> dict[ $0.0 : $0.1 ]
let b = setOfTuples.toDict( key: \.0 ) { _ in $0.1 }

Related

How can I merge 2 dictionaries into one array?

My JSON data look like this image below. Now I wanna merge the value of Shop Type and Promotion into one to use as collection view data. How can I do that?
I just filter the response data from the server like this:
var dataBanDau: [SDFilterModel] = []
var quickData: [SDFilterModel] = []
let filters: [SDFilterModel] = data
self.filterEntries = filters
//let nsarray = NSArray(array: self.filterEntries! , copyItems: true)
// self.filterEntriesStoreConstant = nsarray as! Array
self.dataBanDau = filters
for i in 0..<self.dataBanDau.count {
if self.dataBanDau[i].search_key.count == 0 {
self.quickData.append(self.dataBanDau[i])
}
}
self.quickData = self.quickData.filter {
$0.type != "range"
}
DispatchQueue.main.async {
//Note: Reload TableView
self.quickFilterCollection.reloadData()
completed(true)
}
}
the class SDFilterModel:
class SDFilterModel: DSBaseModel {
var name = String()
var type = String()
var is_expanded = Int()
var search_key = String()
var filterEntries : [SDFilterModel]?
override func copy(with zone: NSZone? = nil) -> Any {
// This is the reason why `init(_ model: GameModel)`
// must be required, because `GameModel` is not `final`.
let copy = SDFilterModel(dict: self.dictionary)
if let arrAttribute = NSArray(array: self.value , copyItems: true) as? [AttributeValueModel] {
copy.value = arrAttribute
}
return copy
}
override init(dict: Dictionary<String, Any>) {
super.init(dict: dict);
value = self.valueParse()
name = dict.getString(forKey: "name")
type = dict.getString(forKey: "type")
search_key = dict.getString(forKey: "search_key")
is_expanded = dict.getInt(forKey: "is_expanded")!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var value: [AttributeValueModel] = [];
func valueParse()-> [AttributeValueModel] {
guard let childs = (self.dictionary["value"]) as? [Dictionary<String, AnyObject>]
else { return [] }
var output: [AttributeValueModel] = [];
for aDict in childs {
let item = AttributeValueModel(dict:aDict);
// if type == .Range && item.option_id == "0" {
// item.setRangeOptionID(aValue: item.option_name!)
// }
//
output.append(item);
}
return output;
}
Let be Assume you have let myArray = [1,2,3,4,5,6,7,8]
Now you wanted to square of each and every element in the array,
With for loop you do like this
for item in myArray {
print(item * item)
}
Now assume item = $0
With for map you jus do
myArray.map({ $0 * $0 })
Both will gave same output.
map : Use to do same operation on every element of array.
flatmap : It is used to flattern the array of array.
let myArr = [[1,2,3],[4,5,6,7]]
and you want o/p as [1,2,3,4,5,6,7]
So can get above output with myArr.flatMap({$0})
Now back to your question.
let reqArray = myModel.data.map({ $0.value }).flatMap({ $0 })
First, map gaves you array-of-array of key value but you need a single array, so for that you need to use flatmap.
You can take ref : https://medium.com/#Dougly/higher-order-functions-in-swift-sorted-map-filter-reduce-dff60b5b6adf
Create the models like this
struct Option {
let name: String
let searchKey: String
let id: String
}
struct Model {
let type: String
let name: String
let isExpanded: Bool
let value: [Option]
}
You should get the options array values and join all the arrays
let models:[Model] = //...
let collectionViewArray = models.map { $0.value }.reduce([Option](), +)
Using for loop
var collectionViewArray = [Option]()
for model in models {
collectionViewArray.append(contentsOf: model.value)
}

Swift - Convert Array to Dictionary

I just want convert an array of Player Names into a dictionary Scoreboard, giving everyone an initial score of 0.
Meaning...
var playerNames = ["Harry", "Ron", "Hermione"]
becomes...
var scoreBoard: [String:Int] = [ "Ron":0, "Harry":0, "Hermione":0 ]
This is my first time asking a question, but I’m totally completely stuck on what feels so simple and all how-to's/questions I've found are off in some way. I have tried using reduce in a variety of ways, but always end up short. Thanks in advance!
Here's a quick one liner that I like to use:
let scoreboard = playerNames.reduce(into: [String: Int]()) { $0[$1] = 0 }
reduce is definitely one of the more difficult builtin functions to use correctly, but it is what you want here.
let names = ["Harry", "Ron", "Hermione"]
let scoreboard: [String: Int] = names.reduce(into: [:], { result, next in
result[next] = 0
})
It takes 2 parameters: the initial value (in our case, an empty dictionary [:]), and a closure that updates the result with each element in the array. This closure has 2 parameters, result and next. We want to update result based on the next element. Our closure does this by setting result[next] to 0.
If the player names are known to be all different then you can do
let playerNames = ["Harry", "Ron", "Hermione", "Ron"]
var scoreBoard = Dictionary(uniqueKeysWithValues: zip(playerNames,
repeatElement(0, count: playerNames.count)))
print(scoreBoard) // ["Harry": 0, "Ron": 0, "Hermione": 0]
Here zip is used to create a sequence of player/score pairs, from which the dictionary is created.
Remark: Originally I had used AnySequence { 0 } to generate the zeros. Using repeatElement() instead was suggested by Alexander and has the advantage that the correct required capacity is passed to the dictionary intializer.
You can use reduce(into:) as you suspected. You simply need to declare the initial value as [String:Int]() to be an empty Dictionary of the required type, then simply set the value of all keys in playerNames to 0.
var playerNames = ["Harry", "Ron", "Hermione"]
var scoreBoard = playerNames.reduce(into: [String:Int](), { currentScores,player in
currentScores[player] = 0
})
Using reduce(into:_:):
var playerNames = ["Harry", "Ron", "Hermione"]
let playerScore = playerNames.reduce(into: [:]) { counts, playerNames in
counts[playerNames, default: 0] += 0
}
print(playerScore)
To keep a count of the players names (eg. duplicate names):
counts[myArray, default: 0] += 1
So for example if Ron had two entries before the game started (score > 0) then you would know.
Without using reduce(into:_:) method and as an extension:
var playerNames = ["Harry", "Ron", "Hermione"]
extension Sequence where Self.Iterator.Element: Hashable {
func freq() -> [Self.Iterator.Element: Int] {
return reduce([:]) {
( iter: [Self.Iterator.Element: Int], element) in
var dict = iter
dict[element] = 0
return dict
}
}
}
print(playerNames.freq())
// ["Harry": 0, "Hermione": 0, "Ron": 0]
keep a count (eg. duplicate names):
dict[element, default: -1 ] += 1
Here is how you can do that:
var playerNames = ["Harry", "Ron", "Hermione"]
var dictionary = [String: Int]()
for player in playerNames {
dictionary[player] = 0
}
Here is another way to do it:
// Implementation
extension Dictionary {
static func from(_ array: [Value], key: KeyPath<Value, Key>) -> Dictionary<Key, Value> {
var dict: Dictionary<Key, Value> = [:]
array.forEach { dict[$0[keyPath: key]] = $0}
return dict
}
}
/// Usage
let array: [String] = ["Banana", "Apple"]
Dictionary.from(array, key: \.self)
// or if you have a more complex object
struct Foo {
let id: Int
}
let array2: [Foo] = [Foo(id: 1), Foo(id: 2)]
Dictionary.from(array2, key: \.id)
Based on jmad8 answer
Details
Swift 5.3
Xcode 12.0.1 (12A7300)
Solution
extension Sequence {
func toDictionary<Key: Hashable, Value>(where closure: (Element) -> (Key, Value)) -> [Key: Value] {
reduce(into: [Key: Value]()) { (result, element) in
let components = closure(element)
result[components.0] = components.1
}
}
func toCompactDictionary<Key: Hashable, Value>(where closure: (Element) -> ((Key, Value)?)) -> [Key: Value] {
reduce(into: [Key: Value]()) { (result, element) in
guard let components = closure(element) else { return }
result[components.0] = components.1
}
}
}
Usage
// Sample 1
print(languages.toDictionary { (string) -> (Character, String) in
return (string.first!, string)
})
print(languages.toCompactDictionary { (string) -> (Character, String)? in
guard let character = string.first, character != Character("J") else { return nil }
return (character, string)
})
// Sample 2
print(languages.enumerated().toDictionary { (data) -> (Int, String) in
return (data.offset, data.element)
})
// Shorter version of sample 2
print(languages.enumerated().toDictionary { ($0.offset, $0.element) })
// Sample 3
struct Order {
let id: Int
let desctiption: String
}
let orders = [
Order(id: 0, desctiption: "Apple"),
Order(id: 1, desctiption: "Banana"),
Order(id: 2, desctiption: "watermelon")
]
print(orders.toDictionary { ($0.id, $0) })

Filtering Dictionary with an array of random Ints to make a new dict

So I have this method to get an array of random ints between 1-9, a random number of times between 1 and 7.
let n = arc4random_uniform(7) + 1
var arr: [UInt32] = []
for _ in 0 ... n {
var temp = arc4random_uniform(9) + 1
while arr.contains(temp) {
temp = arc4random_uniform(9) + 1
}
print(temp)
arr.append(temp)
}
print(arr)
So that gets me an array like [1,4,2] or [5,7,3,4,6]. And I have a method to turn another array of strings into a enumerated dictionary.
var someArray: [String] = ["War", "Peanuts", "Cats", "Dogs", "Nova", "Bears", "Pigs", "Packers", "Mango", "Turkey"]
extension Collection {
var indexedDictionary: [Int: Element] {
return enumerated().reduce(into: [:]) { $0[$1.offset] = $1.element }
}
}
let dict1 = someArray.indexedDictionary
print(dict1)
giving me the indexed dictionary
[1:"War", 2:"Peanuts",..etc]
MY question is using the Ints of the random array how do I create a new dictionary that only includes those keys and their values?
So for example if arr = [3,1,5]
how do I get a new dictionary of
[3:"dogs", 1:"Peanuts",5:"Bears"].
This should do it:
let finalDict = dict1.filter { arr.contains($0.key) }
Update:
You can even go a step further and skip the whole strings to array mapping. So remove
extension Collection {
var indexedDictionary: [Int: Element] {
return enumerated().reduce(into: [:]) { $0[$1.offset] = $1.element }
}
}
let dict1 = someArray.indexedDictionary
print(dict1)
and just use this:
Swift 4:
let finalArray = someArray.enumerated().flatMap { arr.contains($0.offset) ? $0.element : nil }
Swift 4.1:
let finalArray = someArray.enumerated().compactMap { arr.contains($0.offset) ? $0.element : nil }
Update 2:
If you need a dictionary and not an array in the end use this:
Swift 4:
let finalDict = someArray.enumerated().flatMap { randomInts.contains($0.offset) ? ($0.offset, $0.element) : nil }.reduce(into: [:]) { $0[$1.0] = $1.1 }
Swift 4.1:
let finalDict = someArray.enumerated().compactMap { randomInts.contains($0.offset) ? ($0.offset, $0.element) : nil }.reduce(into: [:]) { $0[$1.0] = $1.1 }

List in dictionary as a template definition

I'm using arrays which are stored in a dictionray for fast access. Because I need this logic for different data types, I like to define it as a template, but I don't have any idea how to pass types. In my own description it should look like this:
struct KeyList {
let key : MyType1
var list = [MyType2]()
init(key : MyType1, value : MyType2) {
self.key = key
self.list.append(value)
}
}
var dicList = [String: KeyList]()
value = ...of MyType2
key = ... of MyType1
if dicList[key] == nil {
// new entry
dicList[key] = KeyList(key: key, value: value)
}
else {
// add it to existing list
dicList[key]!.list.append(value)
}
}
But I want to use Swift 3. Any idea, if this is possible?
You'll need a couple of things:
generics
encapsulation
Snippet
Here's an example
struct Container<Key, Value> where Key: Hashable {
private var dict: [Key:[Value]] = [:]
func list(by key: Key) -> [Value]? {
return dict[key]
}
mutating func add(value: Value, to key: Key) {
dict[key] = (dict[key] ?? []) + [value]
}
}
Usage
Now you can create a Container specifying the Key and the Value types
var container = Container<String, Int>()
container.add(value: 1, to: "a")
container.add(value: 2, to: "a")
container.list(by: "a") // [1, 2]
Update
You asked in the comments how to implement a remove functionality. In this case the Value needs to be Equatable. Here's the code
struct Container<Key, Value> where Key: Hashable, Value: Equatable {
private var dict: [Key:[Value]] = [:]
func list(by key: Key) -> [Value]? {
return dict[key]
}
mutating func add(value: Value, to key: Key) {
dict[key] = (dict[key] ?? []) + [value]
}
mutating func remove(value: Value, from key: Key) {
guard var list = dict[key] else { return }
guard let index = list.index(of: value) else { return }
list.remove(at: index)
dict[key] = list
}
}

Is this possible to check all value in swift array is true instead of looping one by one?

I just whether is this possible to have an array of object MyObject, and the MyObject got a variable called isTrue, except from looping the whole array to check whether all the object in that array is true, is that any short hands to do so? Thanks.
edit/update: Swift 4.2 or later
Swift 4.2 introduced a new method called allSatisfy(_:)
let bools = [true,false,true,true]
if bools.allSatisfy({$0}) {
print("all true")
} else {
print("contains false") // "contains false\n"
}
Swift 5.2 we can also use a KeyPath property
class Object {
let isTrue: Bool
init(_ isTrue: Bool) {
self.isTrue = isTrue
}
}
let obj1 = Object(true)
let obj2 = Object(false)
let obj3 = Object(true)
let objects = [obj1,obj2,obj3]
if objects.allSatisfy(\.isTrue) {
print("all true")
} else {
print("not all true") // "not all true\n"
}
As of Xcode 10 and Swift 4.2 you can now use allSatisfy(_:) with a predicate:
let conditions = [true, true, true]
if conditions.allSatisfy({$0}) {
// Do stuff
}
A purely functional way using reduce function:
let boolArray = [true,true,true,true]
let isAllTrue = boolArray.reduce(true, combine: {$0 && $1}) // true
A simple way to do it is using a predicate:
let notAllTrue = contains(array) {
item in item.isTrue == false
}
Try any of these.
1.
let names = ["Sofia", "Camilla", "Martina", "Mateo", "Nicolás"]
if names.allSatisfy({ $0.count >= 5 }){
print("true")
}else{
print("false")
}
2.
class SelectedModel {
var isSelected : Bool = false
required init(_ isSelected: Bool) {
self.isSelected = isSelected
}
}
let model1 = SelectedModel(true)
let model2 = SelectedModel(true)
let model3 = SelectedModel(true)
let modelArr = [model1,model2,model3]
if modelArr.allSatisfy({ $0.isSelected == true }){
print("true")
}else{
print("false")
}

Resources