Javascript/Lodash - Getting unique values in an Array of arrays - javascript

I am having a complicated case where I need to have unique values in an array of arrays.
here is what I have right now :
var ar = [{
"theFirstArray": [
[{
"no": 1
}, {
"no": 2
}, {
"no": 3
}, {
"no": 4
}, {
"no": 11
}]
],
"theSecondArray": [{
"no": 3
},
{
"no": 1
},
{
"no": 4
},
{
"no": 9
},
{
"no": 39
},
{
"no": 18
},
{
"no": 19
}
],
"theThirdArray": [{
"no": 20
}, {
"no": 12
}, {
"no": 10
}, {
"no": 9
}, {
"no": 16
}]
}]
What I need to achieve :
[{
"theFirstArray":[{"no":1},{"no":2},{"no":3},{"no":4}]
},{
"theSecondArray":[{"no":9}, {"no":39}, {"no":18}, {"no":19}]
},{
"theThirdArray":[{"no":20},{"no":12},{"no":10},{"no":16}]
}]
PS. note that I need only to output 4 values in each subarray, and also the value is not repeated in all the array.
I've already tried the following:
_.uniqBy(ar, 'no');
It's true that I got only the unique values in the result; however, I was not able to get the same wanted structure as I got the following:
[{
theFirstArray:[{"no":1,"no":2,"no":3,"no":4, "no":9, "no":39, "no":18, "no":19, "no":20,"no":12,"no":10,"no":16}]
}]
I've also tried to work with _.map but that didn't work to!

You can use something like this:
function getUniqueArrays(arr) {
var uniqAr = [];
var allArr = [{
no: null
}];
arr.forEach(function(item) {
var itemKey = Object.keys(item)[0];
var itemArr = _.uniqBy(item[itemKey], 'no');
var itemArr = _.differenceBy(itemArr, allArr, 'no').slice(0, 4);
allArr = itemArr.concat(allArr);
var newItem = {};
newItem[itemKey] = itemArr;
uniqAr.push(newItem);
})
return uniqAr;
}
From your example I presume, you always has only one key in object.
If you always have unique values in every array (that one that contain objects like {no: 1}), than you can get rid off line:
var itemArr = _.uniqBy(item[itemKey], 'no');

Lodash all the things! You can use reduce to store already seen values and cleanup them with uniqBy, and get only needed ones with differenceBy for each loop cycle.
Here is how it could look:
_.map(items, item => _.reduce(
_.toPairs(item),
({ seenValues, result }, [key, values]) => ({
seenValues: _.uniqBy([...seenValues, ...values], 'no'),
result: {
...result,
[key]: _.take(_.differenceBy(values, seenValues, 'no'), 4)
}
}),
{ seenValues: [], result: {} }
));
We use toPairs to have that unpacked key and value in reduce function, so we can costruct needed structure back.
At the end you can just get the result property. Check this jsbin: http://jsbin.com/tikoyehapi/1/edit?js,console for more.

Related

Generating a Nested Set Model from a POJO

