Replace value in one array with matching properties from second array - javascript

I've got two arrays:
arrayOne = ["green","blue","purple"]
and
arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
]
I want the return array to be [1, 8, 9], with "purple" being pushed as an object at the end of arrayTwo (with a new id).
What's the most efficient way to go about this?

The following code uses map to either retrieve the id of the element in the second array or, if that element doesn't exist, create a new one by incrementing the last id in that array by 1.
arrayOne = ["green","blue","purple"]
arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
]
const newArr = arrayOne.map(color => {
const found = arrayTwo.find(el => el.name === color);
if (found) {
return found.id;
}
const newId = arrayTwo[arrayTwo.length - 1].id + 1;
arrayTwo.push({ name: color, id: newId });
return newId;
});
console.log(newArr);
console.log(arrayTwo);
Edit: Note that it may be brittle to assume the last item in arrayTwo contains the highest id. In that case, you can always find the max ID:
const newArr = arrayOne.map(color => {
let maxId = 0;
const found = arrayTwo.find(el => {
if (el.id > maxId) {
maxId = el.id;
}
return el.name === color
});
if (found) {
return found.id;
}
const newId = maxId + 1;
arrayTwo.push({ name: color, id: newId });
return newId;
});
A thought on efficiency
If you have any concerns about efficiency if this is going to be a large (hundreds/thousands/more) element arrays, you can consider changing arrayTwo to an object with color as a key:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
];
let maxId = 0;
// Create map
const arrayTwoMap = arrayTwo.reduce((acc, el) => {
if (el.id > maxId) maxId = el.id;
acc[el.name] = el.id;
return acc;
}, {});
// Find elements
const newArr = arrayOne.map(el => {
const found = arrayTwoMap[el];
if (found !== undefined) {
return found;
}
const newId = maxId + 1;
arrayTwoMap[el] = newId;
return newId;
});
console.log(newArr);

this code can achieve what you want:
let arrayTwo = [
{ name: "green", id: 1 },
{ name: "red", id: 2 },
{ name: "yellow", id: 3 },
{ name: "blue", id: 8 },
];
let indexes = {}, lastId = 0;
arrayTwo.forEach(({name, id}) => {
if(indexes[name] = id, id > lastId) lastId = id
});
function getResult(a){
return a.map(e => indexes[e] || (arrayTwo.push({name: e, id: ++lastId}), indexes[e] = lastId))
}
// arrayOne contents
let result = getResult(["green", "blue", "purple"]);
console.log(arrayTwo, result);
// with other data
let result2 = getResult(["cyan", "blue", "purple", "azure"]);
console.log(arrayTwo, result2);
Hope it helps

You can convert arrayTwo into a Map (for efficiency), where the name is the key and the id is the value. Then, once you have done that, you can .map() arrayOne into an array of id by using each name element as a look-up key to get its associated id. If you find a name which is not in the Map, then you can add a new object, to your arrayTwo array, and increment the id counter:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [{ name: "green", id: 1 }, { name: "red", id: 2 }, { name: "yellow", id: 3 }, { name: "blue", id: 8 },];
let [{id:l_id}] = arrayTwo.slice(-1);
const lut = new Map(arrayTwo.map(({name, id}) => [name, id]));
const res = arrayOne.map(name => lut.get(name) || (arrayTwo.push({name, id: ++l_id}), l_id));
console.log(res);
console.log(arrayTwo);
If you're only concerned about the return value (and not changing array 2), you can simplify the code down:
const arrayOne = ["green","blue","purple"];
const arrayTwo = [{ name: "green", id: 1 }, { name: "red", id: 2 }, { name: "yellow", id: 3 }, { name: "blue", id: 8 },];
let [{id:l_id}] = arrayTwo.slice(-1);
const lut = new Map(arrayTwo.map(({name, id}) => [name, id]));
const res = arrayOne.map(
name => lut.get(name) || ++l_id
);
console.log(res);

Related

Can this algorithm be made more efficient?

