JavaScript: Find all parent indexes in a nested object - javascript

I have the following object:
const obj = {
alphas: {
top: [{name: "q"}, {name: "w"}],
middle: [{name: "a"}, {name: "s"}],
bottom: [{name: "z"}, {name: "x"}],
},
numbers: {
low: [{name: "1"}, {name: "2"}, {name: "3"}],
high: [{name: "1000"}, {name: "2000"}],
}
}
I need the nested indexes of the name.
For instance, if I'm looking for "s" the result should be the array [0, 1]:
0 because "s" is in the first category (alphas)
1 because "s" is in the second subcategory (middle).
I'm able to find the indexes in separate loops:
const categoryIndex = Object.keys(obj).findIndex(
cat => Object.keys(obj[cat]).some(
subcat => obj[cat][subcat].some(
key => key.name === "s")));
const categoryName = Object.keys(obj)[categoryIndex];
const subcategoryIndex = Object.keys(obj[categoryName]).findIndex(
subcat => obj[categoryName][subcat].some(key => key.name === "s"));
const result = [categoryIndex, subcategoryIndex];
https://jsfiddle.net/7w523ojn/
Having two separate loops may cost too much so I'm looking for a way to get indexes at once. Something like this:
[categoryIndex , subcategoryIndex] = ...
Is there a way to get nested indexes at once?
Solutions involving Lodash, Ramda and whatnot are also welcome.
Thank you.

If I understand you question correctly, then this can be achieve via a recursive search and return function, as detailed below:
const obj={alphas:{top:[{name:"q"},{name:"w"}],middle:[{name:"a"},{name:"s"}],bottom:[{name:"z"},{name:"x"}]},numbers:{low:[{name:"1"},{name:"2"},{name:"3"}],high:[{name:"1000"},{name:"2000"}]}};
const recursiveSearch = (object, searchFor) => {
let index = 0;
for (const key in object) {
const value = object[key];
if (Array.isArray(value)) {
/* If object value is an array, iterate through it and
search for item with name field matching searchFor
character */
const array = value;
for (let i = 0; i < array.length; i++) {
/* If array item's name matches searchFor charater
then return this as the first entry in our results
array */
if (array[i].name === searchFor) {
return [index, i];
}
}
} else if (typeof object === "object") {
/* If object value is object, then recursivly search
through children for a result. If an result is found,
prepend the current key's index to the result array */
const result = recursiveSearch(value, searchFor);
/* Array.isArray() tells us this call to recursiveSearch()
found a result, so we are to return from this call */
if (Array.isArray(result)) {
return [index].concat(result);
}
}
index++;
}
};
console.log('indicies for "s":', recursiveSearch(obj, 's'));

Related

JS React: Two Objects, change properties if name matches