I have been playing around with some Nested Set Models (NSM). One thing I wanted to do is to be able to generate a NSM from a given JavaScript object.
For example, given the following object:
var data = {
Clothes: {
Jackets: {
Waterproof: true,
Insulated: true
},
Hats: true,
Socks: true
},
}
I'd like to generate an array of objects like so.
[
{
"name": "Clothes",
"lft": 1,
"rgt": 12
},
{
"name": "Jackets",
"lft": 2,
"rgt": 7
},
{
"name": "Waterproof",
"lft": 3,
"rgt": 4
},
{
"name": "Insulated",
"lft": 5,
"rgt": 6
},
{
"name": "Hats",
"lft": 8,
"rgt": 9
},
{
"name": "Socks",
"lft": 10,
"rgt": 11
}
]
That is - a depth first walk through the object, assigning an ID and counting the left and right edge for each object in the hierarchy. So that each node has a unique ID and the correct lft and rgt values for a NSM.
I've tried various approaches but just can't seem to get the result I am after...I had some success by altering the model to use properties for the node name and child nodes - i.e.
var data2 = {
name: "Clothes",
children: [{
name: "Jackets",
children: [{
name: "Waterproof",
}, {
name: "Insulated"
}]
}, {
name: "Hats"
},
{
name: "Socks"
}
]
};
function nestedSet(o, c, l = 0) {
let n = {
name: o.name,
lft: l + 1
};
c.push(n);
let r = n.lft;
for (var x in o.children) {
r = nestedSet(o.children[x], c, r);
}
n.rgt = r + 1;
return n.rgt;
}
let out = [];
nestedSet(data2, out);
console.log(out)
This gives the correct result but requires altering the input data...is there a way to generate the same Nested Set Model using the original data object?
I actually managed to solve this in the end...I just forgot about it for a long while! Basically all that is required is to reclusively pass the Object.entries as kindly suggested in #CherryDT's comment. This way one can resolve the name/children to build the nested set model as required.
var data = {
Clothes: {
Jackets: {
Waterproof: {},
Insulated: {},
},
Hats: {},
Socks: {},
},
};
function ns(node, stack = [], lft = 0) {
var rgt = ++lft;
var item = {
name: node[0],
lft: lft,
};
stack.push(item);
Object.entries(node[1]).forEach(function (c) {
rgt = ns(c, stack, rgt);
});
item.rgt = ++rgt;
return rgt;
}
var result = [];
ns(Object.entries(data)[0], result);
console.log(result);

Get all the objects from state which is having multiple arrays

Actually I have a state data which is an object , It has following structutre,
{ one : [ { abc:1 }, { abc: 2 }], two : [ { abc:3 }, { abc: 4 }, three : [ { abc:5 }, { abc: 6 }]] }
So its like an arry of objects in an state object .
Now, I want to create an array of objects which will have all these objects .
So I want to have it like,
[{ abc:1 }, { abc: 2 },{ abc:3 }, { abc: 4 },{ abc:5 }, { abc: 6 }]
The way I tried is using for loop.
let quizCriteriaObj = [];
let low = this.props.lowQuizData["Low"];
let High = this.props.lowQuizData["High"];
let Medium = this.props.lowQuizData["Medium"];
console.log("data is ", low);
for (let i = 0; i <= low.length - 1; i++) {
quizCriteriaObj.push(low[i]);
}
for (let i = 0; i <= High.length - 1; i++) {
quizCriteriaObj.push(High[i]);
}
for (let i = 0; i <= Medium.length - 1; i++) {
quizCriteriaObj.push(Medium[i]);
}
console.log(quizCriteriaObj);
I have taken each field aside from that object and using a for loop on every field. SO, It is working for me. But , I think this is not a proper solution for me .Is there any thing That I am doing wrong ?
Modern javascript makes this trivial
Array.prototype.flat
Please note: Array.prototype.flat is a stage 3 TC39 proposal, so is not part of the ECMAScript specification (yet)
It is supported in all modern browsers (and can be polyfilled for Microsofts attempts at browsers, both Internet Explorer and Edgey)
Note: I assume you mistyped the "source" object, because as it was, it was invalid
let obj = {
one : [
{ abc:1 },
{ abc: 2 }
],
two : [
{ abc:3 },
{ abc: 4 }
],
three : [
{ abc:5 },
{ abc: 6 }
]
}
let ftw = Object.values(obj).flat(); //<== single line of code is all you need
console.log(JSON.stringify(ftw))
You can use Object.values to convert the object into an array. Use spread syntax and concat() to flatten the array
var obj ={"one":[{"abc":1},{"abc":2}],"two":[{"abc":3},{"abc":4}],"three":[{"abc":5},{"abc":6}]}
var result = [].concat(...Object.values(obj));
console.log(result);
You can use a for...in loop
let obj = { one : [ { abc:1 }, { abc: 2 }], two : [ { abc:3 }, { abc: 4 }], three : [ { abc:5 }, { abc: 6 }] };
let result = [];
for (let key in obj) result = [...result, ...obj[key]];
console.log(result);
Using lodash is simple:
const obj = { one : [ { abc:1 }, { abc: 2 }], two : [ { abc:3 }, { abc: 4 }, three : [ { abc:5 }, { abc: 6 }]] }
_.flatten(obj)
// return => [{ abc:1 }, { abc: 2 },{ abc:3 }, { abc: 4 },{ abc:5 }, { abc: 6 }]
Good question you can't simply spread the arrays so a good one liner
for this would be.
state = {
one : [ { abc:1 }, { abc: 2 }],
two : [ { abc:3 }, { abc: 4 }],
three : [ { abc:5 }, { abc: 6 }]
}
var result = Object.values(state).flat() // solution
console.log(result)

