Filter array by matching id properties - arrays

I feel like this has to be answered some where, but I have been searching for a few days with no luck. I have an example below. I have an array of users and I need to filter them down to the ones that have a matching ID property, I know the code below doesn't compile.. would be very grateful for any help with this.
struct User {
var id: Int
var name: String
}
let userArray = [
User(id: 1, name: "A"),
User(id: 2, name: "B"),
User(id: 1, name: "C"),
User(id: 3, name: "D"),
]
let newArray = userArray.filter({ $0.id == $1.id })
// This is what i want to achieve
// newArray = [User(id: 1, name: "A"), User(id: 1, name: "C")]
In the actual project, the id is dynamically returned. So I just need to be able to check for what is matching, without knowing what the id will actually be.

Your approach won't work as filter only takes one dynamic parameter and only processes one item at a time. Therefore it can't match two separate array entries.
You example also doesn't specify how you want to handle the situation where you have multiples of different User.id. This answer assumes you want to be able to separate them into separate arrays.
Dictionary has a handy initialiser that will do the bulk of the work for you and group on a defined property. Grouping on id will give you a dictionary where the key is the id and the values an array of matching User records. You can then filter the dictionary to get a dictionary where there are multiple users for any id.
let multiples = Dictionary(grouping: userArray, by: \.id).filter{$0.value.count > 1}
Using your data you will end up with a dictionary of:
[1: [User(id: 1, name: "A"), User(id: 1, name: "C")] ]

Your condition in filter does not compare to a given id value. Below is one added called, which I call matchingId:
struct User {
var id: Int
var name: String
}
let userArray = [
User(id: 1, name: "A"),
User(id: 2, name: "B"),
User(id: 1, name: "C"),
User(id: 3, name: "D"),
]
let matchingId = 1 // or: let matchingId = someFunctionCallReturningAnId()
let result = userArray.filter { $0.id == matchingId }
print(result)

Related

Swift: Group duplicate array elements into new array

I have an Order object as follows:
struct Order {
let id: Int
let item: String
let price: Int
}
These are grouped in an, however sometimes there are duplicate IDs and I need these to be grouped into their own array of Order duplicate objects. So essentially at the moment I have [Order] and I need to convert this into [[Order]] where all duplicates will be grouped together, and where there are no duplicates they will simply be on their own in an array.
So for example, imagine I have the following array of orders:
[Order(id: 123, item: "Test item1", price: 1)
Order(id: 345, item: "Test item2", price: 1)
Order(id: 678, item: "Test item3", price: 1)
Order(id: 123, item: "Test item1", price: 1)]
This needs to be converted to:
[[Order(id: 123, item: "Test item1", price: 1), Order(id: 123, item: "Test item1", price: 1)],
[Order(id: 345, item: "Test item2", price: 1)],
[Order(id: 678, item: "Test item3", price: 1)]]
I have been playing around with a dictionary and have so far come up with the following:
let dictionary = Dictionary(grouping: orders, by: { (element: Order) in
return element.id
})
This returns the following type:
[Int : [Order]]
Which is close, but I don't really want them in a dictionary like this. I just need to be able to get an [[Order]] array that I can loop through for use in my UI.
All you have to do is to use .values on the dictionary you have created and you have your array,
let values = Dictionary(grouping: orders, by: \.id).values
This somewhat should do the trick, not the best solution but should be enough to get it working.
var orders = [[Order]]()
for order in orders {
if let index = orders.firstIndex(where: { $0.id == order.id }) {
// We have this order id already let's append another duplicate
orders[index].append(order)
} else {
// We don't have this order id, create a new array
orders.append([order])
}
}

How to find an item in an object of array