i have a problem with Objects in JavaScript (React).
I have two different Objects, which are generated from two different XML-Files. Each Object has the same Names in it but the points and position can be different. My Goal is to add the Points from the second Object to the First if the name matches.
The Structure of the Objects is the following:
let obj = [{name: "Max", points: 2},{name:"Marc", points: 1}]
let obj2 = [{name:"Marc", points: 2},{name: "Max", points: 1}]
The Goal is to have one updated Object:
let updatedObj = [{name: "Max", points:3},{name:"Marc", points:3}]
My Code looks like this rn:
import React, {useState} from 'react'
import axios from 'axios'
const App = () => {
const [list1,setList1] = useState()
const [list2,setList2] = useState()
const readFile = (file, number) => {
const dayPath = `/files/`;
axios.post(`http://localhost:5001/getData`, {dayPath, file})
.then(res => {
// File Number 1
if(number === 1){
let obj = res.data.vehicles[1].vehicle.map((item) => (
{
name: item.name,
points: res.data.vehicles[0] - Number(item.Position) +1 // Vehicles Total - Position + 1
})
)
setList(obj)
}else {
/*
* Get second Object -> Map -> Find matching Name ->
* Keep the Name Value -> (Calculate) and Add Points ->
* Push to State
*/
}
})}
}
return (
<div>App</div>
)
export default App
I've tried it with Object.entries, but only the last item was updated and the others were empty.
Thanks for your help!
let obj = [{name: "Max", points: 2},{name:"Marc", points: 1}]
let obj2 = [{name:"Marc", points: 2},{name: "Max", points: 1}]
const updatedObj = obj.map((person)=> {
const personRes = obj2.find((searchPerson)=> searchPerson.name === person.name)
if(personRes){
person.point += personRes.point
}
return person
})
You could use this createCombinedArray function. It is a little longer than some solutions, but the function is very easy to read, flexible (even with inconsistent or incorrect inputs), and has a runtime complexity of O(n) (A.K.A. a linear runtime complexity):
let obj = [{name: "Max", points: 2},{name:"Marc", points: 1}]
let obj2 = [{name:"Marc", points: 2},{name: "Max", points: 1}]
function createCombinedArray(arr1, arr2) {
//we'll keep track of how many points are associated with a name
let pointTracker = {}
//total all the points for every name in arr1
for (let i = 0; i < arr1.length; i++) {
let currObj = arr1[i]
//if this object has a 'name' property and a property 'number' that
//is actually a number
if (currObj.hasOwnProperty('name') && !isNaN(currObj.points)) {
//then add it to the name's total if it exists
if (pointTracker[currObj.name] !== undefined) {
pointTracker[currObj.name] += +currObj.points
} else {
//or if it doesn't exist, then make a new entry in your point
//tracker system with those points
pointTracker[currObj.name] = +currObj.points
}
}
}
//in addtion, total all the points for every name in arr2, also
//in the pointTracker object
for (let i = 0; i < arr2.length; i++) {
let currObj = arr2[i]
//if this object has a 'name' property and a property 'number' that
//is actually a number
if (currObj.hasOwnProperty('name') && !isNaN(currObj.points)) {
//then add it to the name's total if it exists
if (pointTracker[currObj.name] !== undefined) {
pointTracker[currObj.name] += +currObj.points
} else {
//or if it doesn't exist, then make a new entry in your point
//tracker system with those points
pointTracker[currObj.name] = +currObj.points
}
}
}
let combinedArr = []
//now fill in the currently-empty return array, by making and
//pushing objects for all the names found in our pointTracker object
for (let name in pointTracker) {
combinedArr.push({name: name, points: pointTracker[name]})
}
return combinedArr
}
//here's the function in action
const newObj = createCombinedArray(obj, obj2)
console.log(newObj)
This code will print out the array
[ { name: 'Max', points: 3 }, { name: 'Marc', points: 3 } ]
Just include the function code anywhere in the same .js file in your React project, and use the function where ever you like in that file.
One possible downside to this solution is that if the objects in your original arrays contained any properties that weren't name or points, then those properties' values won't be inside of the new object.

Mapping arrays into object with values as an array

