Array to map based on object property containing array items - javascript

If i have a object like following
result = [
{
phones : ["ABC", "DEF"],
name: "Simon"
},
{
phones : ["ABC", "XZY"],
name: "John"
}
]
Expected output
Map of key, value
{ABC, ["Simon", "John"]}
{DEF, ["Simon"]}
{XYZ, ["John"]}
My try
map: Map = new Map();
for ( r of result ) {
for( phone of r.phones) {
if(map.get(phone)){
map.put(phone, map.get(phone).concat(r.name))
} else {
map.put(phone, r.name);
}
}
}
Is there a ES6 way to perform the above function ?

Using Array.prototype.reduce, you can do this.
const input = [{
phones: ["ABC", "DEF"],
name: "Simon"
},
{
phones: ["ABC", "XZY"],
name: "John"
}];
const result = input.reduce((acc, cur) => {
cur.phones.forEach((item) => {
acc[item] ? acc[item].push(cur.name) : acc[item] = [ cur.name ];
});
return acc;
}, {});
console.log(result);

I don't know if it's ES6 enough, but using map-reduce gives something like this:
result.reduce((map, person) => {
person.phones.forEach(phone =>
map.has(phone)
? map.get(phone).push(person.name)
: map.set(phone, [person.name])
);
return map;
}, new Map());
But the code is more or less the same, the performance are probably not that far off, and readability is in the eye of the reader.
In short, your mileage may vary.

Related

How to add duplicate key elements in JavaScript Object?

I want to make the following newArray using by following testArray.
newArray↓
let newArray = [
{section:"business",name:["Bob","John"]},
{section:"H&R",name:["Jen","Bobby"]},
]
testArray↓
let test = [
{section:"business",name:"Bob"},
{section:"business",name:"John"},
{section:"H&R",name:"Jen"},
{section:"H&R",name:"Bobby"},
]
First of all, I tried to find some key elements using by filter method like below.
let newArray = test.filter((x:any,i,self)=>
self.indexOf(x.section)===i
)
but this code output is [].
So, how do I make code to get my expected output?
Does anyone help me?
Use Array.reduce():
let test=[{section:"business",name:"Bob"},{section:"business",name:"John"},{section:"H&R",name:"Jen"},{section:"H&R",name:"Bobby"}];
let newArray = test.reduce((acc,cur) => {
if(acc.some(el => el.section === cur.section)){
acc.forEach((el,idx) => {
if(el.section === cur.section){
acc[idx].name.push(cur.name)
}
})
}else{
cur.name = [cur.name]
acc.push(cur)
}
return acc
},[])
console.log(newArray)
you can try this
const test = [{
section: "business",
name: "Bob"
},
{
section: "business",
name: "John"
},
{
section: "H&R",
name: "Jen"
},
{
section: "H&R",
name: "Bobby"
},
];
// gather sections
const sections = {};
test.forEach(t => {
sections[t.section] = sections[t.section] || [];
sections[t.section].push(t.name);
});
// convert sessions to array
const newArray = Object.keys(sections).map(k => {
return {
section: k,
name: sections[k]
};
});
console.log(newArray);

Converting to Correct Object Format from Array

I am aware of options like the spread operator and Object.assign() when it comes to converting an array to an object, however, I am having difficulty figuring out how to format the final object in the format I need.
My original array looks like this:
let propsArray = [ { id: '1' },
{ 'name.first': 'john' },
{ 'name.last': 'smith' } ]
The object I want from this data needs to look like this:
{
"id" : 1,
"name" : {
"first" : "john",
"last" : "smith"
}
}
What I've tried so far, using object.assign(), ends up adding numbers as property keys in the resulting object, which is clearly not what I'm looking for:
let finalObj = Object.assign({}, propsArray);
How can I get the resulting object formatted the way I need here?
You need a deeper look into the object and take the splitted key and reduce them by taking an object with the property or an empty object. At the end assign the value.
var data = [{ id: '1' }, { 'name.first': 'john' }, { 'name.last': 'smith' }],
result = data.reduce((r, o) => {
Object.entries(o).forEach(([k, v]) => {
var keys = k.split('.'),
last = keys.pop();
keys.reduce((q, k) => q[k] = q[k] || {}, r)[last] = v;
});
return r;
}, {});
console.log(result);
Try this:
const data = [
{
"id": "1"
},
{
"name.first": "john"
},
{
"name.last": "smith"
}
]
const result = Object.entries(Object.assign({}, ...data))
.reduce((acc,[k,v])=>{
[first, last] = k.split('.');
if (!last) {
acc[k] = v
} else {
acc[first] = acc[first] || {};
acc[first][last] = v;
}
return acc
}, {})
console.log(result);

