For example, has the following data:
let example = {
content: [
...
{ // index = 3
id: "b3bbb2a0-3345-47a6-b4f9-51f22b875f22",
data: {
value: "hello",
content: [
...
{ // index = 0
id: "65b1e224-4eae-4a6d-8d00-c1caa9c7ed2a",
data: {
value: "world",
content: [
...
{ // index = 1
id: "263a4961-efa7-4639-8a57-b20b23a7cc9d",
data: {
value: "test",
content: [
// Nesting unknown.
]
}
}
]
}
}
]
}
}
]
}
And for example an array with indexes leading to the required element(but can be any other):
const ids = [3, 0, 1]
How can you work with an element having this data?
For example, need to change "value" in the element at the specified path in "ids".
You could take an array of indices and get the item of the property content by calling the function again for each missing index.
const
getElement = ({ content }, [index, ...indices]) => indices.length
? getElement(content[index], indices)
: content[index];
If needed, you could add a guard for a missing index and exit early.
You can just recursively loop over your element and change the value, if you got to the last element.
I have written a small example, all you would have to do, is to extend the method for your own data structure (el.data.content):
const el = [[1,2], [3,4], [5,6]];
const changeEl = (el, indexArr, val) => {
if(indexArr.length == 1) {
el[indexArr[0]] = val;
} else {
changeEl(el[indexArr.shift()], indexArr, val);
}
}
changeEl(el, [1, 0], 99);
console.log(el);
Related
Here is my array of objects
const array = [
{id: 1, data: "foo"},
{id: 1, data: "bar"},
{id: 2, data: "baz"}
]
I want to remove all duplicate objects by its id and return only the array of objects that have an unique id.
Expected result:
[
{id: 2, data: "baz"}
]
This is what I have now: O(n^2)
function getUnique(array) {
const newArray = []
for (let obj of array) {
if (array.filter(x => x.id === obj.id).length === 1) {
newArray.push(obj)
}
}
return newArray
}
Whats the more efficient way to achieve this?
Is it possible to get the time-complexity to O(n) or O(n log n)?
const array = [{
id: 1,
data: "foo"
},
{
id: 1,
data: "bar"
},
{
id: 2,
data: "baz"
}
]
let map = {};
array.forEach(x => {
map[x.id] = (map[x.id] || 0) + 1
});
console.log(array.filter(x => map[x.id] === 1))
I would suggest to count the number of occurrences in your array and store them in a Map. Next you filter all element, which count is 1
function getUnique(arr) {
const count = new Map();
arr.forEach((element) => {
count.set(element.id, (count.get(element.id) || 0) + 1);
});
return array.filter((element) => {
return count.get(element.id) === 1;
});
}
This has a runtime of 2(n) since you have to iterate over them twice
// This is a large array of objects, e.g.:
let totalArray = [
{"id":"rec01dTDP9T4ZtHL4","fields":
{"user_id":170180717,"user_name":"abcdefg","event_id":516575,
}]
let uniqueArray = [];
let dupeArray = [];
let itemIndex = 0
totalArray.forEach(x => {
if(!uniqueArray.some(y => JSON.stringify(y) === JSON.stringify(x))){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
node.warn(totalArray);
node.warn(uniqueArray);
node.warn(dupeArray);
return msg;
I need my code to identify duplicates in the array by a key value of user_id within the objects in the array. Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this? I am struggling to figure out how to path the for each loop to identify the dupe based on the key value instead of the entire object.
Right now, my code works to identify identical objects in the array, but I need it to identify dupes based on a key value inside the objects instead. How do I do this?
Don’t compare the JSON representation of the whole objects then, but only their user_id property specifically.
totalArray.forEach(x => {
if(!uniqueArray.some(y => y.fields.user_id === x.fields.user_id)){
uniqueArray.push(x)
} else(dupeArray.push(x))
})
You could take a Set and push to either uniques or duplicates.
var array = [
{ id: 1, data: 0 },
{ id: 2, data: 1 },
{ id: 2, data: 2 },
{ id: 3, data: 3 },
{ id: 3, data: 4 },
{ id: 3, data: 5 },
],
uniques = [],
duplicates = [];
array.forEach(
(s => o => s.has(o.id) ? duplicates.push(o) : (s.add(o.id), uniques.push(o)))
(new Set)
);
console.log(uniques);
console.log(duplicates);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way is to keep a list of ids you found so far and act accordingly:
totalArray = [
{ id: 1, val: 10 },
{ id: 2, val: 20 },
{ id: 3, val: 30 },
{ id: 2, val: 15 },
{ id: 1, val: 50 }
]
const uniqueArray = []
const dupeArray = []
const ids = {}
totalArray.forEach( x => {
if (ids[x.id]) {
dupeArray.push(x)
} else {
uniqueArray.push(x)
ids[x.id] = true
}
})
for (const obj of uniqueArray) console.log("unique:",JSON.stringify(obj))
for (const obj of dupeArray) console.log("dupes: ",JSON.stringify(obj))
i want to access the id 'qwsa221' without using array index but am only able to reach and output all of the array elements not a specific element.
i have tried using filter but couldnt figure out how to use it properly.
let lists = {
def453ed: [
{
id: "qwsa221",
name: "Mind"
},
{
id: "jwkh245",
name: "Space"
}
]
};
Use Object.keys() to get all the keys of the object and check the values in the array elements using . notation
let lists = {
def453ed: [{
id: "qwsa221",
name: "Mind"
},
{
id: "jwkh245",
name: "Space"
}
]
};
Object.keys(lists).forEach(function(e) {
lists[e].forEach(function(x) {
if (x.id == 'qwsa221')
console.log(x)
})
})
You can use Object.Keys method to iterate through all of the keys present.
You can also use filter, if there are multiple existence of id qwsa221
let lists = {
def453ed: [
{
id: "qwsa221",
name: "Mind"
},
{
id: "jwkh245",
name: "Space"
}
]
};
let l = Object.keys(lists)
.map(d => lists[d]
.find(el => el.id === "qwsa221"))
console.log(l)
you can do it like this, using find
let lists = {
def453ed: [
{
id: "qwsa221",
name: "Mind"
},
{
id: "jwkh245",
name: "Space"
}
]
};
console.log(
lists.def453ed // first get the array
.find( // find return the first entry where the callback returns true
el => el.id === "qwsa221"
)
)
here's a corrected version of your filter :
let lists = {def453ed: [{id: "qwsa221",name: "Mind"},{id: "jwkh245",name: "Space"}]};
// what you've done
const badResult = lists.def453ed.filter(id => id === "qwsa221");
/*
here id is the whole object
{
id: "qwsa221",
name: "Mind"
}
*/
console.log(badResult)
// the correct way
const goodResult = lists.def453ed.filter(el => el.id === "qwsa221");
console.log(goodResult)
// filter returns an array so you need to actually get the first entry
console.log(goodResult[0])
I have two arrays of objects and I want to compare the objects of the first array to the ones of the second array. If they match, I use the splice to remove the object from the second array.
I have the following code
existing.forEach((existingitem, existingindex, existingfeatures) => {
(newdatafeat.features).forEach((newitem, newindex, newfeatures) => {
console.log('existing index-name --- new index-name', existingindex ,' - ',existingitem.values_.name,' - ',newindex,' - ',newitem.properties.name,'-----');
if (existingitem.values_.id == newitem.properties.id && existingitem.values_.cat == newitem.properties.cat){
console.log(' index to remove - ', newindex); (newdatafeat.features).splice(newindex,1);
}
})
});
So, If existing is
var existing= [
{ info: true, values_:{id:1, cat:true, name : "John"} },
{ info : true, values_:{id:2, cat:false, name : "Alice"} }
];
and newdatafeat.features is
var newdatafeat= {
status:scanned,
features : [ { info: true, properties:{id:1, cat:true, name : "Mike"} },
{ info : false, properties:{id:22, cat:false,name : "Jenny"} } ]
};
Then, Mike from newdatafeat.features should be removed.
The error is that every item of the newdatafeat.features array with index 0 is not removed. In the loop, I can see index to remove - 0, but Mike is never removed. I know, because if I console.log the newdatafeat.features after the loops, Mike is there
This is inside an angular6 code.
What am I missing here?
Thanks
I had to clean up some code, but it looks like yours is working fine. It identified one element to be removed, called slice and it was gone.
var existing = [{
info: true,
values_: {
id: 1,
cat: true,
name: "John"
}
},
{
info: true,
values_: {
id: 2,
cat: false,
name: "Alice"
}
}
];
var newdata = {
status: "scanned",
features: [
{
info: true,
properties: {
id: 1,
cat: true,
name: "Mike"
}
},
{
info: false,
properties: {
id: 22,
cat: false,
name: "Jenny"
}
}
]
};
existing.forEach(
(existingitem, existingindex, existingfeatures) => {
(newdata.features).forEach((newitem, newindex, newfeatures) => {
console.log('existing index-name --- new index-name', existingindex, ' - ', existingitem.values_.name, ' - ', newindex, ' - ', newitem.properties.name, '-----');
if (existingitem.values_.id == newitem.properties.id && existingitem.values_.cat == newitem.properties.cat) {
console.log(' index to remove - ', newindex);
(newdata.features).splice(newindex, 1);
}
})
});
console.log(newdata.features);
The main problem here is that you are iterating an array in a loop, but you are removing items from that array in the same loop. So the index will often be off, and the wrong element or no element will be removed. It can be hard to reproduce with simple examples, but here ya go:
function removeVowels(letters) {
letters.forEach((element, index, arr) => {
if ('aeiou'.indexOf(element) > -1) {
arr.splice(index, 1);
}
});
}
var myArray = ['a','b','c','d','e'];
removeVowels(myArray);
console.log(myArray);
// works great!
var myArray = ['a','e','c','d','b'];
removeVowels(myArray);
console.log(myArray);
// wtf!
A simple way to approach this problem is to handle the looping manually, and change the index manually if you DO remove an element.
function removeVowels(letters) {
for (var i=0; i < letters.length; i++) {
if ('aeiou'.indexOf(letters[i]) > -1) {
letters.splice(i, 1);
i--;
}
}
}
var myArray = ['a','b','c','d','e'];
removeVowels(myArray);
console.log(myArray);
// works great!
var myArray = ['a','e','c','d','b'];
removeVowels(myArray);
console.log(myArray);
// hurray!
const data = [
1,2,3,4,5,[6,7,8],
]
function goEach(items) {
if (items.length) {
items.forEach((item) => goEach(item))
} else {
console.log(items);
}
}
let newList = [];
function buildNewList(items) {
if (items.length) {
items.forEach((item) => buildNewList(item))
} else {
newList.push(items);
// console.log(items);
}
}
let simList = [];
function buildSimList(items) {
if (items.length) {
items.forEach((item) => buildSimList(item))
} else {
simList.push({id: items, checked: false});
// console.log(items);
}
}
buildSimList is what I am working on.
I know how to traverse each item as well as flattening a nested array with recursion. However, my brain getting stucked on the best way to produce an array with identical array structure but in different data format. For example, my buildSimList should be converting the data array into an array similar to this
[
{id: 1, clicked: false},
{id: 2, clicked: false},
{id: 3, clicked: false},
{id: 4, clicked: false},
{id: 5, clicked: false},
[
{id: 6, clicked: flase},
{id: 7, clicked: flase},
{id: 8, clicked: flase},
],
]
Is there an intuitive way to achieve it? I can't think of any. even using a library like lodash can be a good solution as well.
You were close since you do one object for non arrays and recurse when not, but you are adding to the same array and thus that will result in a flattened structure. Here is the same where each array become an array and each element become an object using higher order functions:
const data = [1,2,3,4,5,[6,7,8]]
function convert(e) {
return e instanceof Array ?
e.map(convert) :
{ id: e, clicked: false };
}
console.log(convert(data));
Also know that using the function twice won't give you the same oddity that you get the earlier result combined as in your code. The reson for that is that I do not use a global accumulator. Should I use an accumulator I would do something like this:
function convert(e) {
function helper(e, acc) {
...
}
return helper(e, []);
}
or if you really want mutation:
function convert(e) {
let acc = [];
function helper(e) {
acc.push(...);
}
helper(e);
return acc;
}
Explanation uses symbols from my code snippet
When iterating data within convert we'll have to cover for 2 cases:
numOrArray is a number, in which case we map it toObject
numOrArray is an array, in which case we start a nested iteration. This may be a recursive call, since the logic is identical for nested arrays.
// Converts given number to an object with id and clicked properties:
const toObject = (num) => ({ id: num, clicked: false });
// Iterates given data and converts all numbers within to
// objects with toObject. If an array is encountered,
// convert is called recursively with that array as argument.
const convert = (data) => {
return data.map((numOrArray) => Array.isArray(numOrArray)
? convert(numOrArray)
: toObject(numOrArray));
}
// Test drive!
const data = [1, 2, 3, 4, 5, [6, 7, 8]];
console.log(convert(data));
Both converting items in a nested array, and flattening such an array are pretty simple if you use a recursive function.
All you have to do go through each item, and if it's not another array, perform the transformation you want. Otherwise, recurse. Here's an example for the data in your snippet:
const data = [1,2,3,4,5,[6,7,8]]
function convert(arr) {
return arr.map(item => {
if (item instanceof Array) return convert(item);
return {id: item, clicked: false};
});
}
const mapped = convert(data);
console.log(mapped);
function flatten(arr) {
return [].concat(...arr.map(item => {
if (item instanceof Array) return flatten(item);
return [item];
}));
}
const flat = flatten(mapped);
console.log(flat)
Use forEach function along with recursion and check for the element's type using either typeof keyword or instanceof keyword.
const data = [1, 2, 3, 4, 5, [6, 7, 8]];
let convert = (acc /*This is the current accumulator*/ , array) => {
array.forEach((n) => {
if (n instanceof Array) {
let newAcc = []; // New accumulator because the iteration found an Array.
acc.push(newAcc);
convert(newAcc, n); // Recursion for the new accumulator
} else acc.push({ id: n, clicked: false });
});
};
let result = [];
convert(result, data);
console.log(result);
.as-console-wrapper {
max-height: 100% !important
}
When you want to transform an array to another array, the first thing to think of is map. Given your requirements, here's my solution to your problem:
const data = [
1, 2, 3, 4, 5, [6, 7, 8]
];
const buildSimList = (array) =>
// transform array with map
array.map(item =>
// check if current item is array
item instanceof Array
// if it is an array then recursively transform it
? buildSimList(item)
// if not, then convert the number to an object
: { id: item, checked: false }
);
console.log(buildSimList(data));
I would recommend a generic function like Array.prototype.map but one the maps nested arrays too – we'll call it deepMap.
const identity = x =>
x
const deepMap = (f = identity, xs = []) =>
xs.map (x =>
Array.isArray (x)
? deepMap (f, x)
: f (x))
const makeObject = (id = 0, clicked = false) =>
({ id, clicked })
const data =
[ 1, 2, 3, 4, 5, [ 6, 7, 8 ] ]
console.log (deepMap (makeObject, data))
// [ { id : 1, clicked : false }
// , { id : 2, clicked : false }
// , { id : 3, clicked : false }
// , { id : 4, clicked : false }
// , { id : 5, clicked : false }
// , [ { id : 6, clicked : false }
// , { id : 7, clicked : false }
// , { id : 8, clicked : false }
// ]
// ]
We get a better picture of how this works if we first understand map, which only performs a shallow transformation – Below, we reimplement deepMap but this time using our own implementation of map
const identity = x =>
x
const None =
Symbol ()
const map = (f = identity, [ x = None, ...xs ] = []) =>
x === None
? []
: [ f (x) , ...map (f, xs) ]
const deepMap = (f = identity, xs = []) =>
map (x =>
Array.isArray (x)
? deepMap (f, x)
: f (x), xs)
const makeObject = (id = 0, clicked = false) =>
({ id, clicked })
const data =
[ 1, 2, 3, 4, 5, [ 6, 7, 8 ] ]
console.log (deepMap (makeObject, data))
// [ { id : 1, clicked : false }
// , { id : 2, clicked : false }
// , { id : 3, clicked : false }
// , { id : 4, clicked : false }
// , { id : 5, clicked : false }
// , [ { id : 6, clicked : false }
// , { id : 7, clicked : false }
// , { id : 8, clicked : false }
// ]
// ]