How do I remove duplicate items from observable array based on multiple properties? - rxjs

If I have an array of items like
[
{id: 1, name: 'Sam', gender: 'boy'},
{id: 2, name: 'Mary', gender: 'girl'},
{id: 3, name: 'Sam', gender: 'boy'}
]
Matching on just name and gender, how do I reduce it to the following result?
[
{id: 1, name: 'Sam', type: 'boy'},
{id: 2, name: 'Mary', type: 'girl'}
]

Let try
items$.pipe(map(this.uniqueArray))
uniqueArray(array: any[]): any[] {
return array.filter(
(item, index, self) =>
index === self.findIndex((x) => x.name === item.name)
);
}
https://stackblitz.com/edit/angular-isqjpa?file=src/app/hello.component.ts

Related

Rxjs array of object distinct with clause

I've got these streams
const site = of([
{ name: 'Joe', age: 30, tsUpdate: 5 },
{ name: 'Frank', age: 20, tsUpdate: 4 },
{ name: 'Ryan', age: 50, tsUpdate: 4 }
]);
const db = of([
{ name: 'Jonas', age: 30, tsUpdate: 4 },
{ name: 'Frank', age: 21, tsUpdate: 8 },
{ name: 'Calgor', age: 50, tsUpdate: 4 },
{ name: 'Joe', age: 30, tsUpdate: 4 },
]);
The goal is to have a single array with distinct value for
name but with the highest tsUpdate
So for instance
{ name: 'Joe', age: 30, tsUpdate: 5 }
{ name: 'Joe', age: 30, tsUpdate: 4 }
I want
{ name: 'Joe', age: 30, tsUpdate: 5 }
I am able to have distinct value for name property
but I don't know how to pick the highest value for tsUpdate
https://stackblitz.com/edit/rxjs-mhept2?devtoolsheight=60
This could be a way to solve it:
forkJoin(
[
site,
db
]
).pipe (
map(([site, db]) => [...site, ...db]),
// Emit each item individually
mergeAll(),
// Group by their name
groupBy(o => o.name),
// For each separate group, find the object with the highest `tsUpdate`
mergeMap(
grp$ => grp$.pipe(
reduce((acc, crt) => crt.tsUpdate > acc.tsUpdate ? crt : acc),
),
),
// At the end, collect all the objects in an array
toArray(),
).subscribe(console.log);
A groupBy's group is a Subject, and that's why we can use mergeMap afterwards. That subject will emit whenever it finds an object that matches with its key.
StackBlitz.

Why do aggregating a filtered dataset lose the filters of it?

I have this collection:
// collection
[
{_id: 1, name: 'Luigi', childs: [{name: 'one'}, {name: 'two'}], dad_id: 9]},
{_id: 1, name: 'Mario', childs: [{name: 'four'}, {name: 'five'}], dad_id: 8]},
{_id: 1, name: 'Alessandro', childs: [{name: 'seven'}, {name: 'six'}], dad_id: 9]},
]
and apply this filter to it
result = collection.find({ dad_id: 9 })
Then I want to aggregate the results and get all the childs singularly, I start with unwinding them
(then I 'll make a projection, etc..) but I already encounter a behavior that I do not understand:
the result contains also the documents with dad_id is 8, even if they were already excluded by my query.
result.aggregate([
{ "$unwind"=> "$childs" },
]).each do |e| ... end
// => [
{_id: 1, name: 'Luigi', childs: {name: 'one'}, dad_id: 9]},
{_id: 1, name: 'Luigi', childs: {name: 'two'}, dad_id: 9]},
{_id: 1, name: 'Luigi', childs: {name: 'five'}, dad_id: 8]},
{_id: 1, name: 'Luigi', childs: {name: 'four'}, dad_id: 8]},
{_id: 1, name: 'Luigi', childs: {name: 'seven'}, dad_id: 9]},
{_id: 1, name: 'Luigi', childs: {name: 'six'}, dad_id: 9]},
]
What am I missing?
You can not chain input from one query to another query like that.
Either use search query ex. Model.find(id) or aggregation framework.
Aggregation framework provides you the functionality to create a pipeline (ex. match,unwind,lookup,project).
To utilize mongodb indexing always try to use "$match" first in the pipeline
match = { "$match" => { "dad_id" =>9} }
unwind = {"$uwind"=>"$childs"}
pipeline = [match,unwind]
collection.aggregate(pipeline).each do |obj|
end

How to use .uniq with hashes for unique pairs?