How to do equivalent of LINQ SelectMany() just in javascript

Unfortunately, I don't have JQuery or Underscore, just pure javascript (IE9 compatible).
I'm wanting the equivalent of SelectMany() from LINQ functionality.
// SelectMany flattens it to just a list of phone numbers.
IEnumerable<PhoneNumber> phoneNumbers = people.SelectMany(p => p.PhoneNumbers);
Can I do it?
EDIT:
Thanks to answers, I got this working:
var petOwners =
[
{
Name: "Higa, Sidney", Pets: ["Scruffy", "Sam"]
},
{
Name: "Ashkenazi, Ronen", Pets: ["Walker", "Sugar"]
},
{
Name: "Price, Vernette", Pets: ["Scratches", "Diesel"]
},
];
function property(key){return function(x){return x[key];}}
function flatten(a,b){return a.concat(b);}
var allPets = petOwners.map(property("Pets")).reduce(flatten,[]);
console.log(petOwners[0].Pets[0]);
console.log(allPets.length); // 6
var allPets2 = petOwners.map(function(p){ return p.Pets; }).reduce(function(a, b){ return a.concat(b); },[]); // all in one line
console.log(allPets2.length); // 6
for a simple select you can use the reduce function of Array.
Lets say you have an array of arrays of numbers:
var arr = [[1,2],[3, 4]];
arr.reduce(function(a, b){ return a.concat(b); }, []);
=> [1,2,3,4]
var arr = [{ name: "name1", phoneNumbers : [5551111, 5552222]},{ name: "name2",phoneNumbers : [5553333] }];
arr.map(function(p){ return p.phoneNumbers; })
.reduce(function(a, b){ return a.concat(b); }, [])
=> [5551111, 5552222, 5553333]
Edit:
since es6 flatMap has been added to the Array prototype.
SelectMany is synonym to flatMap.
The method first maps each element using a mapping function, then flattens the result into a new array.
Its simplified signature in TypeScript is:
function flatMap<A, B>(f: (value: A) => B[]): B[]
In order to achieve the task we just need to flatMap each element to phoneNumbers
arr.flatMap(a => a.phoneNumbers);
As a simpler option Array.prototype.flatMap() or Array.prototype.flat()
const data = [
{id: 1, name: 'Dummy Data1', details: [{id: 1, name: 'Dummy Data1 Details'}, {id: 1, name: 'Dummy Data1 Details2'}]},
{id: 1, name: 'Dummy Data2', details: [{id: 2, name: 'Dummy Data2 Details'}, {id: 1, name: 'Dummy Data2 Details2'}]},
{id: 1, name: 'Dummy Data3', details: [{id: 3, name: 'Dummy Data3 Details'}, {id: 1, name: 'Dummy Data3 Details2'}]},
]
const result = data.flatMap(a => a.details); // or data.map(a => a.details).flat(1);
console.log(result)
For those a while later, understanding javascript but still want a simple Typed SelectMany method in Typescript:
function selectMany<TIn, TOut>(input: TIn[], selectListFn: (t: TIn) => TOut[]): TOut[] {
return input.reduce((out, inx) => {
out.push(...selectListFn(inx));
return out;
}, new Array<TOut>());
}
Sagi is correct in using the concat method to flatten an array. But to get something similar to this example, you would also need a map for the select part
https://msdn.microsoft.com/library/bb534336(v=vs.100).aspx
/* arr is something like this from the example PetOwner[] petOwners =
{ new PetOwner { Name="Higa, Sidney",
Pets = new List<string>{ "Scruffy", "Sam" } },
new PetOwner { Name="Ashkenazi, Ronen",
Pets = new List<string>{ "Walker", "Sugar" } },
new PetOwner { Name="Price, Vernette",
Pets = new List<string>{ "Scratches", "Diesel" } } }; */
function property(key){return function(x){return x[key];}}
function flatten(a,b){return a.concat(b);}
arr.map(property("pets")).reduce(flatten,[])
// you can save this function in a common js file of your project
function selectMany(f){
return function (acc,b) {
return acc.concat(f(b))
}
}
var ex1 = [{items:[1,2]},{items:[4,"asda"]}];
var ex2 = [[1,2,3],[4,5]]
var ex3 = []
var ex4 = [{nodes:["1","v"]}]
Let's start
ex1.reduce(selectMany(x=>x.items),[])
=> [1, 2, 4, "asda"]
ex2.reduce(selectMany(x=>x),[])
=> [1, 2, 3, 4, 5]
ex3.reduce(selectMany(x=> "this will not be called" ),[])
=> []
ex4.reduce(selectMany(x=> x.nodes ),[])
=> ["1", "v"]
NOTE: use valid array (non null) as intitial value in the reduce function
try this (with es6):
Array.prototype.SelectMany = function (keyGetter) {
return this.map(x=>keyGetter(x)).reduce((a, b) => a.concat(b));
}
example array :
var juices=[
{key:"apple",data:[1,2,3]},
{key:"banana",data:[4,5,6]},
{key:"orange",data:[7,8,9]}
]
using :
juices.SelectMany(x=>x.data)
I would do this (avoiding .concat()):
function SelectMany(array) {
var flatten = function(arr, e) {
if (e && e.length)
return e.reduce(flatten, arr);
else
arr.push(e);
return arr;
};
return array.reduce(flatten, []);
}
var nestedArray = [1,2,[3,4,[5,6,7],8],9,10];
console.log(SelectMany(nestedArray)) //[1,2,3,4,5,6,7,8,9,10]
If you don't want to use .reduce():
function SelectMany(array, arr = []) {
for (let item of array) {
if (item && item.length)
arr = SelectMany(item, arr);
else
arr.push(item);
}
return arr;
}
If you want to use .forEach():
function SelectMany(array, arr = []) {
array.forEach(e => {
if (e && e.length)
arr = SelectMany(e, arr);
else
arr.push(e);
});
return arr;
}
Here you go, a rewritten version of joel-harkes' answer in TypeScript as an extension, usable on any array. So you can literally use it like somearray.selectMany(c=>c.someprop). Trans-piled, this is javascript.
declare global {
interface Array<T> {
selectMany<TIn, TOut>(selectListFn: (t: TIn) => TOut[]): TOut[];
}
}
Array.prototype.selectMany = function <TIn, TOut>( selectListFn: (t: TIn) => TOut[]): TOut[] {
return this.reduce((out, inx) => {
out.push(...selectListFn(inx));
return out;
}, new Array<TOut>());
}
export { };
You can try the manipula package that implements all C# LINQ methods and preserves its syntax:
Manipula.from(petOwners).selectMany(x=>x.Pets).toArray()
https://github.com/litichevskiydv/manipula
https://www.npmjs.com/package/manipula
For later versions of JavaScript you can do this:
var petOwners = [
{
Name: 'Higa, Sidney',
Pets: ['Scruffy', 'Sam']
},
{
Name: 'Ashkenazi, Ronen',
Pets: ['Walker', 'Sugar']
},
{
Name: 'Price, Vernette',
Pets: ['Scratches', 'Diesel']
}
];
var arrayOfArrays = petOwners.map(po => po.Pets);
var allPets = [].concat(...arrayOfArrays);
console.log(allPets); // ["Scruffy","Sam","Walker","Sugar","Scratches","Diesel"]
See example StackBlitz.
Exception to reduce and concat methods, you can use the native flatMap api.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap

