Javascript findIndex for duplicate values - javascript

JavaScript findIndex returns the very first finded index in case of duplicate values
const arr = [{a: 10, b: 20, c: 30},{a: 15, b: 25, c: 32},{a: 10, b: 23, c: 350}]
const index = arr.findIndex(m => m.a === 10)
console.log(index);
The above code will only return 0 index.
What should I do to get index 2 as well.

You could filter the keys of the array like this:
const arr = [{a: 10, b: 20, c: 30},{a: 15, b: 25, c: 32},{a: 10, b: 23, c: 350}]
const indices = [...arr.keys()].filter(i => arr[i].a === 10)
console.log(indices)
Or, just use a for loop
const arr = [{a: 10, b: 20, c: 30},{a: 15, b: 25, c: 32},{a: 10, b: 23, c: 350}]
const output = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i].a === 10)
output.push(i)
}
console.log(output)

arr.reduce((result, current, index) => {
return result.concat(current.a == 10 ? [index]: [])
})

You can use of Array.reduce to loop on your arr and returns every indexes
Single line :
const arr = [{
a: 10,
b: 20,
c: 30,
}, {
a: 15,
b: 25,
c: 32,
}, {
a: 10,
b: 23,
c: 350,
}]
// Value to check for
const v = 10;
// we loop on every entry of the arr
// we start with an empty array and look if the entries
// contains the value 10, if they do, we push the index of the
// entry in the array that was empty
//
// we do that for every entry of arr, when we treat all, we return
// the list of matching entries
const idx = arr.reduce((tmp, x, xi) => (x.a === v ? [...tmp, xi] : tmp), []);
console.log(idx);
Explained :
const arr = [{
a: 10,
b: 20,
c: 30,
}, {
a: 15,
b: 25,
c: 32,
}, {
a: 10,
b: 23,
c: 350,
}]
const valToCheck = 10;
const indexes = arr.reduce((tmp, x, xi) => {
if (x.a === valToCheck) {
tmp.push(xi);
}
return tmp;
}, []);
console.log(indexes);

Use map and filter:
const arr = [{a: 10, b: 20, c: 30},{a: 15, b: 25, c: 32},{a: 10, b: 23, c: 350}];
const res = arr.map(({ a }, i) => a == 10 ? i : "").filter(String);
console.log(res);

You could map the indices of the found items and filter only valid indices.
const
array = [{ a: 10, b: 20, c: 30 }, { a: 15, b: 25, c: 32 }, { a: 10, b: 23, c: 350 }],
indices = array
.map((m, i) => m.a === 10 ? i : -1)
.filter(i => i != -1);
console.log(indices);

You can just iterate through array and push matching index in new array.
const arr = [{a: 10, b: 20, c: 30},{a: 15, b: 25, c: 32},{a: 10, b: 23, c: 350}]
let p = []
arr.forEach((ele,index)=> ele.a===10 ? p.push(index) : '')
console.log(p)

You can try like this:
const arr = [{a: 10, b: 20, c: 30},{a: 15, b: 25, c: 32},{a: 10, b: 23, c: 350}];
const indices = arr.map((x, index) => {
if (x.a === 10) {
return index;
}
})
.filter(x => x !== undefined);
console.log(indices);

If you want a function which works directly with Array, add this in Array.prototype
Array.prototype.findAllIndexes = function(iteratee){
var resultArray = [];
this.forEach(function(element) {
var validated = iteratee.call(this, element);
if(validated) {
resultArray.push(element);
}
});
return resultArray;
}
Then you can use it anywher you want with array object
(please handle your corner cases based od data type you use)
const arr = [{a: 10, b: 20, c: 30},{a: 15, b: 25, c: 32},{a: 10, b: 23, c: 350}]
const index = arr.findAllIndexes(m => m.a === 10)
console.log(index);
You can use some helper libraries to this kind of stuff like lodash.js, you can include them in your project.

Related

compare two array of objects keys, original array should mutate in javascript

