Combining array with condition in javascript - javascript

I have an array as below
arr1 = [{
'Name': 'Ken',
'ProjectId': '123'
}, {
'Name': 'Kris',
'ProjectId': '223'
}, {
'Name': 'Ken',
'ProjectId': '223'
}, {
'Name': 'Ben',
'ProjectId': '229'
}, {
'Name': 'Alex',
'ProjectId': '222'
}, {
'Name': 'Kris',
'ProjectId': '786'
}]
I want the resulting array where duplicates from array based on name is removed but if the ProjectId = 123 keep that in array and remove other duplicate
i.e in above example there are 2 duplicates with name Ken but one record has ProjectId = 123 , in that case consider this record and delete other similar name duplicate
Also , if the duplicate does not contain ProjectId as 123 remove any one duplicate.
Ex : In above example there are 2 duplicates with name Kris but don't have ProjectId as 123 in that case consider any one of the record.
i.e my resulting array should be like this
[{
'Name': 'Ken',
'ProjectId': '123'
}, {
'Name': 'Kris',
'ProjectId': '223'
}, {
'Name': 'Ben',
'ProjectId': '229'
}, {
'Name': 'Alex',
'ProjectId': '222'
}]
or
[{
'Name': 'Ken',
'ProjectId': '123'
}, {
'Name': 'Ben',
'ProjectId': '229'
}, {
'Name': 'Alex',
'ProjectId': '222'
}, {
'Name': 'Kris',
'ProjectId': '786'
}]

function removeDuplicatesByName(arr) {
var obj = {};
for(var i = 0; i < arr.length; i++) {
obj[arr[i].Name] = arr[i];
}
var out = [];
for(var key in obj)
out.push(obj[key]);
out.sort(function(a, b){ return a.ProjectId - b.ProjectId; });
return out;
}
This uses the fact that objects can't have duplicate keys to remove all the duplicates in your array by name. It returns them sorted by ProjectId, since the ordering will be lost when it goes into an object.

JSFIDDLE DEMO
This should work. :)
var arr1 = [{
'Name': 'Ken',
'ProjectId': '123'
}, {
'Name': 'Kris',
'ProjectId': '223'
}, {
'Name': 'Ken',
'ProjectId': '223'
}, {
'Name': 'Ben',
'ProjectId': '229'
}, {
'Name': 'Alex',
'ProjectId': '222'
}, {
'Name': 'Kris',
'ProjectId': '786'
}];
var removeDups = function (array) {
for (var i=0; i < array.length; i++) {
var obj1 = array[i];
for (var j=0; j < array.length; j++) {
if (j==i)
continue;
var obj2 = array[j];
if (obj1.Name == obj2.Name) {
var indexToDelete = checkProject123(i, j);
array.splice(indexToDelete-1, indexToDelete);
}
}
}
return array;
}
var checkProject123 = function (i, j) {
if (arr1[i].ProjectId == '123')
return j; // Index i has projectId 123 so return j to be deleted.
else if (arr1[j].ProjectId == '123')
return i; // Index j has projectId 123 so return i to be deleted.
else
return i; // Neither of them have projectId 123 so delete the first one, idk
}
console.log(removeDups(arr1));

Working solution:
var arr1 = [{
'Name': 'Ken',
'ProjectId': '123'
}, {
'Name': 'Kris',
'ProjectId': '223'
}, {
'Name': 'Ken',
'ProjectId': '223'
}, {
'Name': 'Ben',
'ProjectId': '229'
}, {
'Name': 'Alex',
'ProjectId': '222'
}, {
'Name': 'Kris',
'ProjectId': '786'
}];
function filter(arr) {
var i, j;
for (i = 0; i < arr.length; i++) {
for (j = i; j < arr.length; j++) {
if (i === j) { continue; }
if (arr[j].Name === arr[i].Name) {
var spliceIndex = (arr[j].ProjectId === '123') ? i : j;
arr.splice(spliceIndex, 1);
j--;
if (spliceIndex === i) {
i--;
break;
}
}
}
}
return arr;
}
console.log(filter(arr1));