Is there a method to split an array of objects into groups?

Suppose I have an array of objects with some sort of groupable key:
var people = [
{ 'name':'Alice', 'gender': 'female' },
{ 'name':'Bob', 'gender': 'male' },
{ 'name':'Jeremy', 'gender': 'male' },
{ 'name':'Jess', 'gender': 'female' },
{ 'name':'Seymour', 'gender': 'female' },
];
Is there a (native) function/method that can be applied to the array to 'unzip' the array into two arrays, like so:
boysAndGirls = people.[**something**]('gender');
That could result in:
{
'male': [ ... ],
'female': [ ... ]
}
or even:
[
[ {Bob, ...}, {Jeremy, ...}, {Seymour, ...} ], // 'males' array
[ {Alice, ...}, {Jess, ...} ] // 'female' array
]
I could write this algorithm myself, but I really just want to know if there is a native array method -- or one that might exist in another language that could be polyfilled in?
const groupByAge = users.reduce((p,c) =>{
const genKey = Math.floor(c.age/10);
const key = `${genKey}0- ${genKey}9`;
if(!p[key]){
p[key] =[];
}
p[key].push(c);
return p;
}, {})
console.log(groupByAge);
There is no such method in JavaScript. Ruby has it in Enumerator#group_by:
people.group_by { |person| person['gender'] }
and it is easy enough to write in JavaScript as well. In fact, some libraries have it already, e.g. Lodash.
_.groupBy(people, function(person) {
return person['gender'];
});
If you write it yourself, you can customise it a bit:
function groupByProp(array, prop) {
var result = {};
array.forEach(function(item) {
var val = item[prop];
if (!result[val]) result[val] = [item];
else result[val].push(item);
});
return result;
}
groupByProp(people, 'gender');
There is not a native Javascript function for this but you can use the following code:
var originalArray = [1,2,3,4,5,6,7,8,9];
var splitArray = function (arr, size) {
var arr2 = arr.slice(0),
arrays = [];
while (arr2.length > 0) {
arrays.push(arr2.splice(0, size));
}
return arrays;
}
splitArrays = splitArray(originalArray, 2);
console.log(splitArrays);
The nearest thing I can think of off the top of my head for a native solution is to use reduce. It's not as simple as what you are looking for but it works:
var boysAndGirls = people.reduce(function(obj, item) {
obj[item.gender].push(item.name);
return obj;
}, {male: [], female: []});