Currently I have an algorithm that runs to compare to different arrays of objects.
const allGroups = [{ id: '12345', name: 'groupOne'}, {id: '23421', name: 'groupTwo'},
{id: '28182', name: 'groupThree'}]
const clientsGroups = [{ id: 'abcde', clientGroupID: '12345'}, {id: 'dfcdae', clientGroupID: '93282'},
{id: 'jakdab', clientGroupID: '28182'}, {id: 'oiewad', clientGroupID: '93482'}]
const updateClientGroups = (allGroups, clientsGroups) => {
let allGroupsCopy = [...allGroups];
for (let i = 0; i < allGroupsCopy.length; i++) {
const allGroupsId = allGroupsCopy[i].id;
for (let j = 0; j < clientsGroups.length; j++) {
if (allGroupsId === clientsGroups[j].clientGroupID) {
allGroupsCopy[i] = {
...allGroupsCopy[i],
inGroup: true,
clientGroupID: clientsGroups[j].id,
};
}
}
}
return allGroupsCopy;
};
I check two different arrays of objects, if the id of allGroups matches the clientGroupID of clientGroups, I mutate the 'allGroupsCopy' to have 'inGroup: true' and add in the id of the clientsGroups.
The problem with this algorithm is it runs in n^2 time. Is there a more efficient way to do this?
Without changing the original arrays, could this be the an optimization ?
const allGroups = [
{ id: "12345", name: "groupOne" },
{ id: "23421", name: "groupTwo" },
{ id: "28182", name: "groupThree" },
];
const clientsGroups = [
{ id: "abcde", clientGroupID: "12345" },
{ id: "dfcdae", clientGroupID: "93282" },
{ id: "jakdab", clientGroupID: "28182" },
{ id: "oiewad", clientGroupID: "93482" },
];
const updateClientGroups = (groups, clients) => {
return clients.reduce((acum, current) => {
const isInGroup = groups.find((group) => group.id === current.clientGroupID);
acum.push({
...current,
inGroup: Boolean(isInGroup),
});
return acum;
}, []);
};
updateClientGroups(allGroups, clientsGroups)
If you change allGroups structure from array to map, you can do the job in linear time.
Something like:
const allGroups = {
'12345': { id: '12345', name: 'groupOne'}
...
}
const updateClientGroups = (allGroups, clientsGroups) => {
const clientGroupsMap = {};
clientsGroups.forEach(({clientGroupID}) =>
if(allGroups[clientGroupID]) {
clientGroupsMap[clientGroupID] = {...allGroups[clientGroupID], inGroup: true};
}
);
return {...allGroups, ...clientGroupsMap};
};

How to get unique element from two arrays in js?

I'm trying to get unique (by id) values from two arrays.
But it returns whole array instead of { id: 3 }
const a = [{ id: 1 }, { id: 2 }];
const b = [{ id: 1 }, { id: 2 }, { id: 3 }];
const array3 = b.filter((obj) => a.indexOf(obj) == -1);
console.log(array3);
What's wrong here?
You cannot compare objects you should check that an element with that id doesn't exists in the other array
here I used some that returns a boolean if he can find a match
const a = [{
id: 1
}, {
id: 2
}];
const b = [{
id: 1
}, {
id: 2
}, {
id: 3
}];
const array3 = b.filter(obj => !a.some(({id}) => obj.id === id));
console.log(array3)
In your case, the following code gives all unique objects as an array, based on the id.
const a = [{
id: 1
}, {
id: 2
}];
const b = [{
id: 1
}, {
id: 2
}, {
id: 3
}];
const array3 = b.filter(objB => a.some((objA) => objB.id !== objA.id));
console.log(array3)
A different approach with a symmetrically result.
const
take = m => d => o => m.set(o.id, (m.get(o.id) || 0) + d),
a = [{ id: 1 }, { id: 2 }],
b = [{ id: 1 }, { id: 2 }, { id: 3 }],
map = new Map(),
add = take(map),
result = [];
a.forEach(add(1));
b.forEach(add(-1));
map.forEach((v, id) => v && result.push({ id }));
console.log(result);

Javascript - How to combine all combinations into an array of objects

