When working with data from database, we often get arrays of stuff that, due to database constraints, can be (uniquely) indexed by compound indices. However, indexBy does not seem to work for compound indices, or does it?
Given an array x with objects that have properties a and b, I want to have a dictionary of dictionaries that contain all objects of x, indexed by a and b, respectively. For example:
Fiddle here.
var x = [
{
a: 1,
b: 11,
c: 101
},
{
a: 2,
b: 11,
c: 101
},
{
a: 1,
b: 11,
c: 102
},
{
a: 1,
b: 14,
c: 102
},
];
// index x by a, then by b, then by c
var byABC = _.compoundIndexBy(x, ['a', 'b', 'c']);
// there are two items in `x` with a = 1 and b = 11
console.assert(_.size(byABC[1][11]) == 2, 'Something went wrong...');
// display result
console.log(byABC);
byABC now looks like this:
{
1: {
11: {
101: {
a: 1,
b: 11,
c: 101
},
102: {
a: 1,
b: 11,
c: 102
}
},
14: {
102: {
a: 1,
b: 14,
c: 102
}
},
}
2: {
11:{
101: {
a: 2,
b: 11,
c: 101
}
}
}
}
This Fiddle demonstrates the compoundexIndexBy function. Is my work in vain (because Lo-Dash actually does support compound indices), or can it at least be improved?
You can create a mixin that recursively groups/indexes your objects:
_.mixin({
compoundIndexBy: function(lst, iteratees, context) {
if (iteratees.length === 1)
return _.indexBy(lst, iteratees[0], context);
var grouped = _.groupBy(lst, iteratees[0], context);
_.each(grouped, function(sublst, k) {
grouped[k] = _.compoundIndexBy(sublst, _.rest(iteratees), context);
});
return grouped;
}
});
console.dir(_.compoundIndexBy(x, ['a', 'b', 'c']));
If you prefer a list of objects matching the given indexes (in case of non unique paths, for example):
_.mixin({
compoundGroupBy: function(lst, iteratees, context) {
var grouped = _.groupBy(lst, iteratees[0], context);
if (iteratees.length === 1)
return grouped;
_.each(grouped, function(sublst, k) {
grouped[k] = _.compoundGroupBy(sublst, _.rest(iteratees), context);
});
return grouped;
}
});
console.dir(_.compoundGroupBy(x, ['a', 'b', 'c']));
And a demo http://jsfiddle.net/nikoshr/8w4n31vb/
Related
I have this nested Object:
{
1: { one: { a: 5, b: 6, c: 7, d: 8 } },
2: { one: { a: 6, b: 9, c: 10, d: 12, e: 1 } },
3: { one: { a: 3, b: 4, c: 9 } },
}
Required output:
{
one: {
a: [5, 6, 3]
b: [6, 9, 4]
c: [7, 10, 9]
d: [12]
e: [1]
}
}
Tried nested queries but failed.
Here's my take. A series of nested for for...in loops. Tried to optimize for readability.
const object = {
1: { one: { a: 5, b: 6, c: 7, d: 8 } },
2: { one: { a: 6, b: 9, c: 10, d: 12, e: 1 } },
3: { one: { a: 3, b: 4, c: 9 } },
};
const result = {};
for (const group in object) {
for (const num in object[group]) {
if (!result[num]) {
result[num] = {};
}
for (const letter in object[group][num]) {
if (!result[num][letter]) {
result[num][letter] = [];
}
result[num][letter].push(object[group][num][letter])
}
}
}
console.log(result)
Use nested loops. You don't care about the keys of the top-level object, so loop over its values with Object.values(). Then loop over the keys and values of the nested objects. Create nested objects in the result when needed, then add the values.
const obj1 = {
1: {one:{ a:5,b:6,c:7,d:8}},
2: {one:{ a:6,b:9,c:10,d:12,e:1}},
3: {one:{a:3,b:4,c:9}}
};
let result = {};
Object.values(obj1).forEach(val1 => {
Object.entries(val1).forEach(([key2, val2]) => {
if (!result.hasOwnProperty(key2)) {
result[key2] = {};
}
res2 = result[key2];
Object.entries(val2).forEach(([key3, val3]) => {
if (!res2.hasOwnProperty(key3)) {
res2[key3] = [val3];
} else {
res2[key3].push(val3);
}
});
});
});
console.log(result);
My approach is doing two groups:
group the first top-level object keys and get their values store them as value of an array.
group the keys of the arrays value (in the previous group)
let objs = {
1: { one: { a: 5, b: 6, c: 7, d: 8 } },
2: { one: { a: 6, b: 9, c: 10, d: 12, e: 1 } },
3: { one: { a: 3, b: 4, c: 9 } },
};
let groupByKey = {};
for (const obj of Object.values(objs)) {
let [key, inerObjs] = Object.entries(obj)[0];
groupByKey[key] ??= [];
groupByKey[key].push(Object.entries(inerObjs));
}
let result = {};
for (const [k, arrs] of Object.entries(groupByKey)) {
let group = {};
for (const obj of arrs.flat()) {
if (!group.hasOwnProperty(obj[0])) {
group[obj[0]] = [obj[1]];
} else {
group[obj[0]].push(obj[1]);
}
}
result[k] = group;
}
console.log(result);
Here is a recursive option that works for objects of arbitrary depth. Here calling it with an empty object as target and spreading the Object.values of your input in order to skip the top level properties as per your expected output.
function groupProperties(target, ...objects) {
for (const obj of objects) {
for (const [k, v] of Object.entries(obj)) {
if (typeof v === 'object') {
groupProperties((target[k] ??= {}), v);
}
else {
(target[k] ??= []).push(v);
}
}
}
return target;
}
const input = { 1: { one: { a: 5, b: 6, c: 7, d: 8 } }, 2: { one: { a: 6, b: 9, c: 10, d: 12, e: 1 } }, 3: { one: { a: 3, b: 4, c: 9 } }, };
const result = groupProperties({}, ...Object.values(input));
console.log(result);
Let's say i have an array of four objects
var data = [
{a: 1, b: 2},
{a: 2, b: 4},
{a: 1, b: 6},
{a: 2, b: 9}
];
And i want to create an object say
Taking common value and passing it as key and assigning the value ob common objects to an array like below
Const treat = {
1:[{a:1,b:2} {a:1,b:6}],
2:[{a:2,b:4} {a:2,b:9}]
}
Here is a version of what you want. You can research different array methods such as reduce to see other ways to obtain similar results.
var data = [{
a: 1,
b: 2
},
{
a: 2,
b: 4
},
{
a: 1,
b: 6
},
{
a: 2,
b: 9
}
];
function sortData() {
var sortedData = {};
data.forEach(sample => {
// Need to check if the array for the sample has been created since we cannot "push" to "undefined".
if (sortedData[sample.a]) {
sortedData[sample.a].push(sample);
} else {
sortedData[sample.a] = [sample];
}
});
return sortedData;
}
console.log(sortData());
I want to write a helper function to unpack a specific object property from each object in an array of objects. Sometimes this property will be top level, other times it will be nested an arbitrary number of levels. So the crux of this question is: how can I access an object property based on an array of key names of variable length?
I'm hoping for something like:
const func = (arrOfObjects, ...keys) {
return arrOfObjects.map(object => {
return object[keys[0]][keys[1]] ... [keys[N]];
})
}
with example behaviour:
const input = [
{a: b: {c: 10}},
{a: b: {c: 11}},
{a: b: {c: 12}}
]
console.log(func(input, 'a', 'b', 'c'))
// [10, 11, 12]
console.log(func(input, 'a', 'b'))
// [{c: 10}, {c: 11}, {c : 12}]
I feel like there has to be a nice ES6 wizardry solution but as yet haven't found it so any help would be much appreciated!
Cheers,
P
You can get a short and easy solution using Array#reduce
const input = [
{a: { b: {c: 10}}},
{a: { b: {c: 11}}},
{a: { b: {c: 12}}}
]
console.log(func(input, ['a', 'b', 'c']))
// [10, 11, 12]
console.log(func(input, ['a', 'b']))
// [{c: 10}, {c: 11}, {c : 12}]
function func(input, props) {
return input.map(x => exctractByProps(x, props));
}
function exctractByProps(obj, props) {
return props.reduce(
(acc, prop) => typeof acc === 'object' && prop in acc ? acc[prop] : undefined,
obj
)
}
The main logic is to grab all the properties passed in and then try to get the value corresponding to obj[prop[0]][prop[1]][prop[2]]/* ... */[prop[n]]. If the object has an odd shape that doesn't match up with prop (for example, an input of {a: 1}, ['a', 'b'] or {d: {c: 1}}, ['a', 'b']) then the function returns undefined.
Based on the answers you gave me to my questions and your example. It seems as if the order will of the input will always match the objects nesting. So here is my solution:
const func = (arrOfObjects, ...keys) => {
return arrOfObjects.map(object => {
let obj = object, integer = keys.length;
for (let index = 0; index < integer; index++) {
obj = obj[keys[index]];
if(obj === undefined) break;
}
return obj;
});
};
const input = [
{ a: { b: { c: 10 } } },
{ a: { b: { c: 11 } } },
{ a: { b: { c: 12 } } }
];
console.log(func(input, "a", "b", "c"));
// [10, 11, 12]
console.log(func(input, "a", "b"));
// [{c: 10}, {c: 11}, {c : 12}]
Unfortunately there is no such thing as the javascript magic you where expecting.
Note: this code will not work when the order of the keys inside the object are nested at random depth. But for what you are trying to solve, this should work just fine. Also, I tried to preserve your initial code as good as possible
If you supply the accessor like [a, b, c[0], name], you can write a custom function which returns the value if found in a nested Object, otherwise returns undefined
let obj = {
a: 1,
b: 2,
c: {
d: 1,
e: [{
f: 3,
g: [{
z: 45
}]
}]
}
}
function findKeyFromPattern(obj, patternArr) {
let value = obj;
for(let i = 0; i < patternArr.length; i++) {
const arrmatch = patternArr[i].match(/(\w+)\[(\d+)\]/);
if(arrmatch) { // pattern matches and array accessor syntax
value = value[arrmatch[1]];
if(typeof value === 'object' && value !== null) {
value = value[arrmatch[2]];
} else {
return;
}
} else {
if(value[patternArr[i]]) {
value = value[patternArr[i]];
} else {
return;
}
}
}
return value;
}
console.log(findKeyFromPattern(obj, ['c', 'e[0]', 'g']));
console.log(findKeyFromPattern(obj, ['c', 'e[1]', 'g']))
imagine that, we have two arrays. Each of the containing objects of different type. For example:
let first: Foo[] = [
{ a: 12, b: 'x', c: 0 },
{ a: 43, b: 'y', c: 0 }
];
let second: number[] = [11, 15];
I would like merge theirs objects in a way that I finally get one array looks like below:
let first: Foo[] = [
{ a: 12, b: 'x', c: 11 },
{ a: 43, b: 'y', c: 15 }
];
As you can see I just want to assign value from the second array to c property of object from first array.
I hope that you understand my explanation of problem. I believe in your skills, guys!
you could zip the two arrays into one,
const first: Foo[] = [
{ a: 12, b: 'x', c: 0 },
{ a: 43, b: 'y', c: 0 }
];
const second: number[] = [11, 15];
const result: Foo[] = first.map((e, i) => {
return <Foo>Object.assign({}, e, { c: second[i] });
});
As so often, Array.prototype.reduce provides a good base for an approach like e.g. this one ...
var listOfItems = [
{ a: 12, b: 'x', c: 0 },
{ a: 43, b: 'y', c: 0 }
];
var listOfValues = [11, 15];
function reassignValueToGivenKey(collector, item, idx) {
item = Object.assign({}, item); // do not mutate original reference.
item[collector.key] = collector.valueList[idx]; // reassign value.
collector.itemList.push(item); // collect processed items separately.
return collector;
}
var result = listOfItems.reduce(reassignValueToGivenKey, {
key: 'c',
valueList: listOfValues,
itemList: []
}).itemList;
console.log('listOfItems : ', listOfItems);
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
I think you should do it like this...
Maybe not the best, but should work in you case :)
This is very simple...
for(var i in second){
var elem = second[i];
first[i]['c'] = elem;
}
I have an array of objects. I don't know how many objects there will be until the code is being run (it is returned from an API), but let's assume this is the array:
var arr = [ { A: 40, B: 88, C: 11 },
{ A: 10, B: 98, C: 65 }, // sum of A = 188
{ A: 11, B: 15, C: 18 }, // sum of B = 310
{ A: 16, B: 55, C: 16 }, // sum of C = 136
{ A: 22, B: 23, C: 13 },
{ A: 89, B: 31, C: 13 } ]
I want to look over every object in the array. I'd like the end-result to be a list of keys and values, sorted in descending order. So, if we were to use the array above, the code would return something like this:
[["B", 310], ["A", 188], ["C", 136]]
I hope it's not too much to ask if you can add comments in your code, as sometimes the answers here can be very short and efficient (and work brilliantly) but difficult to understand for a beginner with algorithms :)
Many thanks in advance.
EDIT: Each object does not necessarily always have three keys, it is around 30-40 keys.
BIT MORE INFO: I'm writing this for a stacked bar chart where I want to then only extract the top 10 keys and bucket the rest of the values into an "Others" key, but this is irrelevant to the question and only here for information.
If you are about to get sorted result -like you mentioned in your terms- for your complete array then this may be the answer.
You can first calculate sum of corresponding properties of every object in the array with a simple Array.prototype.reduce then convert the result to the structure you want (I preferred to loop over object keys) and then sort your structured array.
var arr = [ { A: 40, B: 88, C: 11 },
{ A: 10, B: 98, C: 65 },
{ A: 11, B: 15, C: 18 },
{ A: 16, B: 55, C: 16 },
{ A: 22, B: 23, C: 13 },
{ A: 89, B: 31, C: 13 }
];
var sum = arr.reduce((p, c) => {
var result = Object.create(null);
Object.keys(p).forEach(k => result[k] = p[k] + c[k]);
return result;
});
var sorted = Object.keys(sum).map(k => [k, sum[k]]).sort((a, b) => a[1] - b[1]);
console.log(sorted);