Lets say I have an array with several hashes of emails and names. For example I have something like this:
foo = [{id: 1, name: 'Eric Cartman', email: 'eric#southpark.com'},
{id: 2, name: 'Eric Cartman', email: 'cartmanfamily#gmail.com'},
{id: 3, name: "Cartman's mom", email: 'cartmanfamily#gmail.com'},
{id: 4, name: 'Eric Cartman', email: 'eric#southpark.com'}]
How can I use .uniq to return unique values based on the combination of name and email? For example I want to return something like this:
[{id: 1, name: 'Eric Cartman', email: 'eric#southpark.com'},
{id: 2, name: 'Eric Cartman', email: 'cartmanfamily#gmail.com'},
{id: 3, name: "Cartman's mom", email: 'cartmanfamily#gmail.com'}]
foo.uniq should work just fine.
Since
{name: "cartman", email: "cartman#sp.com"} == {name: "cartman", email: "cartman#sp.com"} # => True
{name: "stan", email: "stan#sp.com"} == {name: "cartman", email: "cartman#sp.com"} # => False
The == operator check if every field of the hash have the same values. So .uniq will work how you want it to work!
If there is more than only the email and name field you should use the uniq method with a block:
foo.uniq { |x| [x[:name], x[:email]] }
It will keep only the uniq combination of the name and email.
Hope it helped, happy ruby coding!
Array#uniq takes a block:
foo = [{id: 1, name: 'Eric Cartman', email: 'eric#southpark.com'},
{id: 2, name: 'Eric Cartman', email: 'cartmanfamily#gmail.com'},
{id: 3, name: "Cartman's mom", email: 'cartmanfamily#gmail.com'},
{id: 4, name: 'Eric Cartman', email: 'eric#southpark.com'}]
bar = foo.uniq {|h| [h[:name], h[:email]] }
bar == [{id: 1, name: 'Eric Cartman', email: 'eric#southpark.com'},
{id: 2, name: 'Eric Cartman', email: 'cartmanfamily#gmail.com'},
{id: 3, name: "Cartman's mom", email: 'cartmanfamily#gmail.com'}] #=> true
Per the documentation, "If a block is given, it will use the return value of the block for comparison."

Create nested hash RUBY

i have an array of hashes, eg
array = [
{ id: 1, name: 'root' parent: null},
{ id: 2, name: 'first' parent: 1},
{ id: 5, name: 'first step' parent: 2},
{ id: 6, name: 'second step' parent: 2},
{ id: 3, name: 'second' parent: 1},
{ id: 7, name: 'first step' parent: 3},
{ id: 4, name: 'third' parent: 1},
{ id: 2, name: 'first' parent: 1},
]
and i need to build something like that
hash = {
{
id: 1,
name: 'root',
parent: null,
childrens: [
{ id: 2,
name: 'first',
parent: 1,
childrens: [
{
id: 5,
name: 'first step',
parent: 2
},
{
id: 6,
name: 'second step',
parent: 2
},
]},
...
}
I am newbie at ruby and doesnot understand how to do this.
Probably i need to use recursive functions? Or not?
# Put all your nodes into a Hash keyed by id This assumes your objects are already Hashes
object_hash = nodes.index_by {|node| node[:id]}
object_hash[0] = {:root => true}
# loop through each node, assigning them to their parents
object_hash.each_value {|node|
next if node[:root]
children = object_hash[node[:parent_id]][:children] ||= []
children << node
}
#then your should have the structure you want and you can ignore 'object_hash' variable
tree = object_hash[0]
From the answer:
Algorithm for parsing a flat tree into a non-flat tree

Ruby map specific hash keys to new one

I've got an array full of hashes of which I want to combine specific keys to a new one, e.g.
[{ firstname: 'john', lastname: 'doe', something: 'else', key: ... }, { firstname: 'Joe', lastname: 'something', something: 'bla', key:... }]
should become
[{ name: 'john doe' },{ name: 'Joe something' }]
Please note: there are more keys in the hash as first and lastname. Is there a common ruby method to do this? Thanks!
Just do as
array = [{ firstname: 'john', lastname: 'doe' }, { firstname: 'Joe', lastname: 'something' }]
array.map { |h| { :name => h.values_at(:firstname, :lastname) * " " } }
# => [{:name=>"john doe"}, {:name=>"Joe something"}]
Read this Hash#values_at and Array#* .
This is:
a = [{ firstname: 'john', lastname: 'doe' }, { firstname: 'Joe', lastname: 'something' }]
a.map { |n| { name: n.values.join(' ') } }
# => [{:name=>"john doe"}, {:name=>"Joe something"}]

Resources