get at least two matches from json

I have this JSON
{
"people": [{
"games": [
"destiny",
"horizon",
"fifa",
"cuphead"
],
"name": "Cartman"
},
{
"games": [
"fifa",
"pes"
],
"name": "Kyle"
},
{
"games": [
"cuphead",
"destiny"
],
"name": "Stan"
},
{
"games": [
"pokemon",
"metroid"
],
"name": "Clyde"
},
{
"games": [
"fifa",
"metroid",
"pes"
],
"name": "Butters"
}
]
}
and I need to get the users that have at least two games in common so for this JSON the result will be:
Cartman and Stan plays destiny and cuphead
Kyle and Butters plays fifa and pes
Note: Clyde will be not in the results because theres only one game match with other player
I thought about two workarounds
1- have a dictionary using two tags (covering all possibilities) as a key and filled it if it doesnt exist and after other key match that way I will know that theres already two matches
2- get the first user compare it against the others and it if matches at least two printed it then continue with the next one and compare with the rest and so on
Any help will be appreciated thanks
An approach with reducing the games/peoples by checking if only one exists and delet then the respondend part of the other object.
It returns more items than one, but more than two.
For getting a result, you could eliminate the one with two parts and adjust the rest.
var object = { people: [{ games: ["destiny", "horizon", "fifa", "cuphead"], name: "Cartman" }, { games: ["fifa", "pes"], name: "Kyle" }, { games: ["cuphead", "destiny"], name: "Stan" }, { games: ["pokemon", "metroid"], name: "Clyde" }, { games: ["fifa", "metroid", "pes"], name: "Butters" }] },
peoples = {},
games = {},
change = false;
object.people.forEach(function (person) {
person.games.forEach(function (game) {
games[game] = games[game] || {};
games[game][person.name] = true;
peoples[person.name] = peoples[person.name] || {};
peoples[person.name][game] = true;
});
});
do {
change = false;
Object.keys(games).forEach(function (k) {
var keys = Object.keys(games[k]);
if (keys.length === 1) {
delete peoples[keys[0]][k];
delete games[k];
change = true;
}
});
Object.keys(peoples).forEach(function (k) {
var keys = Object.keys(peoples[k]);
if (keys.length === 1) {
delete games[keys[0]][k];
delete peoples[k];
change = true;
return;
}
});
} while (change);
console.log(peoples);
console.log(games);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Remove duplicate object from array javascript [duplicate]

