Fetch data from an array with elements of second array - arrays

I have one array "users" with all user data and and second array "userIds" having user's id. I have to fetch User from "users" array using "userIds" array
struct User {
let name: String
let id: Int
}
let users: [User] = [User(name: "a", id: 1),
User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 4),
User(name: "d", id: 5)]
let userIds = [2,3,2,5]
result array that I want is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "b", id: 2),
User(name: "d", id: 5)]
so it can have duplicate data according to the data in "userIds".
Now I tried using Higher order function filter:
let result = users.filter { (user) -> Bool in
return userIds.contains(user.id)
}
but this removes the duplicate data and the output is :
[User(name: "b", id: 2),
User(name: "c", id: 3),
User(name: "d", id: 5)]
One approach that I tried is using for loop :
var result = [User]()
for i in userIds {
result.append(users.filter({ $0.id == i }).first!)
}
which gives the desired output but if there is a better approach please suggest.

You can solve this using first(where:) to search through users:
let result = userIds.compactMap { desiredDataValue in
users.first(where: { $0.id == desiredDataValue })
}
But if you're doing this a lot, it would probably speed things up if you built a datastructure that allows for fast lookup by the "id" value. You should compare the performance for yourself, and see if you do this enough/frequently enough for it to be worthwhile:
let dictsByData = Dictionary(uniqueKeysWithValues:
users
.lazy
.map { dict in
(key: dict.id, value: dict)
}
)
let result = userIds.compactMap { desiredDataValue in dictsByData[desiredDataValue]! }
result.forEach { print($0) }

Well after digging few more and with the help of this blog:
https://medium.com/#abhimuralidharan/higher-order-functions-in-swift-filter-map-reduce-flatmap-1837646a63e8
I tried doing like this:
let results = userIds.compactMap { (int) -> User? in
var matchedUser: User?
if users.contains(where: { (user) -> Bool in
if user.id == int {
matchedUser = user
}
return user.id == int
}) {
return matchedUser
}
return nil
}
and in playground I checked the count the code was executed :
and it seems like the count is less comparing to "for" loop.

Related

How to get sum from an object array by its ID - Swift

I want to get a sum of each object which will be classified by its id.
So, my model is:
struct MyObject {
let id: String
var amount: Double
}
And my data are:
var myObjectArray = [
MyObject(id: "A", amount: 1.0),
MyObject(id: "B", amount: 0.2),
MyObject(id: "A", amount: 0.4),
MyObject(id: "B", amount: 0.8),
MyObject(id: "C", amount: 2.1)
]
The results should be something like this:
myObjectArrayResults = [
MyObject(id: "A", amount: 1.4),
MyObject(id: "B", amount: 1.0),
MyObject(id: "C", amount: 2.1)
]
I tried something to do like this, but it didn't work.
for (index, object2) in newObjectArray.enumerated() {
for object in myObjectArray {
if object2.id == object.id {
newObjectArray[index].amount = newObjectArray[index].amount + object.amount
} else {
newObjectArray.append(object)
}
}
}
What might be wrong?
Thank you in advance for your contribution.
You can use reduce(into:) to calculate the sums using an interim dictionary object and then map the result back to MyModel
let result = myObjectArray
.reduce(into: [:]) { $0[$1.id, default: 0] += $1.amount }
.map(MyObject.init)
You wrote:
for (index, object2) in newObjectArray.enumerated() {
for object in myObjectArray {
if object2.id == object.id {
newObjectArray[index].amount = newObjectArray[index].amount + object.amount
} else {
newObjectArray.append(object)
}
}
}
But at start newObjectArray is empty, no? So it won't work. Then, the logic rest has be re-checked again.
You can do it like that with a for loop:
var newObjectArray = [MyObject]()
for object in myObjectArray {
if let existingIndex = newObjectArray.firstIndex(where: { $0.id == object.id }) {
newObjectArray[existingIndex].amount += object.amount
} else {
newObjectArray.append(object)
}
}
The idea, is to iterate over all the objects in myObjectArray, then find if it already exists, in which case we sum, or else we just append.
With reduced(into:_:), keeping the same kind of logic:
let reduced = myObjectArray.reduce(into: [MyObject]()) { partialResult, current in
if let existingIndex = partialResult.firstIndex(where: { $0.id == current.id }) {
partialResult[existingIndex].amount += current.amount
} else {
partialResult.append(current)
}
}

How to deal out 2 different length arrays by the value?