How can we compare two array of objects on the basis of their keys or properties of object in javaScript?
for an example:
let result1 = [
{ a: 10, b: 20, c: 22 },
{ a: 20, b: 33, c: 11 },
];
let result2 = [
{ a: 10, b: 20 },
{ a: 20, b: 33 },
];
result1.filter(function (obj) {
return !result2.some(function (obj2) {
let key1 = Object.keys(obj);
let key2 = Object.keys(obj2);
key1?.forEach((x, index1) => {
key2?.forEach((y, index2) => {
console.log(index1, index2)
if (x === y) {
return obj[x] === obj2[y];
}
});
});
});
});
console.log(result1)
output: current output
expected output:
result1 =
[
{ a: 10, b: 20 },
{ a: 20, b: 33 },
];
I just try this solution in a different way, We can also achieve this requirement by doing deep copy of original array and then mutate it using forEach().
Live Demo :
let result1 = [
{ a: 10, b: 20, c: 22 },
{ a: 20, b: 33, c: 11 }
];
let result2 = [
{ a: 10, b: 20 },
{ a: 20, b: 33 }
];
const clone = structuredClone(result1);
clone.forEach((obj, index) => {
result1[index] = {};
Object.keys(result2[index]).forEach(key => {
result1[index][key] = obj[key]
});
});
console.log(result1);
let result1 = [
{ a: 10, b: 20, c: 22 },
{ a: 20, b: 33, c: 11 },
];
let result2 = [
{ a: 10, b: 20 },
{ a: 20, b: 33 },
];
let temp = []
result1.map(function(obj) {
return !result2.some(function(obj2) {
let key1 = Object.keys(obj);
let key2 = Object.keys(obj2);
key1.forEach((x) => {
key2.forEach((y) => {
if (x === y) {
obj[x] === obj2[y] ? temp.push({
[x]: obj[x]
}) : null;
}
})
});
})
})
console.log(temp);
try this code
You are getting the result1 array back beacause the array is not getting filtered.
The filter function is not getting anything from returned as the first foreach loop is not returning and the some function is also not returning anything
Now you can use map function instead of filter as the function is not returning anything
const filterKeysBasedOnFirstArrayOfObjects = <
T extends Record<string, unknown>
>(
arr1: T[],
arr2: T[]
) => {
const keys = _.intersection(
...arr1.map((obj) => Object.keys(obj)),
...arr2.map((obj) => Object.keys(obj))
);
return arr1.map((obj) => _.pick(obj, keys));
};
A more concise approach using lodash.

problem with reduce and memoisation in javascript hardstructure

