Iterating on nested arrays in objects to collate informaton - javascript

I have the following array of objects:
let array = [
{key: 'Newcastle', values[
{key: 'ID1000', values[
{name: 'Jeff', cust_id: "ID1000", status: 'sold'},
{name: 'Jeff', cust_id: "ID1000", status: 'pending'},
]}
{key: 'ID2000', values [
{name: 'Bob', cust_id: "ID2000", status: 'sold'}
]}
]}
{key: 'London', values [
{key: 'ID3000', values[
{name: 'Gary', cust_id: "ID3000", status: 'sold'},
]}
{key: 'ID4000', values[
{name: 'Mary', cust_id: "ID4000", status: 'interest'},
{name: 'Mary', cust_id: "ID4000", status: 'interest'},
{name: 'Mary', cust_id: "ID4000", status: 'pending'},
]}
]}
]
I have been trying to refactor it into something like:
[
{Location: 'Newcastle, customers: 2, sold: 2, pending: 1, interest: 0},
{Location: 'London', customers: 2, sold: 1, pending: 1, interest: 2}
]
So I am attempting to count the number of status events and collate them accordingly.
I get lost when I try to iterate on the nested arrays and then when trying to bubble up the results of the iteration to a final object. The closest I have got is:
function transform(array) {
let arr = []
array.forEach(function(x) {
function soldCount() {
x.values.forEach(function(x) {
let sold = x.values.forEach(function(x) {
let soldTrue = 0
if (x.status === "sold") {
soldTrue++
}
console.log(soldTrue)
if (soldTrue > 0) {
return soldTrue
}
})
})
}
let obj = {
location: x.key,
customers: x.values.length,
sold: soldCount()
}
arr.push(obj)
})
return arr
}
This tries to iterate on each array in the objects and attempts to return a number for how many of the 'sold' status it finds. The console statement does return a number but it returns multiple entries for each item in the array due to 'forEach'.
I am swamped in a number of forEach loops iterating on nested arrays. I suspect that this might not be the correct methodology for what I am trying to achieve.

You could take an object counting status and build a new objects.
var array = [{ key: 'Newcastle', values: [{ key: 'ID1000', values: [{ name: 'Jeff', cust_id: "ID1000", status: 'sold' }, { name: 'Jeff', cust_id: "ID1000", status: 'pending' },] }, { key: 'ID2000', values: [{ name: 'Bob', cust_id: "ID2000", status: 'sold' }] }] }, { key: 'London', values: [{ key: 'ID3000', values: [{ name: 'Gary', cust_id: "ID3000", status: 'sold' },] }, { key: 'ID4000', values: [{ name: 'Mary', cust_id: "ID4000", status: 'interest' }, { name: 'Mary', cust_id: "ID4000", status: 'interest' }, { name: 'Mary', cust_id: "ID4000", status: 'pending' }] }] }],
result = array.map(({ key: Location, values }) => {
var data = { Location, customers: 0, sold: 0, pending: 0, interest: 0 };
values.forEach(({ values }) => {
data.customers++;
values.forEach(({ status }) => data[status]++);
});
return data;
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Look at this:
let array = [
{
key: 'Newcastle',
values: [
{key: 'ID1000',
values:[
{name: 'Jeff', cust_id: "ID1000", status: 'sold'},
{name: 'Jeff', cust_id: "ID1000", status: 'pending'},
]},
{
key: 'ID2000',
values :[
{name: 'Bob', cust_id: "ID2000", status: 'sold'}
]
}
]},
{key: 'London', values :[
{key: 'ID3000', values:[
{name: 'Gary', cust_id: "ID3000", status: 'sold'},
]},
{key: 'ID4000', values:[
{name: 'Mary', cust_id: "ID4000", status: 'interest'},
{name: 'Mary', cust_id: "ID4000", status: 'interest'},
{name: 'Mary', cust_id: "ID4000", status: 'pending'},
]}
]}
]
function countProp(arr, prop) {
let n = 0;
arr.forEach((items) => {
items.values.forEach(item => {
if (item.status === prop) {
n += 1;
}
})
});
return n;
}
const n = array.map(item => {
const a = {};
a.customers = item.values.length;
a.location = item.key;
a.sold = countProp(item.values, "sold");
a.pending = countProp(item.values, "pending");
a.interest = countProp(item.values, "interest");
return a;
});
This gives
[
{Location: 'Newcastle, customers: 2, sold: 2, pending: 1, interest: 0},
{Location: 'London', customers: 2, sold: 1, pending: 1, interest: 2}
]

Assumingly you are using let.
I have written my solution in ES6, getting Location, customer count is easy.
The challenging part would be the accumulating the status count per type of status, you can use reduce with object as initial value and using the status types as keys with 0 initial value, which shown in my solution
let array = [
{key: 'Newcastle', values: [
{key: 'ID1000', values: [
{name: 'Jeff', cust_id: "ID1000", status: 'sold'},
{name: 'Jeff', cust_id: "ID1000", status: 'pending'}
]},
{key: 'ID2000', values: [
{name: 'Bob', cust_id: "ID2000", status: 'sold'}
]}
]},
{key: 'London', values: [
{key: 'ID3000', values: [
{name: 'Gary', cust_id: "ID3000", status: 'sold'}
]},
{key: 'ID4000', values:[
{name: 'Mary', cust_id: "ID4000", status: 'interest'},
{name: 'Mary', cust_id: "ID4000", status: 'interest'},
{name: 'Mary', cust_id: "ID4000", status: 'pending'}
]}
]}
];
const newArray = array.map((a) => {
const Location = a.key;
const customer = a.values.length;
const status = a.values.reduce((acc, value) => {
value.values.forEach((v1) => {
acc[v1.status] = acc[v1.status] + 1;
});
return acc;
}, {sold: 0, pending: 0, interest: 0});
return {Location, customer, ...status};
});
console.log(newArray);

Related

How to Create Arrays of Objects With A Common Prop Value

Let's say I have an array of objects like:
flattenedObjects = [
{name: 'Bill', city: 1},
{name: 'Guillermo', city: 1},
{name: 'Wilhem', city: 1},
{name: 'William', city: 1},
{name: 'Nick', city: 2},
{name: 'Nicolas', city: 2},
{name: 'Nicholas', city: 2},
{name: 'Rick', city: 3}
]
I want to create individual arrays of objects grouped by "city". In the code, I will also deconstruct each object so that the final output will be:
boston = ['Bill', 'Guillermo', 'Wilhelm', 'William']
miami = ['Nick', 'Nickolas', 'Nicholas']
london = ['Rick']
I am having difficulties creating the grouped array of objects.
I can do it with one single object, as such:
let boston = flattenedObjects.filter(function (obj) {
return obj.city == 1;
});
What I was thinking of doing was to take a iterate through an object and filtering dynamically, like so:
let cities = {
boston: 1,
miami: 2,
london: 3
}
And then trying something like:
let newObj = flattenedObjects.filter(function (x) {
let obj = {};
Object.entries(cities).forEach(([key, value]) => {
obj["name"] = `${key}`;
obj["city"] = x.city == `${value}`;
return obj;
});
});
This isn't consoling what is expected. It's just an array of objects very similar to the "obj" up above.
let flattenedObjects = [
{name: 'Bill',city: 1},
{name: 'Guillermo',city: 1},
{name: 'Wilhem',city: 1},
{name: 'William',city: 1},
{name: 'Nick',city: 2},
{name: 'Nicolas',city: 2},
{name: 'Nicholas',city: 2},
{name: 'Rick',city: 3}
];
let cities = {
boston: 1,
miami: 2,
london: 3
}
let data = {}
for (const [key, value] of Object.entries(cities)) {
data[key] = flattenedObjects.filter(p => p.city === value).map(e => e.name);
}
console.log(data)
Create a reverse map of city code to city name, O(1) constant time lookups.
Reduce the flattenedObjects array into an object using the city name as a key and generate an array fo the names, O(n) linear access.
const flattenedObjects = [
{ name: "Bill", city: 1 },
{ name: "Guillermo", city: 1 },
{ name: "Wilhem", city: 1 },
{ name: "William", city: 1 },
{ name: "Nick", city: 2 },
{ name: "Nicolas", city: 2 },
{ name: "Nicholas", city: 2 },
{ name: "Rick", city: 3 }
];
const cities = {
boston: 1,
miami: 2,
london: 3
};
const citiesByCode = Object.fromEntries(
Object.entries(cities).map(([city, code]) => [code, city])
);
const groupedResult = flattenedObjects.reduce((groups, current) => {
const cityCode = citiesByCode[current.city];
if (!groups[cityCode]) groups[cityCode] = [];
groups[cityCode].push(current.name);
return groups;
}, {});
console.log(groupedResult);
You could use a reduce statement to reduce the flattenedObjects array into a single object in the format that you want.
const flattenedObjects = [
{name: 'Bill', city: 1},
{name: 'Guillermo', city: 1},
{name: 'Wilhem', city: 1},
{name: 'William', city: 1},
{name: 'Nick', city: 2},
{name: 'Nicolas', city: 2},
{name: 'Nicholas', city: 2},
{name: 'Rick', city: 3},
];
// the keys are the city number rather than city name
const cities = {
1: 'boston',
2: 'miami',
3: 'london',
};
const obj = flattenedObjects.reduce((o, flattenedObject) => {
const cityName = cities[flattenedObject.city];
if (o[cityName] === undefined) {
o[cityName] = [];
}
o[cityName].push(flattenedObject.name);
return o;
}, {});
console.log(obj);

How can I output multiple results from an array

I have this array of objects, that initially only had name and unit fields. I recently added a unitConv field of array type.
I used to output an array of strings with the name and unit from every object.
const ingredients = [
{name: 'wine', unit: 'ml', unitConv: []},
{name: 'salt', unit: 'gr', unitConv: [{unitMeasure: { name: 'spoon'}}, {unitMeasure: { name: 'tea-spoon'}}]},
{name: 'onion', unit: 'piece', unitConv: []},
]
const response = ingredients.map(ing => `${ing.name} [${ing.unit}]`)
And this is the response:
["wine [ml]", "salt [gr]", "onion [piece]"]
Now that I added unitConv, I want to see if any unitConv are available in the object, and pass those too, as options, like this:
["wine [ml]", "salt [gr]", "onion [piece]", "salt[spoon]", "salt[tea-spoon]"]
And I want to keep the initial value of salt too, the one the uses the 'gr' as a unit. So for salt, because I have one unit and two unitConv, I want to output it three times, with each of this options.
If one of the objects doesn't have unitConv, the unitConv fields will appear as an empty array, like in the example above.
You can use Array#flatMap to create the second array to concatenate with the first.
const ingredients = [
{name: 'wine', unit: 'ml', unitConv: []},
{name: 'salt', unit: 'gr', unitConv: [{unitMeasure: { name: 'spoon'}}, {unitMeasure: { name: 'tea-spoon'}}]},
{name: 'onion', unit: 'piece', unitConv: []},
]
const response = ingredients.map(ing => `${ing.name} [${ing.unit}]`)
.concat(ingredients.flatMap(({name, unitConv})=>
unitConv.map(x => `${name} [${x.unitMeasure.name}]`)));
console.log(response);
a simple array reduce method:
const ingredients =
[ { name: 'wine', unit: 'ml', unitConv: [] }
, { name: 'salt', unit: 'gr', unitConv:
[ { unitMeasure: { name: 'spoon' } }
, { unitMeasure: { name: 'tea-spoon' } }
]
}
, { name: 'onion', unit: 'piece', unitConv: [] }
]
const resp2 = ingredients.reduce((resp,{name,unit,unitConv}) =>
{
resp.push( `${name} [${unit}]` )
unitConv.forEach(({unitMeasure}) =>
resp.push(`${name} [${unitMeasure.name}]`))
return resp
},[])
console.log( resp2 )
.as-console-wrapper{max-height:100% !important;top: 0;}
you can use flatMap and inside its callback function check for unitConv array lentgh, if it's true then you can use a map for that. here is the demo:
const ingredients = [{
name: 'wine',
unit: 'ml',
unitConv: []
},
{
name: 'salt',
unit: 'gr',
unitConv: [{
unitMeasure: {
name: 'spoon'
}
}, {
unitMeasure: {
name: 'tea-spoon'
}
}]
},
{
name: 'onion',
unit: 'piece',
unitConv: []
},
]
function strFormat(name, unit) {
return `${name} [${unit}]`;
}
const result = ingredients.flatMap((ing) => {
if (ing.unitConv.length) {
const ingAllUnits = ing.unitConv.map((unit) => strFormat(ing.name, unit.unitMeasure.name));
ingAllUnits.push(strFormat(ing.name, ing.unit));
return ingAllUnits;
} else return strFormat(ing.name, ing.unit);
});
console.log(result);

How to find matches in an array with objects and change property?

When names match in object property "name" I want to change this property to "TV(1)" for the first match and increase a counter for every next match, but it's doesn't work for me (please run code to see the problem), how I can change array with objects regarding that?
const array = [
{name: "TV", price: 12323, id: 321 },
{name: "Kettle", price: 123, id: 1211 },
{name: "TV", price: 3434, id: 3434312 },
{name: "Car", price: 12343, id: 123123123 },
{name: "TV", price: 4554, id: 2313123123311 }
]
const nameIsMatch = (name, id) => {
let count = 0
return array.map((item) => {
const condition = item.name === name && item.id !== id
const formattedName = `${item.name}(${count += 1})`
return {
price: item.price,
name: condition ? formattedName : item.name,
id: item.id
}
})
}
array.forEach((el) => {
console.log(nameIsMatch(el.name, el.id))
})
The easy fix is to just rework a tiny bit of code with the old technique of moving things around to where they belong:
return array.map((item) => {
return {
price: item.price,
name: (item.name === name && item.id !== id) ? `${item.name}(${count += 1})` : item.name,
id: item.id
}
})
It's now a bit of a mess due to the ternary, so you could always unwind that a bit:
return array.map(item => {
// Make a copy of `item`
let i = { ...item };
// Override if necessary
if (item.name === name && item.id !== id) {
i. name = `${item.name}(${count += 1})`;
}
return i;
})
Where that's not as concise, but it's a lot more clear.
Use an object that keeps a counter for each name.
const array = [
{name: "TV", price: 12323, id: 321 },
{name: "Kettle", price: 123, id: 1211 },
{name: "TV", price: 3434, id: 3434312 },
{name: "Car", price: 12343, id: 123123123 },
{name: "TV", price: 4554, id: 2313123123311 }
];
const nameIsMatch = (array) => {
let counts = {};
return array.map(({
name,
price,
id
}) => {
let count = counts[name] || 1;
const formattedName = `${name}(${count})`
counts[name] = count + 1;
return {
price,
name: formattedName,
id
}
})
}
console.log(nameIsMatch(array))
You can use the following code
const array = [
{name: "TV", price: 12323, id: 321 },
{name: "Kettle", price: 123, id: 1211 },
{name: "TV", price: 3434, id: 3434312 },
{name: "Car", price: 12343, id: 123123123 },
{name: "TV", price: 4554, id: 2313123123311 }];
const hash = {};
array.forEach(function(obj){
if(!hash[obj.name])
hash[obj.name] = 1;
obj.name = `${obj.name}(${hash[obj.name]++})`;
})
console.log(array)
const array = [
{name: "TV", price: 12323, id: 321 },
{name: "Kettle", price: 123, id: 1211 },
{name: "TV", price: 3434, id: 3434312 },
{name: "Car", price: 12343, id: 123123123 },
{name: "TV", price: 4554, id: 2313123123311 }];
const result = array.map((data, index) => {
if(data.name === 'TV') {
return {...data, name:'TV'+'('+index+')'}
}
return data
})
console.info(result)
You have to use like this It's working for me as per your output
you could actually do it one by one even though this step will take a lot of time, however this will gives you a better understanding.
Data
const array = [
{name: "TV", price: 12323, id: 321 },
{name: "Kettle", price: 123, id: 1211 },
{name: "TV", price: 3434, id: 3434312 },
{name: "Car", price: 12343, id: 123123123 },
{name: "TV", price: 4554, id: 2313123123311 }
]
function to get all the Data Name. This will return [ 'TV', 'Kettle', 'Car' ]
function getAllDataName(arr){
let a = arr.map((dataName) => dataName["name"] )
let b = []
a.filter((dataName) => {
// includes() used to find the intersect of array with particular data
if(!b.includes(dataName)){
b.push(dataName)
}
})
return b
}
function to map all the Data Name accordingly
function remapDataName(arr, dataName){
let s = [...arr] //
console.log(s)
console.log(dataName)
dataName.forEach(function(val, counter = 0){
counter = 0
s.forEach(function(data){
if(data["name"] == val){
data["name"] += "(" + counter++ + ")"
}
})
})
return s
}
call those functions
let objectName = getAllDataName(array)
let objectNameMapped = remapDataName(array, objectName)
objectNameMapped will print
[
{ name: 'TV(0)', price: 12323, id: 321 },
{ name: 'Kettle(0)', price: 123, id: 1211 },
{ name: 'TV(1)', price: 3434, id: 3434312 },
{ name: 'Car(0)', price: 12343, id: 123123123 },
{ name: 'TV(2)', price: 4554, id: 2313123123311 }
]
JavaScript:
const array = [
{ name: 'TV', price: 12323, id: 321 },
{ name: 'Kettle', price: 123, id: 1211 },
{ name: 'TV', price: 3434, id: 3434312 },
{ name: 'Car', price: 12343, id: 123123123 },
{ name: 'TV', price: 4554, id: 2313123123311 },
];
const tv_array = [];
let count = 1
for (let i = 0; i < array.length; i++) {
let name = array[i].name;
if (name === 'TV') {
tv_array.push({
name: `TV(${count})`,
price: array[i].price,
id: array[i].id,
})
count++
} else {
tv_array.push(array[i]);
}
}
console.log(tv_array);
Output:
[
{ name: 'TV(1)', price: 12323, id: 321 },
{ name: 'Kettle', price: 123, id: 1211 },
{ name: 'TV(2)', price: 3434, id: 3434312 },
{ name: 'Car', price: 12343, id: 123123123 },
{ name: 'TV(3)', price: 4554, id: 2313123123311 }
]

How to use find() method on nested arrays?

In this example you can see that find method works fine in this array:
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5}
];
console.log(inventory.find(fruit => fruit.name === 'cherries'));
// { name: 'cherries', quantity: 5 }
Once I add one more level and trying to find item in it it just dont find it, it shows undefined:
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
console.log(inventory.find(fruit => fruit.name === 'bing'));
// undefined
// should be: { name: 'bing', quantity: 2 }
So I guess there is some other way to do this, but i dont know it and cant find nothing.
Your code isn't allowing for the optional type array. Assuming you want to do a depth-first search, you'd make your callback a named function and use it recursively, see comments:
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
// Define the function
function find(array, name) {
// Loop the entries at this level
for (const entry of array) {
// If we found it, return it
if (entry.name === name) {
return entry;
}
// If not but there's a type array, recursively search it
if (Array.isArray(entry.type)) {
const found = find(entry.type, name);
if (found) {
// Recursive search found it, return it
return found;
}
}
}
// If execution falls off the end, it's effectively `return undefined;`
}
console.log(find(inventory, 'bing'));
inventory.find will only find element of array with given conditions in the inventory and because {name: 'bing', quantity: 2} is not present in inventory so it will return undefined
.You can do that using recursion
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
function findFruitWithName(arr,name){
let x = arr.find(fruit => fruit.name === name);
if(x === undefined){
for(let fruit of arr){
if(fruit.type) {
let y = findFruitWithName(fruit.type,name);
if(y !== undefined) return y
}
}
}
else return x;
}
console.log(findFruitWithName(inventory,'bing'))
// undefined
// should be: { name: 'bing', quantity: 2 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
You are right in that find only looks at the elements in the array and doesn't look in the nested ones, so you have to create your own.
You could loop over all the items in the inventory and collect all the elements in the potential type arrays and continue to loop over them until you find an item with the desired name, or return undefined if you check all items and you don't find a match.
Example
const inventory = [
{ name: "apples", quantity: 2 },
{ name: "bananas", quantity: 0 },
{
name: "cherries",
quantity: 5,
type: [{ name: "rainier", quantity: 3 }, { name: "bing", quantity: 2 }]
}
];
function findItem(inventory, name) {
let items = [...inventory];
let item;
while (items.length !== 0) {
item = items.pop();
if (item.name === name) {
return item;
} else if (item.type) {
items.push(...item.type);
}
}
return undefined;
}
console.log(findItem(inventory, "bing"));
try this
var inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5, type: [
{name: 'rainier', quantity: 3},
{name: 'bing', quantity: 2}
]}
];
var name = 'bing'
var result = inventory.find((fruit) => {
if(fruit.name === name) return true
if(fruit.type) return fruit.type.find(type=>type.name===name)
return false
})
console.log(result)
UPDATE:
if you want it to make it recursive you can try
function find(name, inventory) {
return inventory.find((fruit) => {
if(fruit.name === name) return true
if(fruit.type) return find(name, fruit.type)
return false
})
}
console.log(find(name,inventory))