I would like to create an object containing all the values from the array first and the values from second as an array of values, assuming that this key references multiple numbers such that given the arrays:
first = [3,0,5,3]
second = [1,3,10,5]
my output would be {0: 3, 3: [1,5], 5: 10}
This is what I have tried:
const newObj = {}
for(let i = 0; i < first.length; i++){
newObj[first[i]] = second[i] ? [ second[i] ] : second[i]
}
This is what I get:
{ '0': [ 3 ], '3': [ 5 ], '5': [ 10 ] }
This is the easiest method to understand as it's just the bare logic written in its entirety.
for (let i = 0; i < first.length; i++) {
if (first[i] in newObj) { // If key exists already
if (typeof newObj[first[i]] === "number") { // and if it's a number
newObj[first[i]] = [newObj[first[i]]]; // then we wrap it into an array
}
newObj[first[i]].push(second[i]); // add the new number
} else {
newObj[first[i]] = second[i]; // if it doesn't exist then add it
}
}
You can loop through all the items. You have three conditions for every array element you get. You will try to map it to a key of your new object you are creating and these will be the cases:
the key exists and value is already an array. (just push to the value)
the key exists and is not an array. (just create an array of two values)
the key does not exist. (just add a new key value pair)
const first = [3,0,5,3];
const second = [1,3,10,5];
const ans = {};
first.forEach((curr,index) => {
if(Array.isArray(ans[curr])){ ans[curr] = [...ans[curr],second[index]];
}
else{
if(ans[curr]){ans[curr] = [ans[curr],second[index]];
}
else ans[curr] = second[index];
}
});
console.log(ans);
I would collect all the values into arrays, regardless of how many there are (the transform function below does this) — having a data structure with uniform types is more predictable/easier to work with.
Then, in a subsequent step, the single element from the arrays with only one element can be extracted (the extractSingleArrayElements function below does this). See comments in the code for explanation:
Code in TypeScript Playground
'use strict';
/**
* Takes an array of keys and an array of values, and returns an object
* with values collected in arrays grouped by the keys that match their indexes
*
* #param {number[]} keys Array of integer keys
* #param {number[]} values Array of integer values
* #returns {Record<string, number[]>}
*/
function transform (keys, values) {
// Ensure that the lengths of the arrays are the same before proceeding:
if (keys.length !== values.length) throw new Error('Input lengths differ');
const result = {};
for (let i = 0; i < values.length; i += 1) {
const key = keys[i];
const value = values[i];
// Create a refrence to the array of values at the key,
// first creating and assigning it to the object at the key
// if it doesn't already exist:
const array = (result[key] ??= []);
array.push(value);
}
return result;
}
/**
* Takes an object with array values and returns a shallow copy of the object,
* replacing any array values that have only one element with the element itself
*
* #param {Record<string, number[]>} obj
* #returns {Record<string, number | number[]>}
*/
function extractSingleArrayElements (obj) {
const result = {};
for (const [key, array] of Object.entries(obj)) {
result[key] = array.length === 1 ? array[0] : array;
}
return result;
}
const keys = [3, 0, 5, 3];
const values = [1, 3, 10, 5];
const transformed = transform(keys, values);
const actual = extractSingleArrayElements(transformed);
const expected = {0: 3, 3: [1, 5], 5: 10};
console.log(JSON.stringify(actual) === JSON.stringify(expected)); // true
This is my suggestion for it, using Array.reduce, spread syntax and computed property names
first.reduce((o, k, i) => ({ ...o, [k]: [...o[k]||[], second[i]] }), {})
first = [3,0,5,3]
second = [1,3,10,5]
output = first.reduce((o, k, i) => ({ ...o, [k]: [...o[k]||[], second[i]] }), {})
console.log(output)
You can find detailed explanation on this strategy in this other answer.

How can I delete duplicated values in an array while counting the repetitions?