i have to do a task, where i need to iterate over hardstructure with nested functions arrays objects and other data types. get all the numbers there are in string or not in string into the array and then with reduce method i need to calculate their sum, after calculating their sum i need to do memoisation on this function. but something doesnt work with my reduce even though i have written the most simple task there is, calculating the sum of numbers. my reduce doesnt really calculate the right way it skips some parts of the structure and doesnt gather all of the nubmers also it doesnt really calculate right i think
const ex1 = () => ({ a: 13 });
const ex2 = () => [1, 2, 3, 4, 5, "a,,,,,,", 2, 2, 2];
const ex3 = () => [...ex2()];
const ex4 = new Set([1, 2, 3, 4]);
const hardStructure = [
{
a: 1,
b: "2",
c: [
{
a: "123",
b: "#333z$34",
c: "122,333,4444,5555",
z: ex4,
d: [
[
123,
1,
"1111",
23323,
1222,
55,
[1212, 1212, 1212],
{
a: 111,
b: null,
c: () => 111,
d: 22,
e: "e",
f: () => "fffqqq",
},
],
[
2212,
1,
"111211",
23,
121,
22,
[33, 3, 3],
{
a: 3,
b: null,
c: () => 11221,
d: 2112,
e: "e11",
f: () => "fffqqq",
g: 2,
},
],
[
11,
22,
"dsds",
{
a: {
b: {
c: {
d: {
a: 1,
b: [ex1()],
c: {
a: {
b: {
c: {
d: {
a: new Map([
["a2134", 2123123],
["b", 3],
["c", 3],
]),
b: ex2,
c: [...ex3(), "1", 1],
},
},
},
},
},
},
},
},
},
},
],
],
},
],
},
{
d: [
[
123,
1,
"1111",
23323,
1222,
55,
[121322332, 12132322, 12323212],
{
a: 111,
b: null,
c: () => 1123231,
d: 22,
e: "e",
f: () => "fffqqq",
},
],
[
2212,
1,
"111211",
23,
121,
22,
[33, 3, 3],
{
a: 3,
b: null,
c: () => 1123221,
d: 211,
e: "e1231",
f: () => "fffqq1232312123q",
g: 2123,
},
],
[
11,
22,
"dsds",
{
a: {
b: {
c: {
d: {
a: 1,
b: [ex1()],
c: {
a: {
b: {
c: {
d: {
a: new Map([
[true, 222312],
[2, 322],
["c2", 32],
[() => {}, 32],
]),
b: ex2,
c: [...ex3(), "1121123", 1],
},
},
},
},
},
},
},
},
},
},
],
],
},
{
a: {
b: {
c: {
d: {
a: 112312,
b: [1],
c: {
a: {
b: {
c: {
d: {
a: "",
b: "",
c: "",
v: "v12312323",
},
},
},
},
},
},
},
},
},
},
];
const numbersArr = [];
const iterate = (hardStructure) => {
if (hardStructure === null || hardStructure === undefined) {
return [];
}
if (
hardStructure.constructor === new Set().constructor ||
hardStructure.constructor === new Map().constructor
) {
console.log("console from map or set");
hardStructure.forEach((element) => {
// console.log("123", element);
return iterate(element);
});
}
if (typeof hardStructure === "function") {
return iterate(hardStructure());
}
if (Array.isArray(hardStructure)) {
hardStructure.map((items) => {
return iterate(items);
});
}
if (typeof hardStructure === "object" && !Array.isArray(hardStructure)) {
for (let key in hardStructure) {
// console.log(hardStructure[key]);
return iterate(hardStructure[key]);
}
}
if (typeof hardStructure !== "string") {
const stringItem = String(hardStructure);
for (let i = 0; i < stringItem.length; i++) {
if (!isNaN(+stringItem)) {
numbersArr.push(+stringItem);
}
}
}
for (let i = 0; i < hardStructure.length; i++) {
// console.log(i);
if (!isNaN(+hardStructure[i])) {
// console.log("12345678910", isNaN(+hardStructure[2]));
numbersArr.push(+hardStructure[i]);
}
}
const sumWithInitial = numbersArr.reduce((accumulator, currentValue) => {
console.log(accumulator,currentValue)
return accumulator + currentValue;
},0);
// console.log(numbersArr)
console.log(sumWithInitial);
return sumWithInitial;
};
iterate(hardStructure);
Your requirements aren't entirely clear, especially as to handling of String and Function properties. This solution assumes that you want to call functions and that you can use parseInt for Strings. If you need to change that, it should be clear enough how and where to do so:
const sumNumbers = (xs) =>
xs == null
? 0
: xs .constructor == String
// ? parseInt (xs, 10) || 0 // original version not enough. Below might not be so either
? xs .includes (',') ? sumNumbers (xs .split (',')) : Number (xs .replaceAll (/\D/g, ''))
: xs .constructor == Number
? xs
: xs .constructor === Array
? xs .map (sumNumbers) .reduce ((a, b) => a + b, 0)
: xs .constructor === Set
? sumNumbers ([...xs])
: xs .constructor === Map
? sumNumbers ([...xs .values()])
: xs .constructor === Function
? sumNumbers (xs ()) // or just 0?
: xs .constructor == Object
? sumNumbers (Object .values (xs))
// TODO: Other types possible here?
: 0
const ex1 = () => ({a: 13}),
ex2 = () => [1, 2, 3, 4, 5, "a,,,,,,", 2, 2, 2],
ex3 = () => [...[1, 2, 3, 4, 5, "a,,,,,,", 2, 2, 2]],
ex4 = new Set ([1, 2, 3, 4]),
hardStructure = [{a: 1, b: "2", c: [{a: "123", b: "#333z$34", c: "122,333,4444,5555", z: ex4, d: [[123, 1, "1111", 23323, 1222, 55, [1212, 1212, 1212], {a: 111, b: null, c: () => 111, d: 22, e: "e", f: () => "fffqqq"}], [2212, 1, "111211", 23, 121, 22, [33, 3, 3], {a: 3, b: null, c: () => 11221, d: 2112, e: "e11", f: () => "fffqqq", g: 2}], [11, 22, "dsds", {a: {b: {c: {d: {a: 1, b: [{a: 13}], c: {a: {b: {c: {d: {a: new Map ([["a2134", 2123123], ["b", 3], ["c", 3]]), b: ex2, c: [...ex3(), "1", 1]}}}}}}}}}}]]}]}, {d: [[123, 1, "1111", 23323, 1222, 55, [121322332, 12132322, 12323212], {a: 111, b: null, c: () => 1123231, d: 22, e: "e", f: () => "fffqqq"}], [2212, 1, "111211", 23, 121, 22, [33, 3 , 3], {a: 3, b: null, c: () => 1123221, d: 211, e: "e1231", f: ()=> "fffqq1232312123q", g: 2123}], [11, 22, "dsds", {a: {b: {c: {d: {a: 1, b: [{a: 13}], c: {a: {b: {c: {d: {a: new Map ([[true, 222312], [2, 322], ["c2", 32], [() => {}, 32]]), b: ex2, c: [...ex3 (), "1121123", 1]}}}}}}}}}}]]}, {a: {b: {c: {d: {a: 112312, b: [1], c: {a: {b: {c: {d: {a: "", b: "", c: "", v: "v12312323"}}}}}}}}}}]
console .log (sumNumbers (hardStructure))
We have four base cases:
on a nil value (null or undefined), we return 0
on a Number, we return its value
on a String, we call parseInt on it and return the value. This is the part I think you would most likely need to change
on an unknown type (perhaps a regular expression or a date), we return 0
The others are recursive case:
for an Array, we recursively call our function on each element and then total them together.
for a Function, we call the function with no arguments, and recur on the result.
for our other known types (Object, Set, Map), we extract its values into an Array and recur on the result.

