Merge arrays with condition - arrays

I would like to merge two arrays with specific condition and update objects that they are containing.
First my struct that is in arrays:
struct Item {
var id:Int
var name:String
var value:Int
}
Second elements for the two arrays:
let fisrt = Item(id: 1, name: "Oleg", value: 3)
let second = Item(id: 2, name: "Olexander", value:5)
let fisrtInSecond = Item(id: 1, name: "Bogdan", value: 6)
let secondInSecond = Item(id: 2, name: "Max", value: 9)
Arrays:
var fisrtArray = [fisrt, second]
let secondArray = [fisrtInSecond, secondInSecond]
I woudl like to use zip and map functions of the collection to achive result. Result is that fisrtArray elements names are updated by id.
Example: fisrtArray = [Item(id: 1, name: "Bogdan", value:3), Item(id: 2, name: "Max", value:5)]
I know how to do this via simple loops. But i am looking for more advanced usage of the functional programing is Swift.
My experiment:
fisrtArray = zip(fisrtArray, secondArray).map()
The main problem i do not know how to write condition in the map function. Condition should be:
if ($0.id == $1.id) {
$0.name = $1.name
}
From the comment discussing it is possible to highlight that zip is not suitable in my case because we should iterate over all array to find if we have similar id's that are not in the same order.

The following code does work independently by the order of the elements inside the 2 arrays
firstArray = firstArray.map { (item) -> Item in
guard
let index = secondArray.index(where: { $0.id == item.id })
else { return item }
var item = item
item.name = secondArray[index].name
return item
}
"[Item(id: 1, name: "Bogdan", value: 3), Item(id: 2, name: "Max", value: 5)]\n"
Update
The following version uses the first(where: method as suggested by Martin R.
firstArray = firstArray.map { item -> Item in
guard let secondElm = secondArray.first(where: { $0.id == item.id }) else { return item }
var item = item
item.name = secondElm.name
return item
}

A solution for your specific problem above would be:
struct Item {
var id: Int
var name: String
}
let first = Item(id: 1, name: "Oleg")
let second = Item(id: 2, name: "Olexander")
let firstInSecond = Item(id: 1, name: "Bogdan")
let secondInSecond = Item(id: 2, name: "Max")
let ret = zip([first, second], [firstInSecond, secondInSecond]).map({
return $0.id == $1.id ? $1 : $0
})
=> But it requires that there are as many items in the first as in the second array - and that they have both the same ids in the same order...

The map function cannot directly mutate its elements. And since you're using structs (passed by value), it wouldn't work anyway, because the version you see in $0 would be a different instance than the one in the array. To use map correctly, I'd use a closure like this:
fisrtArray = zip(fisrtArray, secondArray).map() {
return Item(id: $0.id, name: $1.name, value: $0.value)
}
This produces the result you're expecting.
Now, if your structs were objects (value types instead of reference types), you could use forEach and do the $0.name = $1.name in there.

Related

Swift: How to correctly compare String value to multiple values

I'm looking for the best method to compare a string value entered by the user and compare it to the proper Stage and Level value.
As of now I've made a lot of arrays like so
let Stage1Level1 = ["Phone","Computer,Television"]
let Stage1Level2 = ["Horse","Shoe"]
let Stage1Level3 = ["Milk"]
let Stage2Level1 = ["Snow","Sun"]
let Stage2Level2 = ["Smile","Cry","Water","Salt"]
let Stage2Level3 = ["Five"]
and so on...
So instead of making a long if statement checking for which Stage and Level the user entered I'm looking for the most efficient way of doing this.
Something like this:
var currentStage = 1
var currentLogo = 2
#IBAction func textFieldChanged(_ sender: Any) {
if textFieldChangedOut.text? == Stage(currentStage)Level(currentLogo){
print("Contains the value")
}
}
It's not really clear what these string are, but this is definitely the wrong data structure. I suspect you're looking for something like this, an array of stages that each contain an array of levels, which contain an array of strings.
struct Level {
var values: [String]
}
struct Stage {
var levels: [Level]
}
let stages = [
Stage(levels: [
Level(values: ["One", "Two"])
Level(values: ["Horse", "Shoe"]),
Level(values: ["One", "Two"]),
]),
Stage(levels: [
Level(values: ["Snow", "Sun"]),
Level(values: ["Smile", "Cry"]),
Level(values: ["Five", "Six"]),
]),
]
var currentStage = 1
var currentLogo = 2
// Remember that arrays are 0-indexed. If "currentStage" is 1-indexed
// you need to adjust it
let level = stages[currentStage - 1].levels[currentLogo - 1]
let words = level.values
if let text = textFieldChangedOut.text, words.contains(text) {
print("Contains the value")
}
What you're trying to do with dynamically computing the name of the variable is impossible in pure Swift. There are ways to achieve it by bridging to ObjC, but they're not the right way to attack this problem.
I would create a struct of stage, level and the strings and have an array of that struct
struct StageLevel {
let stage: Int
let level: Int
let words: [String]
}
let stageLevelArray: [StageLevel] =
[StageLevel(stage: 1, level: 1, words: ["Hello", "Hi"]),
StageLevel(stage: 1, level: 2, words: ["Red", "Blue", "Green"]),
StageLevel(stage: 2, level: 1, words: ["No", "Yes"])]
then you can filter out all elements for a chosen stage
let levels = stageLevelArray.filter( { $0.stage == 1} )
or filter out for a stage and a level
let selection = stageLevelArray.filter( { $0.stage == 1 && $0.level == 2 } )
or if you only want the levels or arrays
let levels = stageLevelArray.filter( { $0.stage == 1} ).map { $0.level}
let selection = stageLevelArray.filter( { $0.stage == 1 && $0.level == 2 } ).map { $0.words }
Maybe you can understand adding a dictionary to your current data.
let Stage1Level1 = ["Phone","Computer,Television"]
let Stage1Level2 = ["Horse","Shoe"]
let Stage1Level3 = ["Milk"]
let Stage2Level1 = ["Snow","Sun"]
let Stage2Level2 = ["Smile","Cry","Water","Salt"]
let Stage2Level3 = ["Five"]
var currentStage = 1
var currentLogo = 2
var stageDict : [String: [String]] = [:]
stageDict["Stage1Level1"] = Stage1Level1
stageDict["Stage1Level2"] = Stage1Level2
stageDict["Stage1Level3"] = Stage1Level3
stageDict["Stage2Level1"] = Stage2Level1
stageDict["Stage2Level2"] = Stage2Level2
stageDict["Stage2Level3"] = Stage2Level3
//You also can build by this way
[[Stage1Level1, Stage1Level2, Stage1Level3], [Stage2Level1, Stage2Level2,Stage2Level3]]
.enumerated().forEach{ stage in stage.element.enumerated().forEach{
stageDict["Stage\(stage.offset+1)Level\($0.offset+1)"] = $0.element
}
}
#IBAction func textFieldChanged(_ sender: Any) {
if stageDict["Stage\(currentStage)Level\(currentLogo)"]!.contains(textFieldChangedOut.text!) {
print("Contains the value")
}
}