This is one solution. You can filter your input array and search if the current element position is the same that the first result in the input array to eliminate the duplicates.
var arr1 = [{
'Name': 'Ken',
'ProjectId': '123'
}, {
'Name': 'Kris',
'ProjectId': '223'
}, {
'Name': 'Ken',
'ProjectId': '223'
}, {
'Name': 'Ben',
'ProjectId': '229'
}, {
'Name': 'Alex',
'ProjectId': '222'
}, {
'Name': 'Kris',
'ProjectId': '786'
}];
var result=arr1.filter(function(e,i,a){
if (a.map(function(d) { return d['Name']; }).indexOf(e.Name) == i) {
return e;
}
})
console.log(result);

Use lodash's uniq function. Pass in a custom comparator. Example code from their documentation:
// using an iteratee function
_.uniq([1, 2.5, 1.5, 2], function(n) {
return this.floor(n);
}, Math);
// → [1, 2.5]
Or the pluck shorthand:
// using the `_.property` callback shorthand
_.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
I believe underscore.js has the same function.

Related

Removing items from a nested array with unknown depth

I am trying to remove items from a nested array based on an array of correct matches.
Three requirements apply:
The depth of the array is unknown. Items can have nested children.
Only items without children should be removed
The items should be removed if they are not in the matching array
I have build a function to recursively get to the deepest level and filter the items based on the $match array.
This is what my code looks like so far:
import * as lodash from "https://cdn.skypack.dev/lodash#4.17.21";
let filterRecursively = (arr, match) => {
// Recursively go to the deepest array we can find
arr.forEach(el => {
arr = el.children ? filterRecursively(el.children, match) : arr
});
// If we are at the deepest level we filter the items ...
if (arr[0] && arr[0].children === undefined) {
return _.filter(arr, (item) => {
return match.includes(item.name)
})
} else { // ... if not we just return the array as-is
return arr
}
}
let arr = [
{
'name': 'John',
'children': [
{
'name': 'John',
'children': [
{ 'name': 'John' },
{ 'name': 'Jane' },
{ 'name': 'Joe' }
]
}]
}, {
'name': 'Jeff',
'children': [
{
'name': 'Joe',
'children': [
{ 'name': 'Jill' },
{ 'name': 'Jeff' },
{ 'name': 'Joe' }
]
}]
}];
let match = ['John', 'Joe'];
let result = filterRecursively(arr, match);
console.log(result);
// Expected result:
[
{
'name': 'John',
'children': [
{
'name': 'John',
'children': [
{ 'name': 'John' },
{ 'name': 'Joe' }
]
}]
}, {
'name': 'Jeff',
'children': [
{
'name': 'Joe',
'children': [
{ 'name': 'Joe' }
]
}]
}];
// Current output
[
{
"name": "Joe"
}
]
See the Codepen
Because the forEach basically "skips" layers without returning anything, you end up with just your first and deepest result.
I also think your function is a bit more complicated because it starts with an array, rather than a sort of ROOT node.
Here's an alternative that (I think) meets your requirements:
let childlessMatch = (node, match) => {
// If it's at the deepest level, check against match
if (node.children === undefined) {
return match.includes(node.name) ? [node] : [];
}
// If not, calculate the next child layer first
const newChildren = node.children.flatMap(c => childlessMatch(c, match));
// With the children calculated, we can prune based on whether there
// are any children left to show
if (newChildren.length === 0) return [];
return [{
...node,
children: newChildren
}]
}
In a runnable snippet:
let childlessMatch = (node, match) => {
if (node.children === undefined) {
return match.includes(node.name) ? [node] : [];
}
const newChildren = node.children.flatMap(c => childlessMatch(c, match));
if (newChildren.length === 0) return [];
return {
...node,
children: newChildren
}
}
let arr = [
{
'name': 'John',
'children': [
{
'name': 'John',
'children': [
{ 'name': 'John' },
{ 'name': 'Jane' },
{ 'name': 'Joe' }
]
}]
}, {
'name': 'Jeff',
'children': [
{
'name': 'Joe',
'children': [
{ 'name': 'Jill' },
{ 'name': 'Jeff' },
{ 'name': 'Joe' }
]
}]
}];
let match = ['John', 'Joe'];
let result = childlessMatch({ children: arr }, match).children;
console.log(result);
I think it's better to separate out a generic node filtering technique that handles children appropriately from the code that checks the names. Here filterNodes accepts a predicate that says whether the node should be included (without worrying about children). It then does the child handling bit.
We write our main function by just passing a predicate that tests whether the name is on the allowed list.
Together, it looks like this:
const filterNodes = (pred) => (nodes) =>
nodes .flatMap (
(node, _, __,
kids = filterNodes (pred) (node .children || [])
) => pred (node) || node .children ?.length > 0
? [{...node, ... (kids .length ? {children: kids} : {})}]
: []
)
const removeUnmatchedNames = (names) =>
filterNodes (({name}) => names .includes (name))
const arr = [{name: "John", children: [{name: "John", children: [{name: "John"}, {name: "Jane"}, {name: "Joe"}]}]}, {name: "Jeff", children: [{name: "Joe", children: [{name: "Jill"}, {name: "Jeff"}, {name: "Joe"}]}]}]
console .log (removeUnmatchedNames (['John', 'Joe']) (arr))
.as-console-wrapper {max-height: 100% !important; top: 0}
let filterRecursively = (arr, match) => {
// Recursively go to the deepest array we can find
return arr
.map((el) =>
el.children
? { ...el, children: filterRecursively(el.children, match) }
: el
)
.filter((el) => el.children || match.includes(el.name));
};
I have updated filterRecursively.
let filterRecursively = (arr, match) => {
// Recursively go to the deepest array we can find
return arr
.map((el) =>
el.children
? { ...el, children: filterRecursively(el.children, match) }
: el
)
.filter((el) => el.children || match.includes(el.name));
};
let arr = [
{
name: "John",
children: [
{
name: "John",
children: [{ name: "John" }, { name: "Jane" }, { name: "Joe" }],
},
],
},
{
name: "Jeff",
children: [
{
name: "Joe",
children: [{ name: "Jill" }, { name: "Jeff" }, { name: "Joe" }],
},
],
},
];
let match = ["John", "Joe"];
let result = filterRecursively(arr, match);
console.log(JSON.stringify(result));
// Expected result:
// [
// {
// 'name': 'John',
// 'children': [
// {
// 'name': 'John',
// 'children': [
// { 'name': 'John' },
// { 'name': 'Joe' }
// ]
// }]
// }, {
// 'name': 'Jeff',
// 'children': [
// {
// 'name': 'Joe',
// 'children': [
// { 'name': 'Joe' }
// ]
// }]
// }];