I have this kind of array:
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
I'd like to filter it to have:
var bar = [ { "a" : "1" }, { "b" : "2" }];
I tried using _.uniq, but I guess because { "a" : "1" } is not equal to itself, it doesn't work. Is there any way to provide underscore uniq with an overriden equals function?
.uniq/.unique accepts a callback
var list = [{a:1,b:5},{a:1,c:5},{a:2},{a:3},{a:4},{a:3},{a:2}];
var uniqueList = _.uniq(list, function(item, key, a) {
return item.a;
});
// uniqueList = [Object {a=1, b=5}, Object {a=2}, Object {a=3}, Object {a=4}]
Notes:
Callback return value used for comparison
First comparison object with unique return value used as unique
underscorejs.org demonstrates no callback usage
lodash.com shows usage
Another example :
using the callback to extract car makes, colors from a list
If you're looking to remove duplicates based on an id you could do something like this:
var res = [
{id: 1, content: 'heeey'},
{id: 2, content: 'woah'},
{id: 1, content:'foo'},
{id: 1, content: 'heeey'},
];
var uniques = _.map(_.groupBy(res,function(doc){
return doc.id;
}),function(grouped){
return grouped[0];
});
//uniques
//[{id: 1, content: 'heeey'},{id: 2, content: 'woah'}]
Implementation of Shiplu's answer.
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var x = _.uniq( _.collect( foo, function( x ){
return JSON.stringify( x );
}));
console.log( x ); // returns [ { "a" : "1" }, { "b" : "2" } ]
When I have an attribute id, this is my preffered way in underscore:
var x = [{i:2}, {i:2, x:42}, {i:4}, {i:3}];
_.chain(x).indexBy("i").values().value();
// > [{i:2, x:42}, {i:4}, {i:3}]
Using underscore unique lib following is working for me, I m making list unique on the based of _id then returning String value of _id:
var uniqueEntities = _.uniq(entities, function (item, key, a) {
return item._id.toString();
});
Here is a simple solution, which uses a deep object comparison to check for duplicates (without resorting to converting to JSON, which is inefficient and hacky)
var newArr = _.filter(oldArr, function (element, index) {
// tests if the element has a duplicate in the rest of the array
for(index += 1; index < oldArr.length; index += 1) {
if (_.isEqual(element, oldArr[index])) {
return false;
}
}
return true;
});
It filters out all elements if they have a duplicate later in the array - such that the last duplicate element is kept.
The testing for a duplicate uses _.isEqual which performs an optimised deep comparison between the two objects see the underscore isEqual documentation for more info.
edit: updated to use _.filter which is a cleaner approach
The lodash 4.6.1 docs have this as an example for object key equality:
_.uniqWith(objects, _.isEqual);
https://lodash.com/docs#uniqWith
Try iterator function
For example you can return first element
x = [['a',1],['b',2],['a',1]]
_.uniq(x,false,function(i){
return i[0] //'a','b'
})
=> [['a',1],['b',2]]
here's my solution (coffeescript) :
_.mixin
deepUniq: (coll) ->
result = []
remove_first_el_duplicates = (coll2) ->
rest = _.rest(coll2)
first = _.first(coll2)
result.push first
equalsFirst = (el) -> _.isEqual(el,first)
newColl = _.reject rest, equalsFirst
unless _.isEmpty newColl
remove_first_el_duplicates newColl
remove_first_el_duplicates(coll)
result
example:
_.deepUniq([ {a:1,b:12}, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ],[ 2, 1, 2, 1 ], {a:1,b:12} ])
//=> [ { a: 1, b: 12 }, [ 2, 1, 2, 1 ], [ 1, 2, 1, 2 ] ]
with underscore i had to use String() in the iteratee function
function isUniq(item) {
return String(item.user);
}
var myUniqArray = _.uniq(myArray, isUniq);
I wanted to solve this simple solution in a straightforward way of writing, with a little bit of a pain of computational expenses... but isn't it a trivial solution with a minimum variable definition, is it?
function uniq(ArrayObjects){
var out = []
ArrayObjects.map(obj => {
if(_.every(out, outobj => !_.isEqual(obj, outobj))) out.push(obj)
})
return out
}
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) {
return JSON.stringify(f);
}), function (gr) {
return gr[0];
}
);
Lets break this down. First lets group the array items by their stringified value
var grouped = _.groupBy(foo, function (f) {
return JSON.stringify(f);
});
grouped looks like:
{
'{ "a" : "1" }' = [ { "a" : "1" } { "a" : "1" } ],
'{ "b" : "2" }' = [ { "b" : "2" } ]
}
Then lets grab the first element from each group
var bar = _.map(grouped, function(gr)
return gr[0];
});
bar looks like:
[ { "a" : "1" }, { "b" : "2" } ]
Put it all together:
var foo = [ { "a" : "1" }, { "b" : "2" }, { "a" : "1" } ];
var bar = _.map(_.groupBy(foo, function (f) {
return JSON.stringify(f);
}), function (gr) {
return gr[0];
}
);
You can do it in a shorthand as:
_.uniq(foo, 'a')