Swift 4 Array Find or First

I'm trying to perform a filter on an array of structs whereby if the element exists return the element, otherwise return the first element in the array. At the moment I have a func to do this, was wondering if there was a more efficient way. Predicate or Array extension?
For example, if this is my array of structures and method:
struct Person {
let name: String
let age: Int
}
let items = [Person(name: "Fred", age: 12),
Person(name: "Bill", age: 14),
Person(name: "Jane", age: 15),
Person(name: "Mary", age: 12)]
// Find the person based on name, if no match, return first item
func filterOrFirst(with name: String? = "") -> Person?
{
if (items.contains(where: {$0.name == name}))
{
return items.first(where: {$0.name == name})
}
return items.first
}
print(filterOrFirst(with: "Bill")) // prints Bill
print(filterOrFirst()) // prints Fred
You can do it like
func filterOrFirst(with name: String? = "") -> Person? {
if let item = items.first(where: {$0.name == name}) {
return item
}
return items.first
}
So you need not to traverse complete array two times. You can use ternary operator here. But it increases compilation time.
You can get idea from this:
var arrData:[String] = ["Cat","Dog","Horse","Sheep"]
func respondTheValue(strSearch:String) -> String{
let stringToSearch = strSearch
if let index = arrData.index(of: stringToSearch){ //Check if data is present or not
return arrData[index]
}else{ // Data not present then return first
return arrData.first!
}
}
And use it Like:
let responseStr = respondTheValue(strSearch: "Dog")
print(responseStr) //Case True : Output -- Dog
let responseStr1 = respondTheValue(strSearch: "Fish")
print(responseStr1) //Case False : Output -- Cat
Hope this helps.