Sort big array into smaller by value in objects

I have large array, which looks like this:
let arr = [{
'name': '1',
'val': '12'
},{
'name': '4',
'val': '52'
},
{
'name': '11',
'val': '15'
},
{
'name': '4',
'val': '33'
},
...]
I want find all objects with same name values and push them into separated arrays .
P.S. I don't know possible name values in array.
You can easily group by name to get what you need. Below is a quite general function to group any array of objects to a Map, by specified property. Then simply make it an array and you're done
function group(arr, propertyToGroupBy) {
return arr.reduce(function(a, b) {
return a.set(b[propertyToGroupBy], (a.get(b[propertyToGroupBy]) || []).concat(b));
}, new Map);
}
const map = group(arr, 'name');
console.log(Array.from(map));
<script>
let arr = [{
'name': '1',
'val': '12'
}, {
'name': '4',
'val': '52'
},
{
'name': '11',
'val': '15'
},
{
'name': '4',
'val': '33'
}
];
</script>
You can group your array based on name in an object accumulator. Then using Object.values() you can get all the values.
let arr = [{ 'name': '1', 'val': '12' },{ 'name': '4', 'val': '52' }, { 'name': '11', 'val': '15' }, { 'name': '4', 'val': '33' }],
result = Object.values(arr.reduce((r,o) => {
r[o.name] = r[o.name] || [];
r[o.name].push(o);
return r;
},{}));
console.log(result);
let arr=[{'name':'1','val':'12'},{'name':'4','val':'52'},{'name':'11','val':'15'},{'name':'4','val':'33'}];
let groups = arr.reduce((obj, el) => ({...obj, [el.name]: [...obj[el.name] || [], el] }), {});
console.log(groups);
Group with reduce and map out the output to be an array:
let arr = [{'name': '1','val': '12'},{'name': '4','val': '52'}, {'name': '11','val': '15'},{'name': '4','val': '33'}];
// group object by same name
const dataObj = arr.reduce((all, {name, val}) => {
if (!all.hasOwnProperty(name)) all[name] = [];
all[name].push({name, val});
return all;
}, {});
// map out the result into an array
const result = Object.keys(dataObj).map(k => (dataObj[k]));
console.log(result);