Finding matching objects in an array of objects?

var set = [{"color":"blue"},{"color":"green"},{"color":"red"},{"color":"green"}];
I'd like to be able to do something like a db call, set.find({"color":"green"}) and have it return an array full of objects that contain that property.
Using Array#filter, for this particular case the code would look like
var results = set.filter(function (entry) { return entry.color === "green"; });
Array#filter is not implemented in some older browsers, so see the linked article for a backward compatibility shim, or better yet get a full-fledged ES5 shim.
For the more general case, it's just a matter of extending this idea:
function findByMatchingProperties(set, properties) {
return set.filter(function (entry) {
return Object.keys(properties).every(function (key) {
return entry[key] === properties[key];
});
});
}
var results = findByMatchingProperties(set, { color: "green" });
Again, I am using ECMAScript 5 methods Object.keys and Array#every, so use an ES5 shim. (The code is doable without an ES5 shim but uses manual loops and is much less fun to write and read.)
I have used map function from jquery and I am getting selected index by passing searched key value so by using that index we will get required object from array.
var mydata = [{ name: "Ram", Id: 1 }, { name: "Shyam", Id: 2 }, { name: "Akhil", Id: 3 }];
searchKey = 2
var mydata = [{ name: "Ram", Id: 1 }, { name: "Shyam", Id: 2 }, { name: "Akhil", Id: 3 }];
searchKey = 2
var selectedData = mydata[mydata.map(function (item) { return item.Id; }).indexOf(searchKey)];
console.log(selectedData)
var selectedData = mydata[mydata.map(function (item) { return item.Id; }).indexOf(searchKey)];
console.log(selectedData)
output
{ name: "Shyam", Id: 2 }
Note: if you want to pass search key as object then
searchKey = { Id: 2 };
mydata[mydata.map(function (item) { return item.Id; }).indexOf(searchKey.Id)];
output
{ name: "Shyam", Id: 2 }
Using arrow functions with an implied return and concise body:
const results = set.filter(entry => entry.color === "green");
Another example passing in a search variable:
const searchString = 'green';
const results = set.filter(entry => entry.color === `${searchString}`);
Read more about arrow functions on
MDN
Since you've included the jQuery tag, here's one way to do it using jQuery's map:
var results = $.map( set, function(e,i){
if( e.color === 'green' ) return e;
});
The documentation states that you need to return null to remove the element from the array, but apparently this is false, as shown by the jsFiddle in the comments; returning nothing (i.e. returning undefined) works just as well.
I went with a different approach that I found to be a bit easier.
function isObjEqual(a, b) {
const x = JSON.stringify(a);
const y = JSON.stringify(b);
return x === y;
}
// Example 1
const set = [{"color":"blue"},{"color":"green"},{"color":"red"},{"color":"green"}];
const findObj1 = {"color":"green"};
const arr1 = set.filter((objInArr) => isObjEqual(objInArr, findObj1));
console.log(arr1) // [ { color: 'green' }, { color: 'green' } ]
// Example 2
const list = [{
"label": "Option 2",
"value": "option2"
},
{
"label": "Option 3",
"value": "option3"
},
{
"label": "Option 2",
"value": "option2"
}
];
const findObj2 = {
"label": "Option 2",
"value": "option2"
}
const newList = list.filter((objInArr) => isObjEqual(objInArr, findObj2));
console.log(newList) //[ { label: 'Option 2', value: 'option2' }, { label: 'Option 2', value: 'option2' } ]

Categories

Resources