Find all Objects in a Collection with at least one matching property - angularjs

I have an Array with Objects. In short
Array =[
{id = 1, products='1,2'} //products consist of String with Products Seperates by','
{id = 2, products='1'}
{id = 3, products='3'}
{id = 4, products='1,2,3'}
{id = 5, products='2,3'}
...
]
SelectedProd = ['2,3']// consists a String as well seperated by ','
This are all Displayed in a Table. Im writing now a Filter to show only the ones which are selected through a MultipleSelect.
Therefore I want filter all Objects where at least one Product is in products
So my Filter is getting the Objects and the Selected Products
.filter('filterByProd', () => {
return (objects,prod) => {
var filtered = [];
/*
FIlter Array
*/
return filtered;
};
});
If a User Selects Product = '2,3' it shall return me the Objects with the id=1/3/4/5..
As well clearly if nothing selected return all.
If possible using lodash but other solutions accepted as well.
The problem I have is the Functions are just listed in lodash Docu and cant read everyone which I need.
At the moment Iam stuck at
.filter('filterByProd', () => {
return (items,prod) => {
var filtered = [];
filtered = _.filter(items,['products',prod]);
return filtered;
};
});
This is giving me out only the exact matches with '2,3' -> Object = id=5 only.
I need something
filtered = findAllWhere(selectedProducts,iterateOverSendObjects(checkProductsOfObjects)+_.ifIsAtLeastOneOfSendProducts))