Why it didnot delete the object from the array base on if statement?

Below if my code
var data = [
{
'Number': 'B20051209003301',
'Name': 'GGG'
}, {
'Number': 'A20051130027901',
'Name': 'BBB'
}, {
'Number': 'H20170206000056',
'Name': 'CCC'
}, {
'Number': 'K20051216000301',
'Name': 'AAA'
}, {
'Number': 'F20170120000056',
'Name': 'DDD'
}, {
'Number': 'A20051020032201',
'Name': 'EEE'
}, {
'Number': 'A20071005006001',
'Name': 'AAA'
}, {
'Number': 'D20170126000023',
'Name': 'AAA'
}, {
'Number': 'A20051020016601',
'Name': 'KKK'
}, {
'Number': 'A20051028007501',
'Name': 'LLL'
}
]
var removeUselessData = data;
data.map(function(element, index) {
if (element['Name'] === "AAA" || Number(element['Number'].slice(1, 5)) < 2011) {
removeUselessData.splice(index, 1);
}
});
console.log(removeUselessData);
the result print on my terminal is
[ { Number: 'A20051130027901', Name: 'BBB' },
{ Number: 'H20170206000056', Name: 'CCC' },
{ Number: 'F20170120000056', Name: 'DDD' },
{ Number: 'A20071005006001', Name: 'AAA' },
{ Number: 'A20051020016601', Name: 'KKK' } ]
What I expect it will delete the number small than 2011 and the Name cannot be "AAA".
But the result still have them.
What you want is Array.filter :
console.log(data.filter(function(element, index){
return !(element['Name'] === "AAA" || Number(element['Number'].slice(1, 5)) < 2011)
}));
Now, as to why your code isn´t working :
var removeUselessData = data;
Note that this does not copy the data, it justs makes a variable point to the same array reference (if you remove from removeUselessData, you remove from data as well). To prevent this, you need to do :
var removeUselessData = data.slice(0); // this will copy the array (not deep copy though)
Now, when you use splice, you are removing something from the array. Problem is, doing this in a for loop is usually inconsistent, as when you remove something, array elements now have different indexes, so the index from the array and the index from removeUselessData will not match.
To fix it, you could remove the elements in backwards order to ensure consistency :
for (var index = data.length - 1; index >= 0; index--) {
var element = data[index];
if (element['Name'] === "AAA" || Number(element['Number'].slice(1, 5)) < 2011) {
removeUselessData.splice(index, 1);
}
}

ImmutableJS: merge two list of objects, without duplicating them

Supposing I have the below:
var allFoods = Immutable.List();
var frenchFood = Immutable.List([
{
'type': 'french fries',
'price': 3
},
{
'type': 'petit gateau',
'price': 40
},
{
'type': 'croissant',
'price': 20
},
]);
var fastFood = Immutable.List([
{
'type': 'cheeseburger',
'price': 5
},
{
'type': 'vegan burger',
'price': 20
},
{
'type': 'french fries',
'price': 3
}
]);
I want to merge both lists, in a way that I also remove dupes (in this case, french fries), so the expected result would be:
{
'type': 'french fries', // keep the first french fries
'price': 3
},
{
'type': 'petit gateau',
'price': 40
},
{
'type': 'croissant',
'price': 20
},
{
'type': 'cheeseburger',
'price': 5
},
{
'type': 'vegan burger',
'price': 20
}
What I'm trying (doesn't remove dupes):
allFoods = frenchFood.concat(fastFood);
allFoods = allFoods.filter(function(item, pos) {
return allFoods.indexOf(item) === pos;
});
Which returns arrays merged, but still duplicated.
What am I missing?
const allFoods = frenchFood.concat(fastFood.filter((item) =>
frenchFood.indexOf(item) < 0
));
I would use reduce
var result = frenchFood.concat(fastFood).reduce( (reduction, food) => {
if(reduction[food.type]) {
return reduction;
} else {
return reduction.set([food.type], food);
}
}, new Immutable.Map()).valueSeq().toList();
I would highly encourage you to not nest js objects inside immutable structures. Better to wrap those objects in an Immutable.Map() or do Immutable.fromJS(yourJsObj).
Least amount of code
const results = Immutable.Set(frenchFood).union(Immutable.Set(fastFood));
However #rooftop answer fastest
https://jsperf.com/union-vs-concat-immutable
I found a best solution (for me) on medium, link to origin answer is dead: https://medium.com/#justintulk/merging-and-deduplicating-data-arrays-with-array-reduce-efaa4d7ef7b0
const arr1 = [
{ id: 1, name: 'Array 1-1' },
{ id: 2, name: 'Array 1-2' },
{ id: 3, name: 'Array 1-3' }
]
const arr2 = [
{ id: 1, name: 'Array 2-1' },
{ id: 3, name: 'Array 2-3' },
{ id: 4, name: 'Array 2-4' }
]
const mergeArrObjectsUnique = (currentArr, newArr) => {
let obj = {}
currentArr.forEach(item => {
obj[item.id] = item
})
newArr.forEach(item => {
obj[item.id] = item
})
let result = [];
for(let p in obj) {
if(obj.hasOwnProperty(p))
result.push(obj[p])
}
console.log('result: ', result)
return result
}
mergeArrObjectsUnique(arr1, arr2)