I would like to count the amount of times that a value appears in an array, and delete the repeated values keeping just the first.
So I can store in another array the amount of times that it appears, and it will have the same index as the value in the other array.
The loops I have tried don't count the amount of times correctly or delete other values that shouldn't be the ones deleted.
var itemCount = [];
var itemsName = ["aaa", "bbb", "aaa", "ccc", "ddd", "ddd"]
for (let index = 0; index < itemsName.length; index++) {
for (var i = index + 1; i < itemsName.length; ++i) {
itemCount[index] = 1
if (itemsName[i] === itemsName[index]){
itemCount[index]++;
itemsName.splice(i, 1)
}
}
if (itemCount[index] > 1) {
console.log(itemsName[index] + itemCount[index])
} else {
console.log(itemsName[index])
}
}
So the output should be like,
aaa3 bbb ccc ddd2
I made some changes to your code
const itemsName = ["aaa", "bbb", "aaa", "ccc", "ddd", "ddd"];
const itemCount = {}; // It's better to set this as an object.
for (let index = 0; index < itemsName.length; index++) {
// index is a key and not the value of the array.
// itemsName[index] will give you the value of an item in the array
// Here I check if the key exists, incrementing it or setting it to 1.
if (itemCount[itemsName[index]]) itemCount[itemsName[index]]++;
else itemCount[itemsName[index]] = 1;
}
for (let key in itemCount) {
console.log(key + itemCount[key]);
}
If you remove an entry from an array you change the index of subsequent entries. Thus you cannot keep the old index in the new array you create for duplicates.
What you can do is create two separate arrays, one without duplicates, the other containing objects with the duplicates and their index.
var itemsName = [...];
var newArray = [];
var duplicatesArray = [];
var duplicateCount = 0;
// map creates a new array with the return statement
newArray = itemsName.map((entry,index) => {
// check if the new array includes the current entry iteration
if ( newArray.inculdes(entry) ) {
duplicateCount++; // just the duplicate counter
let duplicate = { oldIndex: index, value: entry }; // duplicate entry containing the oldIndex well .. index. This is so that you can get the index of the entry in the old array.
duplicatesArray.push(duplicate); // add entry to duplicateArray
return; // finish iteration without adding the entry to the new array
}
return e; // add iteration entry to the new array
});
This should be it. =)
Instead of using an array to keep track of the item count, use an object instead. You can use the item name as object key to quickly find the current count and increment it.
const itemsName = ["aaa", "bbb", "aaa", "ccc", "ddd", "ddd", "1", 1];
const itemCount = {};
for (const name of itemsName) {
itemCount[name] ||= 0;
itemCount[name] += 1;
}
console.log("unique values", Object.keys(itemCount));
console.log("itemCount", itemCount);
||= is a fairly new operator, if you are not using a transpiler (like Babel) to support older browsers you might want to use the following instead:
if (!itemCount[name]) itemCount[name] = 0;
Note that when using objects the all keys are converted to strings. So "1" and 1 are considered the same. If you want to differentiate between strings, numbers, complex objects (by identity, not value), etc. use a Map instead.
const itemsName = ["aaa", "bbb", "aaa", "ccc", "ddd", "ddd", "1", 1];
const itemCount = new Map();
for (const name of itemsName) {
if (!itemCount.has(name)) itemCount.set(name, 0);
itemCount.set(name, itemCount.get(name) + 1);
}
console.log("unique values", Array.from(itemCount.keys()));
for (const [name, count] of itemCount) {
console.log(typeof name, name, count);
}

How to get all the values and keys inside an array of objects separated with comma without inbuilt methods

I have tried to retrieve all the values of an plain object which returns an array separated with comma since I am using map().
var obj = { 1: 'Banana', 2: 'Orange'};
var values = Object.keys(obj).map(function(e) {
return obj[e]
}) //returns ['Banana','Orange']
Where as I need all the values and keys as string separated by comma from below object array without using Object.values() and Object.keys
input: var items = [{1:"Banana"}, {2:"Orange"}, {3:"Apple"}]
output 1: "1,2,3" ---> keys
output 2: "Banana,Orange,Apple" --> values
Problem is when the objects are inside an array I am not getting any idea how to retrieve keys and object values separately.
Kindly help!
You can use array reduce & join
Inside reduce callback use for..in to iterate the object. [[],[]] is the accumulator & in the first array keys will be collected and in second array values will be collected.
Inside the for..in push the keys & values from each object.
Once done use join with delimiter , to create the string
var items = [{
1: "Banana"
}, {
2: "Orange"
}, {
3: "Apple"
}]
let vals = items.reduce(function(acc, curr) {
for (let keys in curr) {
acc[0].push(keys);
acc[1].push(curr[keys])
}
return acc;
}, [
[],
[]
]);
console.log(vals[0].join(','), vals[1].join(','))
You can iterate over the array and in a nested for loop iterate over the object:
var items = [{1:"Banana"}, {2:"Orange"}, {3:"Apple"}];
let keys = [];
let values = [];
for (let element of items){
for (let i in element){
keys.push(i);
values.push(element[i]);
}
}
keys = keys.join(",");
values = values.join(",");
console.log(keys);
console.log(values);
Using for ... in
const items = [{
1: "Banana"
}, {
2: "Orange"
}, {
3: "Apple"
}];
let keys = '';
let values = '';
items.forEach((x) => {
for (let y in x) {
keys = `${keys}${keys.length ? ',' : ''}${y}`;
values = `${values}${values.length ? ',' : ''}${x[y]}`;
}
});
console.log(keys);
console.log(values);
Using Object.keys() and Array.reduce
const items = [{
1: "Banana"
}, {
2: "Orange"
}, {
3: "Apple"
}];
const rets = items.reduce((tmp, x) => {
Object.keys(x).forEach((y) => {
tmp.keys = `${tmp.keys}${tmp.keys.length ? ',' : ''}${y}`;
tmp.values = `${tmp.values}${tmp.values.length ? ',' : ''}${y}`;
});
return tmp;
}, {
keys: [],
values: [],
});
console.log(rets);
With the for..in and for..of functions
const items = [{1:"Banana"}, {2:"Orange"}, {3:"Apple"}];
let keys = [];
let values = [];
for(let item of items){
for(let key in item){
keys.push(key);
values.push(item[key]);
}
}
// keys == [1, 2, 3]
// values == [Banana, Orange, Apple]
After this you can use the join function
console.log(keys.join()); // Output : "1,2,3"
console.log(values.join()); // Output : "Orange,Banana,Apple"