I have the following array:
[{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}]
Using some javascript logic I would like to output the following array:
[{
option1: 10,
option2: 'red'
},
{
option1: 10,
option2: 'blue'
},
{
option1: 12,
option2: 'red'
},
{
option1: 12,
option2: 'blue'
}]
What is the best and correct way to achieve this using javascript?
Lets say your first array is named arr.
var arr = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}];
var v1 = arr[0].values.split(',');
var v2 = arr[1].values.split(',');
var res = new Array();
for(i in v1){
for(j in v2){
res.push({'option1':v1[i],'option2':v2[j]});
}
}
console.log(res);
Here's an approach that can handle an arbitrary number of objects.
function valuesCrossProduct(input) {
return input.flatMap((current, index, array) => {
let result = [];
let values = current.values.split(',');
for (let v of values) {
for (let i = 0; i < array.length; i++) {
if (i <= index) {
// Skip creating cross products with self (i.e. == index)
// and with previously visited objects (i.e. < index).
continue;
}
let iValues = array[i].values.split(',');
let currentKey = `option${index}`;
let iKey = `option${i}`;
for (let iv of iValues) {
result.push({
[currentKey]: v,
[iKey]: iv,
});
}
}
}
return result;
});
}
let twoElementArray = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue',
}];
let threeElementArray = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue',
},
{
name: 'baz',
values: 'wham,bam',
}];
console.log(valuesCrossProduct(twoElementArray));
console.log(valuesCrossProduct(threeElementArray));
Functional for the win.
Note: as it is, this only works for an array of two objects, with any number of values in each, where the first set of values are numbers and the second set are strings, which is what you described above.
const arr = [{
name: 'foo',
values: '10,12'
},
{
name: 'bar',
values: 'red,blue'
}];
const values = arr
.map(o => o.values.split(','))
.reduce((cur, next) => {
return cur.map(c => {
return next.map(n => {
return {
option1: parseInt(c),
option2: n
};
});
}).flat();
});
console.log(values);
If you need generic approach to get possible options from various values.
const options = data => {
let sets = [[]];
data.forEach(({ values }, i) => {
const new_set = [];
values.split(",").forEach(value => {
new_set.push(
Array.from(sets, set => [...set, [`option${i + 1}`, value]])
);
});
sets = new_set.flatMap(set => set);
});
return sets.map(set => Object.fromEntries(set));
};
const data = [
{
name: "foo",
values: "10,12"
},
{
name: "bar",
values: "red,blue,green"
},
{
name: "test",
values: "top,bottom"
}
];
console.log(options(data));

Get count from Array of arrays