Sum value form array in object

I've an object:
var stuffData = {
'Fruit': [{
'Name': 'Apple',
'Price': '2'
}, {
'Name': 'Kiwi',
'Price': '4'
}],
'Sport': [{
'Name': 'Ball',
'Price': '10'
}, {
'Name': 'Bike',
'Price': '120'
}],
'Kitchen': [{
'Name': 'Knife',
'Price': '8'
}, {
'Name': 'Fork',
'Price': '7'
}]
}
Now i want to get sum from Price Column.
I thought about this
for (var key in stuffData)
{
// and for each key i have to add new array with sum of price or what?
// But how will I display this sum then?
// I haven't any idea how can I deal with this
}
Something like this should work, mapping the objects, and reducing to sum each one
var stuffData = {
'Fruit': [{
'Name': 'Apple',
'Price': '2'
}, {
'Name': 'Kiwi',
'Price': '4'
}],
'Sport': [{
'Name': 'Ball',
'Price': '10'
}, {
'Name': 'Bike',
'Price': '120'
}],
'Kitchen': [{
'Name': 'Knife',
'Price': '8'
}, {
'Name': 'Fork',
'Price': '7'
}]
}
var o = {};
Object.keys(stuffData).forEach(function(key) {
o[key] = stuffData[key].map(function(item) {
return parseInt(item.Price, 10);
}).reduce(function(a,b) {
return a + b;
});
});
document.body.innerHTML = '<pre>' + JSON.stringify(o, 0, 4) + '</pre>';
The result would be
{
Fruit: 6,
Sport: 130,
Kitchen: 15
}
Try like this
for (var key in stuffData) {
var totalSum =0;
if(stuffData[key] = 'Fruit'{
for(var i=0; i< stuffData[key].length ; i++){
totalSum+=stuffData[key].price;
}
}
if(stuffData[key] = 'Sport'{
for(var i=0; i< stuffData[key].length ; i++){
totalSum+=stuffData[key].price;
}
}
if(stuffData[key] = 'Kitchen'{
for(var i=0; i< stuffData[key].length ; i++){
totalSum+=stuffData[key].price;
}
}
console.log("TOTAL SUM",totalSum);
}
You can use Array.prototype.reduce to sum over a list.
var categorySums = {};
for(category in stuffData) {
var items = stuffData[category];
categorySums[category] = items.reduce(function (sum, item) {
return sum + parseInt(item.Price);
}, 0);
}
If you use the library lodash (or something similar, e.g. underscore or Ramda), they've got a mapValues utility that makes this simpler:
var categorySums = _.mapValues(stuffData, function(items) {
return _.sum(items.map(function (item) {
return parseInt(item.Price);
}));
});
Thank you for every answer. The best solution is below:
var sum = {};
for (var k in stuffData)
{
sum[k] = 0;
stuffData[k].forEach(function (e)
{
sum[k] += parseInt(e.price);
});
}

Categories

Resources