Is there a better way to create an index of an array?

I made up a method to add a variable rankto an array of structs.
The array friutsArrayis created like in the function makeFriuts(). After that, the data gets sorted and according to this, every item gets a rank, respectively index.
In the end I need the FriutsWithRankstruct like it is.
But I´m wondering if there is a better, more effective way to to that. Maybe by even skipping the whole Friuts struct:
struct Friuts {
var name: String
var price: Double
}
struct FriutsWithRank {
var name: String
var price: Double
var rank: Int
}
var friutsArray = [Friuts]()
func makeFriuts() {
friutsArray.append(Friuts(name: "mango", price: 1.2))
friutsArray.append(Friuts(name: "banana", price: 0.79))
friutsArray.append(Friuts(name: "orange", price: 2.2))
}
func makeFriutsWithRank(data: [Friuts]) -> [FriutsWithRank] {
let dataSorted = data.sorted { $1.price < $0.price }
var datatoappend = [FriutsWithRank]()
var i = 0
dataSorted.forEach { fruit in
i += 1
let name = fruit.name
let price = fruit.price
let rank = i
let result = FriutsWithRank(name: name, price: price, rank: rank)
datatoappend.append(result)
}
return datatoappend
}
let friutsArrayWithRank = makeFriutsWithRank(data: friutsArray)
With more effective i mean not necessarily less code. I think the two arrays are now created with two iterations. Is it possible to skip the whole Fruits struct and work just with one struct and one iteration?.
I have applied some modification on your code, please read the inline comments. Not much more optimised, but more readable for sure.
// Typo fixed + it is a single Fruit, not Fruits
struct Fruit {
var name: String
var price: Double
}
struct FruitsWithRank {
// You already have a variable holding name and price, Friut
// Lets just reuse Fruit object
var fruit: Fruits
var rank: Int
}
var fruits = [Fruit]()
func makeFruits() {
fruits.append(Fruit(name: "mango", price: 1.2))
fruits.append(Fruit(name: "banana", price: 0.79))
fruits.append(Fruit(name: "orange", price: 2.2))
}
func makeFruitsWithRank(data: [Fruits]) -> [FruitsWithRank] {
let dataSorted = data.sorted { $1.price < $0.price }
var datatoappend = [FruitsWithRank]()
// Use `enumerated` to get index and the object straight away
for (index, fruit) in dataSorted.enumerated() {
// Just init your `FruitsWithRank` with the fruit and the index
let rankedFruit = FruitsWithRank(fruit: fruit, rank: index)
// Add to your array
datatoappend.append(rankedFruit)
}
return datatoappend
}
let fruitsWithRank = makeFruitsWithRank(data: fruitsArray)
EDIT:
Following the edits of your question, i have applied some changes. If you need FruitsWithRank having name and price, you can just create tuples with name and price, and create an array straight away without any loops or appends. You can omit the makeFruitsWithRank function, and sort, enumerate and create your types straight on the tuple array.
struct FruitsWithRank {
var rank: Int
var name: String
var price: Double
}
let rankedFruits: [FruitsWithRank] = [
(name: "mango", price: 1.2),
(name: "banana", price: 0.79),
(name: "orange", price: 2.2)]
.sorted { $0.price < $1.price }
.enumerated()
.map({ return FruitsWithRank(rank: $0 + 1, name: $1.name, price: $1.price) })
In the end this isn't much more efficient than your code, but it is more compact:
func makeFriutsWithRank(data: [Friuts]) -> [FriutsWithRank] {
let dataMapped = data.sorted { $1.price < $0.price }
.enumerated()
.map { FriutsWithRank(name: $1.name, price: $1.price, rank: $0 + 1) }
return dataMapped
}
Is it really necassary to have Struct? Because you already have sorted, the index can serve as rank.

Group elements of an array by some property