Think of a company, employees work in different capacities. Employees will be employed according to their capacities. There should be similar capacities for every day.
I want the array1's values ​​in the days array to be equally distributed.
For example: Thursday -> 2 item and 5 value, Friday -> 1 item and 5 value
Saturday -> 1 item and 6 value, Sunday -> 2 item and 6 value.
Let it be distributed with similar numbers.
I have 2 array like this:
let days = ["Thursday","Friday","Saturday","Sunday"]
var array1: [T] = [T(name: "a", value: 3),T(name: "b", value: 2),T(name: "c", value: 5),T(name: "d", value: 1),T(name: "e", value: 6),T(name: "f", value: 4)]
How can I deal out array1 objects to days array?
I tried this:
var arrayValueTotal = 0
for t in array1{
arrayValueTotal = arrayValueTotal + t.value
}
var upperLimit = Double(Double(arrayValueTotal)/Double(days.count)) // This will get value per day. 22/4 = 5.5
for i in (0...days.count-1){ //Handling the days
for j in array1{
if j.value >= Int(upperLimit){ //handling the array1's value
}else if j.value < Int(upperLimit){
}
}
}
Desired result like:
let combinedResult = [("Thursday": [T(name: "a", value: 3),
T(name: "b", value: 2)]),
("Friday": [T(name: "c", value: 5)]),
("Saturday": [T(name: "e", value: 6)]),
("Sunday": [T(name: "f", value: 5),
T(name: "d", value: 1)])]
Here is the Class T:
class T {
var name:String
var value:Int
init(name:String, value:Int) {
self.name = name
self.value = value
}
}
How can I get that? Thanks in advance.
Ok, if I understood correctly, let's start by separating array1 according to limit (that you calculated yourself):
var array1: [T] = [T(name: "a", value: 3),
T(name: "b", value: 2),
T(name: "c", value: 5),
T(name: "d", value: 1),
T(name: "e", value: 6),
T(name: "f", value: 4)]
let reducedArray = array1.reduce(into: [[T]]()) { result, current in
guard var last = result.last else { result.append([current]); return }
if last.reduce(0, { $0 + $1.value }) < limit {
last.append(current)
result[result.count - 1] = last
} else {
result.append([current])
}
}
Then reducedArray is:
[
[T(name:a, value: 3), T(name:b, value: 2)],
[T(name:c, value: 5)],
[T(name:d, value: 1), T(name:e, value: 6)],
[T(name:f, value: 4)]
]
Then
var final: [(String, [T])] = []
for i in 0..<min(days.count, reducedArray.count) { //If the count aren't equal, what to do with the left values?
final.append((days[i], reducedArray[i]))
}
print("Final: \(final)")
With final being:
[
("Thursday", [T(name:a, value: 3), T(name:b, value: 2)]),
("Friday", [T(name:c, value: 5)]),
("Saturday", [T(name:d, value: 1), T(name:e, value: 6)]),
("Sunday", [T(name:f, value: 4)])
]

how to combine duplicate key in dictionary and sum up values in swift?

var dictionary: [String : Any] = [:]
for revenueItem in revenues {
if let currentValue = dictionary[revenueItem.customerName] {
dictionary[revenueItem.customerName] = [currentValue] + [revenueItem.revenue]
print("hellooooooooooo \(currentValue)")
} else {
dictionary[revenueItem.customerName] = [revenueItem.revenue]
}
}
like i have a 2 customer name with the same keys but different values. i want to sum up their values and make only one key in output but have a total value.
In my tableview cell appears duplicated and not sum up the values and the customername (key) is also duplicated. please help.. i tried everything but failed to correct the output.
You can use Dictionary.init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value), which takes a Sequence of tuples, storing the key-value pairs and a closure, which defines how to handle duplicate keys.
// Create the key-value pairs
let keysAndValues = revenues.map { ($0.customerName, $0.revenue) }
// Create the dictionary, by adding the values for matching keys
let revenueDict = Dictionary(keysAndValues, uniquingKeysWith: { $0 + $1 })
Test code:
struct RevenueItem {
let customerName: String
let revenue: Int
}
let revenues = [
RevenueItem(customerName: "a", revenue: 1),
RevenueItem(customerName: "a", revenue: 9),
RevenueItem(customerName: "b", revenue: 1)
]
let keysAndValues = revenues.map { ($0.customerName, $0.revenue) }
let revenueDict = Dictionary(keysAndValues, uniquingKeysWith: { $0 + $1 })
revenueDict // ["a": 10, "b": 1]

Merge arrays with condition

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.

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