Convert set of object's into array item

I have several objects like this:
{'id[0]': 2}
{'url[0]': 11}
{'id[1]': 3}
{'url[1]': 14}
And I want to get something like this:
[{id:2, url:11}, {id:3, url:14}]
Also I have lodash in my project. Maybe lodash have some method for this?
You could use a regular expression for the keys and create a new object if necessary. Then assign the value to the key.
var data = [{ 'id[0]': 2 }, { 'url[0]': 11 }, { 'id[1]': 3 }, { 'url[1]': 14 }],
result = [];
data.forEach(function (a) {
Object.keys(a).forEach(function (k) {
var keys = k.match(/^([^\[]+)\[(\d+)\]$/);
if (keys.length === 3) {
result[keys[2]] = result[keys[2]] || {};
result[keys[2]][keys[1]] = a[k];
}
});
});
console.log(result);
This is an ES6 solution based on #NinaScholz solution.
I assume that the objects have only one property each, like the ones presented in the question.
Combine the array of objects to one large object using Object#assign, and convert to entries with Object.entries.
Iterate the array using Array#reduce.
Extract the original key an value from each entry using array
destructuring.
Extract the wanted key and index using a regex and array
destructuring.
Then create/update the new object at the index using object spread.
const data = [{ 'id[0]': 2 }, { 'url[0]': 11 }, { 'id[1]': 3 }, { 'url[1]': 14 }];
// combine to one object, and convert to entries
const result = Object.entries(Object.assign({}, ...data))
// extract the original key and value
.reduce((r, [k, value]) => {
// extract the key and index while ignoring the full match
const [, key, index] = k.match(/^([^\[]+)\[(\d+)\]$/);
// create/update the object at the index
r[index] = {...(r[index] || {}), [key]: value };
return r;
}, []);
console.log(result);
var arr = [{'id[0]': 2},
{'url[0]': 11},
{'id[1]': 3},
{'url[1]': 14}];
var result = [];
arr.forEach(function(e, i, a){
var index = +Object.keys(e)[0].split('[')[1].split(']')[0];//get the number inside []
result[index] = result[index] || {}; //if item is undefined make it empty object
result[index][Object.keys(e)[0].split('[')[0]] = e[Object.keys(e)[0]];//add item to object
})
console.log(result);
You can use for loop, .filter(), RegExp constructor with parameter "\["+i+"\]" where i is current index, Object.keys(), .reduce(), .replace() with RegExp /\[\d+\]/
var obj = [{
"id[0]": 2
}, {
"url[0]": 11
}, {
"id[1]": 3
}, {
"url[1]": 14
}];
var res = [];
for (var i = 0; i < obj.length / 2; i++) {
res[i] = obj.filter(function(o) {
return new RegExp("\[" + i + "\]").test(Object.keys(o))
})
.reduce(function(obj, o) {
var key = Object.keys(o).pop();
obj[key.replace(/\[\d+\]/, "")] = o[key];
return obj
}, {})
}
console.log(res);

Categories

Resources