Underscore.js nested groupings - javascript

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();

Related

Array to map based on object property containing array items

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.

Merge array of objects with underscore

I have array of objects like this. And they have duplicated property 'contactName' values
[
{
categoryId:1
categoryName:"Default"
contactId:141
contactName:"Anonymous"
name:"Mobile"
value:"+4417087654"
},
{
categoryId:1
categoryName:"Default"
contactId:325
contactName:"Anonymous"
name:"Email"
value:"test2#gmail.com"
},
{
categoryId:1
categoryName:"Default"
contactId:333
contactName:"Anonymous"
name:"Email"
value:"ivdtest#test.com"
}
]
I want to merge them in one object by the name of property 'contactName'
To something like this
[
{
categoryId: 1,
categoryName: "Default",
contactId: 141,
contactName: "Anonymous",
names: {
1: "Mobile",
2: "Email",
3: "Email"
},
values: {
1: '+2234324',
2: "ivdtest#test.com",
3: "test2#gmail.com"
}
}
];
Edit: How can I group objects also by categoryName ?
var grouped = _.groupBy(input, 'contactName');
var output = _.map(grouped, function(entries) {
return _.extend(
_.pick(entries[0], 'categoryId', 'categoryName', 'contactId', 'contactName'),
{
names: _.indexBy(_.pluck(entries, 'name'), function(val, index) { return index +1; }),
values: _.indexBy(_.pluck(entries, 'value'), function(val, index) { return index +1; })
}
);
});
https://jsfiddle.net/f1x4tscu/3/
Another variant with array inside the object
var grouped = _.groupBy(this.contacts, 'contactName');
var output = _.map(grouped, function (entries) {
return _.extend(
_.pick(entries[0], 'categoryId', 'categoryName', 'contactId', 'contactName'),
{
addresses: _.map(entries, function (m) {
return {
name: m.name,
value: m.value
}
}),
}
);
});

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

Removing specific key-value pairs from a document/object

I have a document that looks like something like this:
{
name: "Johnny Boy",
age: 24,
hobbies: ["fencing", "chess", "raves"],
_createdAt: 2015-05-15T18:12:26.731Z,
_createdBy: "JohnnyBoy",
_id: mQW4G5yEfZtsB6pcN
}
My goal is to return everything that doesn't start with an underscore, and format it a bit differently so I will end up with this:
[
{
fieldName: "name",
value: "Johnny Boy"
},
{
fieldName: "age",
value: 24
},
{
fieldName: "hobbies",
value: ["fencing", "chess", "raves"]
}
]
My initial solution was to run it through the _.map function of the Underscore library (which has nothing to do with my wanting to remove underscores specifically...) and then using lastIndexOf to figure out which keys begin with an underscore:
var listWithoutUnderscores = _.map(myList, function(val, key) {
if (key.lastIndexOf("_", 0) === -1)
return {fieldName: key, value: val}
return null
})
However, this will literally return null instead of the fields that began with _ in the returned array:
[
...
{
fieldname: "hobbies",
value: ["fencing", "chess", "raves"]
},
null,
null,
null
]
I want to remove them completely, ideally within the map function itself, or else by chaining it through some kind of filter but I don't know which one is fastest in this case.
You can use reduce for this:
var listWithoutUnderscores = _.reduce(myList, function(list, val, key) {
if (key.lastIndexOf("_", 0) === -1){
list.push( {fieldName: key, value: val});
}
return list;
}, []);
Underscore also comes with an array method compact which will remove all falsey and null values from an array:
_.compact([0, 1, false, 2, '', null, 3]);
=> [1, 2, 3]
You could just call _.compact(array) on the array after your map.
You can use pick and pass a predicate to get the valid keys and then map across those fields:
var validKey = function(value, key){
return _.first(key) != '_';
}
var createField = function(value, key){
return {
fieldname: key,
value: value
}
}
var result = _.chain(data)
.pick(validKey)
.map(createField)
.value();
var data = {
name: "Johnny Boy",
age: 24,
hobbies: ["fencing", "chess", "raves"],
_createdAt: '2015-05-15T18:12:26.731Z',
_createdBy: "JohnnyBoy",
_id: 'mQW4G5yEfZtsB6pcN'
}
var validKey = function(value, key){
return _.first(key) != '_';
}
var createField = function(value, key){
return {
fieldname: key,
value: value
}
}
var result = _.chain(data)
.pick(validKey)
.map(createField)
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

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: []});

Categories

Resources