I have a list of IDs. Also I have an object that has arrays of datas like the below structure.
[
foods(
foodId: 345,
category: 10,
tools: [10],
name: "food name 1"
),
foods(
foodId: 191,
category: 4,
tools: [2],
name: "food name 2"
),
]
In my list I have list [345, 191]
I want to have a mechanism to access the information of the object when I provide a foodId.
I made it work with one inner and one outer loop. But I was wondering if there is an easier way to do it:
ForEach(foodDetails, id: \.self){ item in
ForEach(self.foods.datas){ ex in
if(ex.foodId == item){
Text(ex.name)
}
}
Any idea how to make it work?
Thanks in advance
you can do simply by getting first element where id match
let result = foodDetails.first(where: {$0.foodId == id})
if let food = result {
print(food.name ?? "") //if name is optional
print(food.foodId)
print(food.category)
}
result you got is that foods? optional struct which have this id

Fetch data from an array with elements of second array

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.

Array group by order by and last in angular controller

My array Looks like this
var List = [
{qid: 1, ID: 1, text: "XXX",...},
{qid: 1, ID: 2, text: "XXX",...},
{qid: 1, ID: 3, text: "XXX",...},
{qid: 2, ID: 4, text: "XXX",...},
{qid: 2, ID: 5, text: "XXX",...},
{qid: 3, ID: 6, text: "XXX",...},
{qid: 3, ID: 7, text: "XXX",...},
{qid: 3, ID: 8, text: "XXX",...},
];
I want to query the array so that the final list looks like this
var FinalList = [
{qid: 1, ID: 3, text: "XXX",...},
{qid: 2, ID: 5, text: "XXX",...},
{qid: 3, ID: 8, text: "XXX",...},
];
qid can have multiple ID but the last entry will be the one selected for the FinalList[] array
I want to use something like group by on qid and get last row entered based on qid in angularcontroller.js.
I tried using the reduce function but it does not give me exactly what i want
its been just a month since I have started using angularjs any help will be greatly appreciated
Addition:
I tried doing this
angular.forEach($scope.selectedList, function (item) {
var found = $filter('filter')($scope.selectedList, { QuesID: item.qid});
$scope.FinalList .push(found[found.length - 1]);
$scope.List = $scope.List .filter(function (o1) {
return !$scope.List.some(function (o2) {
return o1.qid=== o2.qid;
});
});
});
I get the first item not the subsequent
You can use a combination of reduce and map to solve this.
First use reduce to group your array by qid:
var grouped = List.reduce(function(agg, x){
(agg[x['qid']] = agg[x['qid']] || []).push(x);
return agg;
}, {});
then map the values of this group, with the element with the highest ID. You can find the element with the highest ID using another reduce within this map:
var result = Object.values(grouped).map(function(grp){
return grp.reduce(function(a, b){
return a['ID'] > b['ID'] ? a : b;
});
});
This is in my opinion the most clean solution.
Here is a Plunker showing it in action
Its a Javascript question more than an angularJS one.
Ok, here goes:
Sort the array first: (Credits : https://stackoverflow.com/a/8837511/6347317)
var sortedList = List.sort(function(a, b){
var keyA = a.qid;
keyB = b.qid;
if(keyA < keyB) return -1;
if(keyA > keyB) return 1;
return 0;
});
Assign the required variables:
var firstqid=sortedList[0].qid; //first object
var finObjArr = []; //final array
var finObj={}; //empty object to keep track of the object to be inserted in final array
var lastObj = sortedList.slice(-1)[0]; //last object of sorted array
var flag = 0; //flag to not insert any more object to final result if the last unique qid is reached
Loop through array and get the result: (finObjArr will have the desired output)
sortedList.forEach(function(obj) {
var qidkey = obj.qid;
//we are checking if current qid is same as the last one. this is to determine the last object with a qid key and then pushing the previous object (held in finObj) to the final array
if (qidkey != firstqid)
{
firstqid=qidkey;
finObjArr.push(finObj);
}
//If the qid is same as earlier one, then make the current object as finObj and if its the last unique qid, inset the last object in the array in final array and set the flag so that no more inserts happen into final array
if (qidkey == firstqid)
{
finObj = obj;
if (qidkey == lastObj.qid && flag == 0) {
finObjArr.push(lastObj);
flag=1;
}
}
})
Please check if this works.
Note: I was not sure if you need the sort on the array. If it already comes in the required order, no need to sort and you can directly run the forEach. Else you will have to write a sort function to have the array in the required order for forEach

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.

Resources