Underscore.js nested groupings

I'm having trouble getting the output I need from the following json
[
{"Type":"A","Location":"1"},
{"Type":"A","Location":"2"},
{"Type":"A","Location":"3"},
{"Type":"B","Location":"2"},
{"Type":"B","Location":"3"},
{"Type":"C","Location":"1"},
{"Type":"A","Location":"1"},
{"Type":"A","Location":"1"},
{"Type":"A","Location":"3"},
{"Type":"C","Location":"1"},
{"Type":"C","Location":"1"},
{"Type":"C","Location":"1"}
]
my expected output is as follows:
[
{"Type":"A","Count":"6","Locations":
[
{"Location":"1","Count":"3"},
{"Location":"2","Count":"1"},
{"Location":"3","Count":"2"}
]
},
{"Type":"B","Count":"2","Locations":
[
{"Location":"2","Count":"1"},
{"Location":"3","Count":"1"}
]
},
{"Type":"C","Count":"4","Locations":
[
{"Location":"1","Count":"4"}
]
}
]
The code I have so far will group the locations and give me the counts, but I'm stuck with the inner group
var result = _.chain($scope.incidents)
.groupBy("Type")
.map(function(value,key){
return{
Type:key,
Count:value.length
}
}).value();
Try this:
var result = _.chain($scope.incidents).groupBy('Type').map(function(value, key) {
return {
Count: value.length,
Type: key,
Locations: _.chain(value).groupBy('Location').map(function(value, key) {
return {
Location: key,
Count: value.length
};
}).value()
};
}).value();
I'm assuming you're using Lo-Dash, as underscore didn't seem to have the groupBy(<string>) feature.
In any case, here's a solution:
var result = _(list)
.groupBy('Type')
.map(function (locations, type) {
return {
Type: type,
Count: locations.length,
Locations: _(locations)
.groupBy('Location')
.map(function (arr, location) {
return {
Count: arr.length,
Location: location
};
}).value()
};
}).value();

Categories

Resources