Finding all the keys in the array of objects

Given that I have an array that is return from database that contains object,
var jsObjects = [
{a: 1, b: 2},
{a: 3, b: 4, c: 66},
{a: 5, b: 6, c: 55, d: 66},
{a: 7, b: 8, c: 12, e: 15}
];
How can I get all the keys of the object? I've been using this to obtain the keys, however I notice that index 0 wont always have all the keys. Hence problem lays.
let columns = Object.keys(jsObjects[0]),
However, the first index won't always have all the columns.
My desired output:
["a", "b", "c", "d", "e"]
Sets are good at removing duplicates:
const jsObjects = [
{a: 1, b: 2},
{a: 3, b: 4, c: 66},
{a: 5, b: 6, c: 55, d: 66},
{a: 7, b: 8, c: 12, e: 15}
];
const keys = [...new Set(jsObjects.flatMap(Object.keys))];
console.log(keys);
You could create an object with the count of the keys and get the keys from counts.
const
objects = [{ a: 1, b: 2 }, { a: 3, b: 4, c: 66 }, { a: 5, b: 6, c: 55, d: 66 }, { a: 7, b: 8, c: 12, e: 15 }],
result = Object.keys(objects.reduce((r, o) => {
Object.keys(o).forEach(k => r[k] = true);
return r;
}));
console.log(result);
You can use a Set(), which is like an array with the exception that it doesn't allow multiple occurances of elements.
var jsObjects = [
{a: 1, b: 2},
{a: 3, b: 4, c: 66},
{a: 5, b: 6, c: 55, d: 66},
{a: 7, b: 8, c: 12, e: 15}
];
var keys = new Set();
for(object of jsObjects) {
for(key in object) {
keys = keys.add(key);
}
}
for (const key of keys) {
console.log(key);
}
I tried this solution: iterating on jsObjects elements and pushing keys
into columns array if not present.
var jsObjects = [
{a: 1, b: 2},
{a: 3, b: 4, c: 66},
{a: 5, b: 6, c: 55, d: 66},
{a: 7, b: 8, c: 12, e: 15}
];
let columns = [];
jsObjects.forEach(
o => {
Object.keys(o).forEach(
t => {
if (columns.indexOf(t) < 0 ) {
columns.push(t);
}
}
)
}
)
console.log(columns);