I have an array of objects with property date.
What I want is to create array of arrays where each array will contain objects with the same date.
I understand, that I need something like .filter to filter objects, and then .map to add every thing to array.
But how to tell .map that I want separate array for each group from filtered objects and that this array must be added to "global" array and how to tell .filter that I want objects with the same date ?
It might be late but new Xcode 9 sdk dictionary has new init method
init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
Documentation has simple example what this method does.
I just post this example below:
let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]
let studentsByLetter = Dictionary(grouping: students, by: { $0.first! })
Result will be:
["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]]
improving on oriyentel solution to allow ordered grouping on anything:
extension Sequence {
func group<GroupingType: Hashable>(by key: (Iterator.Element) -> GroupingType) -> [[Iterator.Element]] {
var groups: [GroupingType: [Iterator.Element]] = [:]
var groupsOrder: [GroupingType] = []
forEach { element in
let key = key(element)
if case nil = groups[key]?.append(element) {
groups[key] = [element]
groupsOrder.append(key)
}
}
return groupsOrder.map { groups[$0]! }
}
}
Then it will work on any tuple, struct or class and for any property:
let a = [(grouping: 10, content: "a"),
(grouping: 20, content: "b"),
(grouping: 10, content: "c")]
print(a.group { $0.grouping })
struct GroupInt {
var grouping: Int
var content: String
}
let b = [GroupInt(grouping: 10, content: "a"),
GroupInt(grouping: 20, content: "b"),
GroupInt(grouping: 10, content: "c")]
print(b.group { $0.grouping })
With Swift 5, you can group the elements of an array by one of their properties into a dictionary using Dictionary's init(grouping:by:) initializer. Once done, you can create an array of arrays from the dictionary using Dictionary's values property and Array init(_:) initializer.
The following Playground sample code shows how to group the elements of an array by one property into a new array of arrays:
import Foundation
struct Purchase: CustomStringConvertible {
let id: Int
let date: Date
var description: String {
return "Purchase #\(id) (\(date))"
}
}
let date1 = Calendar.current.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let date2 = Calendar.current.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let date3 = Calendar.current.date(from: DateComponents(year: 2012, month: 8, day: 15))!
let purchases = [
Purchase(id: 1, date: date1),
Purchase(id: 2, date: date1),
Purchase(id: 3, date: date2),
Purchase(id: 4, date: date3),
Purchase(id: 5, date: date3)
]
let groupingDictionary = Dictionary(grouping: purchases, by: { $0.date })
print(groupingDictionary)
/*
[
2012-08-14 22:00:00 +0000: [Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
2010-11-21 23:00:00 +0000: [Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)],
2015-04-30 22:00:00 +0000: [Purchase #3 (2015-04-30 22:00:00 +0000)]
]
*/
let groupingArray = Array(groupingDictionary.values)
print(groupingArray)
/*
[
[Purchase #3 (2015-04-30 22:00:00 +0000)],
[Purchase #4 (2012-08-14 22:00:00 +0000), Purchase #5 (2012-08-14 22:00:00 +0000)],
[Purchase #1 (2010-11-21 23:00:00 +0000), Purchase #2 (2010-11-21 23:00:00 +0000)]
]
*/
Abstracting one step, what you want is to group elements of an array by a certain property. You can let a map do the grouping for you like so:
protocol Groupable {
associatedtype GroupingType: Hashable
var grouping: GroupingType { get set }
}
extension Array where Element: Groupable {
typealias GroupingType = Element.GroupingType
func grouped() -> [[Element]] {
var groups = [GroupingType: [Element]]()
for element in self {
if let _ = groups[element.grouping] {
groups[element.grouping]!.append(element)
} else {
groups[element.grouping] = [element]
}
}
return Array<[Element]>(groups.values)
}
}
Note that this grouping is stable, that is groups appear in order of appearance, and inside the groups the individual elements appear in the same order as in the original array.
Usage Example
I'll give an example using integers; it should be clear how to use any (hashable) type for T, including Date.
struct GroupInt: Groupable {
typealias GroupingType = Int
var grouping: Int
var content: String
}
var a = [GroupInt(grouping: 1, content: "a"),
GroupInt(grouping: 2, content: "b") ,
GroupInt(grouping: 1, content: "c")]
print(a.grouped())
// > [[GroupInt(grouping: 2, content: "b")], [GroupInt(grouping: 1, content: "a"), GroupInt(grouping: 1, content: "c")]]
Rapheal's solution does work. However, I would propose altering the solution to support the claim that the grouping is in fact stable.
As it stands now, calling grouped() will return a grouped array but subsequent calls could return an array with groups in a different order, albeit the elements of each group will be in the expected order.
internal protocol Groupable {
associatedtype GroupingType : Hashable
var groupingKey : GroupingType? { get }
}
extension Array where Element : Groupable {
typealias GroupingType = Element.GroupingType
func grouped(nilsAsSingleGroup: Bool = false) -> [[Element]] {
var groups = [Int : [Element]]()
var groupsOrder = [Int]()
let nilGroupingKey = UUID().uuidString.hashValue
var nilGroup = [Element]()
for element in self {
// If it has a grouping key then use it. Otherwise, conditionally make one based on if nils get put in the same bucket or not
var groupingKey = element.groupingKey?.hashValue ?? UUID().uuidString.hashValue
if nilsAsSingleGroup, element.groupingKey == nil { groupingKey = nilGroupingKey }
// Group nils together
if nilsAsSingleGroup, element.groupingKey == nil {
nilGroup.append(element)
continue
}
// Place the element in the right bucket
if let _ = groups[groupingKey] {
groups[groupingKey]!.append(element)
} else {
// New key, track it
groups[groupingKey] = [element]
groupsOrder.append(groupingKey)
}
}
// Build our array of arrays from the dictionary of buckets
var grouped = groupsOrder.flatMap{ groups[$0] }
if nilsAsSingleGroup, !nilGroup.isEmpty { grouped.append(nilGroup) }
return grouped
}
}
Now that we track the order that we discover new groupings, we can return a grouped array more consistently than just relying on a Dictionary's unordered values property.
struct GroupableInt: Groupable {
typealias GroupingType = Int
var grouping: Int?
var content: String
}
var a = [GroupableInt(groupingKey: 1, value: "test1"),
GroupableInt(groupingKey: 2, value: "test2"),
GroupableInt(groupingKey: 2, value: "test3"),
GroupableInt(groupingKey: nil, value: "test4"),
GroupableInt(groupingKey: 3, value: "test5"),
GroupableInt(groupingKey: 3, value: "test6"),
GroupableInt(groupingKey: nil, value: "test7")]
print(a.grouped())
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")],[GroupableInt(groupingKey: nil, value: "test7")]]
print(a.grouped(nilsAsSingleGroup: true))
// > [[GroupableInt(groupingKey: 1, value: "test1")], [GroupableInt(groupingKey: 2, value: "test2"),GroupableInt(groupingKey: 2, value: "test3")], [GroupableInt(groupingKey: nil, value: "test4"),GroupableInt(groupingKey: nil, value: "test7")],[GroupableInt(groupingKey: 3, value: "test5"),GroupableInt(groupingKey: 3, value: "test6")]]
+1 to GolenKovkosty answer.
init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) rethrows where Value == [S.Element], S : Sequence
More Examples:
enum Parity {
case even, odd
init(_ value: Int) {
self = value % 2 == 0 ? .even : .odd
}
}
let parity = Dictionary(grouping: 0 ..< 10 , by: Parity.init )
Equilvalent to
let parity2 = Dictionary(grouping: 0 ..< 10) { $0 % 2 }
In your case:
struct Person : CustomStringConvertible {
let dateOfBirth : Date
let name :String
var description: String {
return "\(name)"
}
}
extension Date {
init(dateString:String) {
let formatter = DateFormatter()
formatter.timeZone = NSTimeZone.default
formatter.dateFormat = "MM/dd/yyyy"
self = formatter.date(from: dateString)!
}
}
let people = [Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Foo"),
Person(dateOfBirth:Date(dateString:"01/01/2017"),name:"Bar"),
Person(dateOfBirth:Date(dateString:"02/01/2017"),name:"FooBar")]
let parityFields = Dictionary(grouping: people) {$0.dateOfBirth}
Output:
[2017-01-01: [Foo, Bar], 2017-02-01: [FooBar] ]
This is a clean way to perform group by:
let grouped = allRows.group(by: {$0.groupId}) // Dictionary with the key groupId
Assuming you have array of contacts like :
class ContactPerson {
var groupId:String?
var name:String?
var contactRecords:[PhoneBookEntry] = []
}
To achieve this, add this extension:
class Box<A> {
var value: A
init(_ val: A) {
self.value = val
}
}
public extension Sequence {
func group<U: Hashable>(by key: (Iterator.Element) -> U) -> [U: [Iterator.Element]] {
var categories: [U: Box<[Iterator.Element]>] = [:]
for element in self {
let key = key(element)
if case nil = categories[key]?.value.append(element) {
categories[key] = Box([element])
}
}
var result: [U: [Iterator.Element]] = Dictionary(minimumCapacity: categories.count)
for (key, val) in categories {
result[key] = val.value
}
return result
}
}

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")]

Resources