I have an array of arrays below. With ES6, how can I get a count of each value Good, Excellent & Wow into a new array e.g [{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}] in dynamic style. I am attempting to use Object.assign but I am failing to "unique" out the count of the key plus instead, I need to use an array as I am trying to render this out on the front end. Do I need to use reduce? how?
let k = 0
const stats = {}
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
]
remarks.forEach((arr) => {
arr.map((e) => {
Object.assign(stats, { [e.name]: k = k + 1 })
})
})
console.log(stats);
Output:
stats: {Good: 8, Excellent: 11, Wow: 9}
Which is Incorrect plus I need to use an array.
Expected output:
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
Flatten the array of arrays and reduce it starting with an object like : { Good: 0, Excellent: 0, Wow: 0}
then .map the Object.entries of the result to transform it to an array :
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const result = Object.entries(
remarks.flat().reduce(
(all, { name }) => {
all[name] += 1;
return all;
},
{ Good: 0, Excellent: 0, Wow: 0 }
)
).map(([name, count]) => ({ name, count }));
console.log(result);
You can try below logic:
var data = [[{name: "Good"}],[{name: "Good"}, {name:"Excellent"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name:"Excellent"}],[{name:"Excellent"}]]
var nData = [];
(data || []).forEach( e => {
(e || []).forEach(ei => {
var i = (index = nData.findIndex(d => d.name === ei.name)) >=0 ? index : nData.length;
nData[i] = {
name: ei.name,
count : (nData[i] && nData[i].count ? nData[i].count : 0)+1
}
});
});
console.log(nData);
Hope this helps!
You can use reduce, then convert the result into an array of objects:
const counts = remarks.reduce((result, list) => {
list.forEach(remark => {
result[remark.name] = (result[remark.name] || 0) + 1;
});
}, {});
const finalResult = [];
for (let name in counts) {
finalResult.push({name, count: counts[name]});
}
You could achieve this pretty easily by:
1) Flattening the nested array into 1 single level array.
2) Iterating over the flat array and create a "count map" by using Array.prototype.reduce
For example:
const remarks = [
[{
name: 'Good'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Excellent'
}],
[{
name: 'Excellent'
}]
]
const flatten = arr => arr.reduce((accum, el) => accum.concat(el), [])
const map = flatten(remarks).reduce((accum, el) => {
if (accum[el.name]) {
accum[el.name] += 1;
} else {
accum[el.name] = 1;
}
return accum;
}, {});
console.log(map)
First find the counts using reduce than pass that to another function to get the desired view structure:
const Good = 1,
Excellent = 2,
Wow = 3;
const remarks = [
[{name: Good}],
[{name: Good}, {name:Excellent}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name:Excellent}],
[{name:Excellent}]
];
/*
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
*/
function counts(remarks) {
return remarks.flat().reduce((acc, v) => {
const name = v.name;
let count = acc[name] || 0;
return {
...acc,
[name]: count + 1
}
}, {});
}
function view(counts) {
return Object.keys(counts).map(key => {
let count = counts[key];
return { name: key, count };
})
}
console.log(view(counts(remarks)));
Any time you are making a smaller set of data, or transforming data, in JavaScript reduce should be the first method you attempt to use. In this case, you may want to pair it with an indexer (hence preloading with an array of index and an array of result).
This works in one pass without needing to know the name values up front.
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
];
const stats = remarks.reduce((p,c) => (
c.forEach( ({name}) => {
if(!p[0].hasOwnProperty(name)){
p[1].push({name:name,count:0});
p[0][name] = p[1].length - 1;
}
p[1][p[0][name]].count++;
}),p),[{},[]])[1];
console.log(stats);
A slightly more concise and definitely less readable approach (but it's worth to mention) could be:
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const stats = Object.entries(
remarks
.flat()
.reduce((acc, {name}) => (acc[name] = -~acc[name], acc), {})))
).map(([name, count]) => ({ name, count }));
console.log(stats);
It uses the comma operator in the reducer to returns the accumulator; and the bitwise operator NOT to create a counter without the needs to initialize the object upfront with all the names.
const flattenedRemarks = _.flatten(remarks);
const groupedRemarks = _.groupBy(flattenedRemarks, (remark) => remark.name);
const remarkCounts = _.mapValues(groupedRemarks, (group) => group.length);
const data = {
"mchale": {
"classes":["ESJ030", "SCI339"], // get the length
"faculty":["Hardy", "Vikrum"] // get the length
},
"lawerence":{
"classes":["ENG001"], // get the length
"faculty":["Speedman", "Lee", "Lazenhower"] // get the length
}
};
const count = Object.keys(data).map(campusName => {
const campus = data[campusName];
return Object.keys(campus).map(key => campus[key].length).reduce((p, c) => p + c, 0);
}).reduce((p, c) => p + c, 0);
console.log(count);

If you want to remove an object from the first array which is not present in second array

If you want to remove more than one object from the first array "arrayOne" which is not present in the second array "arrayTwo". It's just a suggestion the way I do. If you have any other way please let me know.
let arrayOne = [{
id: 1
}, {
id: 2
}, {
id: 3
}]
let arrayTwo = [{
id: 2
},{
id: 3
}]
for (var index = arrayOne.length; index--;) {
if (!arrayTwo.find(y => y.id === arrayOne[index].id)) {
arrayOne.splice(arrayOne.findIndex(z => z.id === arrayOne[index].id), 1)
console.log("After splice", arrayOne)
}
You can also use a Set to store ids of elements of arrayTwo and then filter to extract only those elements of arrayOne that are also present in arrayTwo:
let arrayOne = [{
id: 1
}, {
id: 2
}, {
id: 3
}];
let arrayTwo = [{
id: 2
}];
let arrayTwoSet = new Set(arrayTwo.map(e => e.id));
console.log(arrayOne.filter(e => arrayTwoSet.has(e.id)));
use Array.some() inside Array.filter()
let arrayOne = [{ id: 1 }, { id: 2 }, { id: 3 }] ;
let arrayTwo = [{ id: 2 }];
const result = arrayOne.filter(obj1 => !arrayTwo.some(obj2 => obj1.id === obj2.id));
console.log('final array : ', result);

Categories