How to group by multiple keys at the same time using D3?

This works, but I was wondering if there was a better way than creating a string with a and b and later splitting it:
const data = [
{ a: 10, b: 20, c: 30, d: 40 },
{ a: 10, b: 20, c: 31, d: 41 },
{ a: 12, b: 22, c: 32, d: 42 }
];
d3.rollups(
data,
x => ({
c: x.map(d => d.c),
d: x.map(d => d.d)
}),
d => `${d.a} ${d.b}`
)
.map(([key, values]) => {
const [a, b] = key.split(' ');
return {a, b, ...values};
});
// OUTPUT
// [
// {a: "10", b: "20", c: [30, 31], d: [40, 41]},
// {a: "12", b: "22", c: [32], d: [42]}
// ]
With d3 v7 released, there is now a better way to do this using the new d3.flatRollup.
const data = [
{ a: 10, b: 20, c: 30, d: 40 },
{ a: 10, b: 20, c: 31, d: 41 },
{ a: 12, b: 22, c: 32, d: 42 }
];
const result = d3.flatRollup(
data,
x => ({
c: x.map(d => d.c),
d: x.map(d => d.d)
}),
d => d.a,
d => d.b
);
console.log(result);
const flattened = result.map(([a, b, values]) => ({a, b, ...values}));
console.log(flattened);
<script src="https://cdn.jsdelivr.net/npm/d3-array#3.0.2/dist/d3-array.min.js"></script>
As you already know d3.rollups() will create nested arrays if you have more than one key:
If more than one key is specified, a nested Map [or array] is returned.
Therefore, as d3.rollups doesn't fit your needs, I believe it's easier to create a plain JavaScript function (I'm aware of "using D3" in your title, but even in a D3 code nothing forbids us of writing plain JS solutions where D3 has none).
In the following example I'm purposefully writing a verbose function (with comments) so each part of it is clear, avoiding more complex features which could make it substantially short (but more cryptic). In this function I'm using reduce, so the data array is looped only once. myKeys is the array of keys you'll use to rollup.
Here is the function and the comments:
function groupedRollup(myArray, myKeys) {
return myArray.reduce((a, c) => {
//Find the object in the acc with all 'myKeys' equivalent to the current
const foundObject = a.find(e => myKeys.every(f => e[f] === c[f]));
//if found, push the value for each key which is not in 'myKeys'
if (foundObject) {
for (let key in foundObject) {
if (!keys.includes(key)) foundObject[key].push(c[key]);
};
//if not found, push the current object with all non 'myKeys' keys as arrays
} else {
const copiedObject = Object.assign({}, c);//avoids mutation
for (let key in copiedObject) {
if (!keys.includes(key)) copiedObject[key] = [copiedObject[key]];
};
a.push(copiedObject);
};
return a;
}, [])
};
Here is the demo:
const data = [{
a: 10,
b: 20,
c: 30,
d: 40
},
{
a: 10,
b: 20,
c: 31,
d: 41
},
{
a: 12,
b: 22,
c: 32,
d: 42
}
];
const keys = ["a", "b"];
console.log(groupedRollup(data, keys))
function groupedRollup(myArray, myKeys) {
return myArray.reduce((a, c) => {
const foundObject = a.find(e => myKeys.every(f => e[f] === c[f]));
if (foundObject) {
for (let key in foundObject) {
if (!keys.includes(key)) foundObject[key].push(c[key]);
};
} else {
const copiedObject = Object.assign({}, c);
for (let key in copiedObject) {
if (!keys.includes(key)) copiedObject[key] = [copiedObject[key]];
};
a.push(copiedObject);
};
return a;
}, [])
};
And here is a demo with a more complex data:
const data = [{
a: 10,
b: 20,
c: 30,
d: 40,
e: 5,
f: 19
},
{
a: 10,
b: 55,
c: 37,
d: 40,
e: 5,
f: 19
},
{
a: 10,
b: 20,
c: 31,
d: 48,
e: 5,
f: 18
},
{
a: 80,
b: 20,
c: 31,
d: 48,
e: 5,
f: 18
},
{
a: 1,
b: 2,
c: 3,
d: 8,
e: 5,
f: 9
},
{
a: 10,
b: 88,
c: 44,
d: 33,
e: 5,
f: 19
}
];
const keys = ["a", "e", "f"];
console.log(groupedRollup(data, keys))
function groupedRollup(myArray, myKeys) {
return myArray.reduce((a, c) => {
const foundObject = a.find(e => myKeys.every(f => e[f] === c[f]));
if (foundObject) {
for (let key in foundObject) {
if (!keys.includes(key)) foundObject[key].push(c[key]);
};
} else {
const copiedObject = Object.assign({}, c);
for (let key in copiedObject) {
if (!keys.includes(key)) copiedObject[key] = [copiedObject[key]];
};
a.push(copiedObject);
};
return a;
}, [])
};
Finally, pay attention that this function will push duplicated values (in the above example d: [40, 40, 33]). If that's not what you want then just check for duplicates.
The approach below allows you to remove the split, but does not prevent the need to create a string for the compound key. In this case, using JSON.stringify({a: d.a, b: d.b}) instead of ${d.a} ${d.b}, allows for the map to return an object where the c and d properties can be assigned to the parse of the key.
This preserves some of the 'd3-ishness' of your question and the utility of rollups to deal with the creation of the arrays for c and d.
const data = [
{ a: 10, b: 20, c: 30, d: 40 },
{ a: 10, b: 20, c: 31, d: 41 },
{ a: 12, b: 22, c: 32, d: 42 }
];
const groups = d3.rollups(
data,
x => ({
c: x.map(d => d.c),
d: x.map(d => d.d)
}),
d => JSON.stringify({a: d.a, b: d.b}) // compare with `${d.a} ${d.b}`
).map(arr => Object.assign(JSON.parse(arr[0]), arr[1]));
console.log(groups);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>
The approach can accommodate the extensibility of #Gerado Furtado's answer, but I fear it's getting a little hectic:
const data = [
{a: 10, b: 20, c: 30, d: 40, e: 5, f: 19},
{a: 10, b: 55, c: 37, d: 40, e: 5, f: 19},
{a: 10, b: 20, c: 31, d: 48, e: 5, f: 18},
{a: 80, b: 20, c: 31, d: 48, e: 5, f: 18},
{a: 1, b: 2, c: 3, d: 8, e: 5, f: 9},
{a: 10, b: 88, c: 44, d: 33, e: 5, f: 19}
];
const keys = ["a", "e", "f"];
const groupedRollup = (data, keys) => {
const others = Object.keys(data[0])
.filter(k => !keys.includes(k)); // finds b, c, d as not part of compound key
return d3.rollups(
data,
x => Object.assign(
{},
...others.map(k => {
return {[k]: x.map(d => d[k])} // dynamically create reducer
})
),
d => JSON.stringify(
Object.assign(
{},
...keys.map(k => {
return {[k]: d[k]} // dynamically add keys
})
)
) // and stringify for compound key
).map(arr => Object.fromEntries( // sorting the output object
Object.entries( // keys in alpha order
Object.assign(JSON.parse(arr[0]), arr[1])).sort() // same approach
)
);
}
console.log(groupedRollup(data, keys));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>
There's some interesting talk about the introduction of use of InternMap in rollups and the associated functions - but I don't see either that it's ready, or that it's useful for what you are trying to do.

add to all values in object using spread

Does anyone know if there is a shorthand for this?
let zx = { a: 1, b: 10};
let as = { a: 20, b: 1};
as += ...zx; //Ideas on a shorthand here?
console.log(as);
desired result: {a: 21, b: 11}
You can use Object.keys method in combination with forEach by passing an arrow function as argument.
let zx = { a: 1, b: 10};
let as = { a: 20, b: 1};
Object.keys(as).forEach(key => as[key] += zx[key])
console.log(as);
Another approach is using reduce method.
let zx = { a: 1, b: 10};
let as = { a: 20, b: 1};
as = Object.keys(as).reduce(function(obj, k) {
obj[k] = zx[k] + as[k]
return obj;
}, {});
console.log(as);

Categories