How do I search and iterate this object for an id?

I have an object where the values are arrays and they've further objects inside them like this:
let primaryStandard = {
section1: [{name: 'andy', id: 1}, {name: 'charles', id: 2},...],
section2: [{name: 'megan', id: 55}, {name: 'derek', id: 56},...],
section3: [{name: 'robert', id: 95}, {name: 'nathan', id: 96},...],
}
Basically, I want to iterate this whole object for a particular id value and then get the name and section in an object.
Example:
For id = 95 the result should be {section: section3, name: 'robert`}
What I've tried so far:
let primaryStandard = {
section1: [{name: 'andy', id: 1}, {name: 'charles', id: 2}],
section2: [{name: 'megan', id: 55}, {name: 'derek', id: 56}],
section3: [{name: 'robert', id: 95}, {name: 'nathan', id: 96}],
}
for (let key of Object.keys(primaryStandard)) {
console.log((primaryStandard[key])) // logs the values(array)
primaryStandard[key].map(student => console.log(student)) // .map() is not defined error
}
You could do this using find method on Object.keys to get section and name.
let data = {section1: [{name: 'andy', id: 1}, {name: 'charles', id: 2}],section2: [{name: 'megan', id: 55}, {name: 'derek', id: 56}],section3: [{name: 'robert', id: 95}, {name: 'nathan', id: 96}],}
let name;
let section = Object.keys(data).find(key => {
const match = data[key].find(({id}) => id == 95);
if(match) return name = match.name
})
console.log({name, section})

Categories