You can use lodash's intersection method to get your desired result.
var a = arr.filter(x => {
return _.intersection(
x.products.split(','), SelectedProd[0].split(',')
).length > 0;
Here's a working inline fiddle:
var arr = [
{id : 1, products:'1,2'},
{id : 2, products:'1'},
{id: 3, products:'3'},
{id : 4, products:'1,2,3'},
{id : 5, products:'2,3'}];
var SelectedProd = ['2,3'];
var a = arr.filter(x => {
return _.intersection(
x.products.split(','), SelectedProd[0].split(',')).length>0;
});
console.log(a);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.js"></script>
Explanation :
Lodash _.intersection returns intersection of two arrays if it exists, or else returns []. We use a filter to filter only those objects where the intersection is non-empty

Once you get this object from the server, modify it, either by injecting a new Array property containing CSV's in Array format or replacing products entirely with an array. Your final array should be something like the following before binding:
[
{id = 1, products=[1,2]},
{id = 2, products=[1]} ... ]
Please mark as answer if this helps

You can filter the items using Array.prototype.filter() with Array.prototype.some() and String.prototype.includes() to search for the selected items in the string:
const findIemsByProducts = (items, selectedProd) => {
const selected = selectedProd[0].split(','); // convert the selected products to array
return items.filter((item) => // filter the items
selected.some((productId) => // if at least one of the selected
item.products.includes(productId) // is in the products string
));
}
var items = [
{id: 1, products: '1,2'},
{id: 2, products: '1'},
{id: 3, products: '3'},
{id: 4, products: '1,2,3'},
{id: 5, products: '2,3'}
];
var SelectedProd = ['2,3'];
var results = findIemsByProducts(items, SelectedProd);
console.log(results);
And the angular filter would be:
.filter('filterByProd', () => (items, selectedProd) => {
const selected = selectedProd[0].split(','); // convert the selected products to array
return items.filter((item) => // filter the items
selected.some((productId) => // if at least one of the selected
item.products.includes(productId) // is in the products string
));
});

Related

Subscribe to an observable and put values into an array

I'm new in angular and I need some help.
I have an observable getting users of type User[]
User: [
id: string,
name: string
]
and I have another array Ids of type string getting the ids of the selected users from a mat-select
Ids = this.Form.controls['users'].value
what I need right now is to subscribe to users$ observable, and get only the users that they have an id in Ids
const selectedUsers = ids.forEach(id =>this.usersSub$.value.filter((user) => user.userId === id))
something like the above but it is not really the right thing to do because it returns undefined . I'm wondering how should I properly get my selectedUsers array.
You use combineLatest to merge both observables and map all elements to accomplish it.
First, Create an observable with ids.
selectedIds$ = of([1, 3]);
players$ = of([
{ id: 1, name: 'lebron' },
{ id: 2, name: 'irving' },
{ id: 3, name: 'love' },
]);
Next, combine both observables, using the combineLatest operator, and return the players using the map to iterate over the response from the combineLast, use the filter and find to match the playerid with the ids from the selectedIds array.
const seletedUsers$ = combineLatest([this.selectedIds$,
this.players$])
.pipe(
map(([ids, players]) => {
return players.filter((p) => ids.find((id) => id === p.id));
})
)
.subscribe((v) => {
console.log(v);
});
https://rxjs.dev/api/index/function/combineLatest
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

How to return objects that have matching value when comparing to a separate array

In my state I have an object called foodLog which holds all entries a user enters with one of the keys being foodSelectedKey and I'm trying to return all entries that have a matching value from that key with a different array called foodFilter.
However, this doesn't work and errors out saying foodLog.filter() isn't a function - I've looked this up and it's because it's an Object (I think). Any help would be greatly appreciated!
state = {
// log food is for the logged entries
foodLog: {},
// used for when filtering food entries
foodFilter: [],
};
findMatches = () => {
let foodLog = this.state.foodLog;
let foodFilter = this.state.foodFilter;
let matched = foodLog.filter((item) => {
return foodLog.foodsSelectedKey.map((food) => {
return foodFilter.includes(food);
});
});
};
I guess the reason behind the error Is not a function is that the object can not be looped. By that it means you can not iterate an object with differend variables inside, if it has no index to be iterated like an array. The same goes for map(), find() and similar functions which MUST be run with arrays - not objects.
As far as I understand you have an object named foodLog which has an array named foodsSelectedKey. We need to find intersected elements out of foodFilter with the array. This is what I came up with:
state = {
// log food is for the logged entries
foodLog: {
foodsSelectedKey: [
{ id: 1, name: "chicken" },
{ id: 2, name: "mashroom" }
]
},
// used for when filtering food entries
foodFilter: [
{ id: 1, name: "chicken" },
{ id: 2, name: "orange" }
]
};
findMatches = () => {
let foodLog = this.state.foodLog;
let foodFilter = this.state.foodFilter;
let matched = foodLog.foodsSelectedKey.filter((key) =>
{
for (let i=0; i<foodFilter.length;i++){
if(foodFilter[i].name===key.name)
return true
}
return false;
}
);
return matched;
};
The Output is filtered array, in this case, of one element only:
[{
id: 1
name: "chicken"
}]
In order to check the output - run console.log(findMatches()). Here is the CodeSandbox of the solution. (check console at right bottom)

How to fetch "name" property from an array if i know its corresponding "id"

I have a simple array with id and name. If i know the id then how can i fetch the name, array is shown below:
var dataobj = [
{id:1,name:"Jessica"},
{id:2,name:"Tom"},
{id:3,name:"Will"}
];
i have the id for example 2 with me in a variable, how can i get the name which belongs to this id ?
I have clickedID=2 value in my slist.component.ts and i want to fetch its corresponding name, how can i do it ?
To log the name which belongs to the id 2, it's as simple as following :
let obj = dataobj.find(obj => obj.id === 2);
console.log(obj.name);
you can use es6 array syntax:
dataobj.find(el => el.id === 2)
output:
Object {id: 2, name: "Tom"}
You can use the array find method
const secondItem = dataObj.find(function (item){
return item.id === 2;
})
Then name can be accessed as
secondItem.name
You can do something more readable and reusable with a dynamic find
var dataobj = [
{id:1,name:"Jessica"},
{id:2,name:"Tom"},
{id:3,name:"Will"}
];
let getNameFromObjId = obj => id => obj.find(x=> x.id===id).name;
console.log(getNameFromObjId(dataobj)(2))

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.

ES6: Merge two arrays into an array of objects

I have two arrays that I want to merge together to one array of objects...
The first array is of dates (strings):
let metrodates = [
"2008-01",
"2008-02",
"2008-03",..ect
];
The second array is of numbers:
let figures = [
0,
0.555,
0.293,..ect
]
I want to merge them to make an object like this (so the array items match up by their similar index):
let metrodata = [
{data: 0, date: "2008-01"},
{data: 0.555, date: "2008-02"},
{data: 0.293, date: "2008-03"},..ect
];
So far I do this like so: I create an empty array and then loop through one of the first two arrays to get the index number (the first two arrays are the same length)... But is there an easier way (in ES6)?
let metrodata = [];
for(let index in metrodates){
metrodata.push({data: figures[index], date: metrodates[index]});
}
The easiest way is probably to use map and the index provided to the callback
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = metrodates.map((date,i) => ({date, data: figures[i]}));
console.log(output);
Another option is to make a generic zip function which collates your two input arrays into a single array. This is usually called a "zip" because it interlaces the inputs like teeth on a zipper.
const zip = ([x,...xs], [y,...ys]) => {
if (x === undefined || y === undefined)
return [];
else
return [[x,y], ...zip(xs, ys)];
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = zip(metrodates, figures).map(([date, data]) => ({date, data}));
console.log(output);
Another option is to make a generic map function which accepts more than one source array. The mapping function will receive one value from each source list. See Racket's map procedure for more examples of its use.
This answer might seem the most complicated but it is also the most versatile because it accepts any number of source array inputs.
const isEmpty = xs => xs.length === 0;
const head = ([x,...xs]) => x;
const tail = ([x,...xs]) => xs;
const map = (f, ...xxs) => {
let loop = (acc, xxs) => {
if (xxs.some(isEmpty))
return acc;
else
return loop([...acc, f(...xxs.map(head))], xxs.map(tail));
};
return loop([], xxs);
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = map(
(date, data) => ({date, data}),
metrodates,
figures
);
console.log(output);
If you use lodash, you can use zipWith + ES6 shorthand propery names + ES6 Arrow functions for a one-liner, otherwise see #noami's answer.
const metrodata = _.zipWith(figures, metrodates, (data, date)=> ({ data, date }));

Resources