Node.js app, writing validation tests. Given the following:
var obj = { foo: null, bar: null, baz: null},
values = [ 0, 1];
I need to create n number of objects to account for every property being assigned every combination of possible values, to represent every possible use case. So for this example, the output should be 2^3=8 objects, e.g.
[
{ foo: 0, bar: 0, baz: 0},
{ foo: 0, bar: 1, baz: 0},
{ foo: 0, bar: 1, baz: 1},
{ foo: 0, bar: 0, baz: 1},
{ foo: 1, bar: 0, baz: 0},
{ foo: 1, bar: 1, baz: 0},
{ foo: 1, bar: 1, baz: 1},
{ foo: 1, bar: 0, baz: 1},
]
Underscore or lodash or other libraries are acceptable solutions. Ideally, I would like something like so:
var mapUseCases = function(current, remaining) {
// using Underscore, for example, pull the current case out of the
// possible cases, perform logic, then continue iterating through
// remaining cases
var result = current.map(function(item) {
// perform some kind of logic, idk
return magic(item);
});
return mapUseCases(result, _.without(remaining, current));
}
var myValidationHeadache = mapUseCases(currentThing, somethingElse);
Pardon my pseudocode, I think I broke my brain. ¯\_(ツ)_/¯
Solution for any object length and any values.
Please note, undefined values do not show up.
function buildObjects(o) {
var keys = Object.keys(o),
result = [];
function x(p, tupel) {
o[keys[p]].forEach(function (a) {
if (p + 1 < keys.length) {
x(p + 1, tupel.concat(a));
} else {
result.push(tupel.concat(a).reduce(function (r, b, i) {
r[keys[i]] = b;
return r;
}, {}));
}
});
}
x(0, []);
return result;
}
document.write('<pre>' + JSON.stringify(buildObjects({
foo: [0, 1, 2],
bar: [true, false],
baz: [true, false, 0, 1, 42]
}), 0, 4) + '</pre>');
One way is to count from "000" to "999" in a values.length-based system:
keys = ['foo','bar','baz']
values = ['A', 'B']
width = keys.length
base = values.length
out = []
for(var i = 0; i < Math.pow(base, width); i++) {
var d = [], j = i;
while(d.length < width) {
d.unshift(j % base)
j = Math.floor(j / base)
}
var p = {};
for(var k = 0; k < width; k++)
p[keys[k]] = values[d[k]]
out.push(p)
}
document.write('<pre>'+JSON.stringify(out,0,3))
Update for products:
'use strict';
let
keys = ['foo', 'bar', 'baz'],
values = [
['A', 'B'],
['a', 'b', 'c'],
[0, 1]
];
let zip = (h, t) =>
h.reduce((res, x) =>
res.concat(t.map(y => [x].concat(y)))
, []);
let product = arrays => arrays.length
? zip(arrays[0], product(arrays.slice(1)))
: [[]];
let combine = (keys, values) =>
keys.reduce((res, k, i) =>
(res[k] = values[i], res)
, {});
let z = product(values).map(v => combine(keys, v));
z.map(x => document.write('<pre>'+JSON.stringify(x)+'</pre>'))
This is a non-recursive version of what you want:
function createRange(keys, values) {
if (typeof values[0] !== typeof [])
values = keys.map(k => values);
var pointer = {};
var repeats = 1;
keys.forEach((k, i) => {
var vLen = values[i].length;
repeats *= vLen;
pointer[k] = {
get value() {
return values[i][pointer[k].current]
},
current: 0,
period: Math.pow(vLen, i),
inc: function() {
var ptr = pointer[k];
ptr.current++;
if (ptr.current < vLen) return;
ptr.current = 0;
if (i + 1 === keys.length) return;
var nk = keys[i + 1];
pointer[nk].inc()
}
};
});
var result = [];
for (var i = 0; i < repeats; i++) {
var o = {};
result.push(o);
keys.forEach(k => o[k] = pointer[k].value)
pointer[keys[0]].inc();
}
return result;
}
var objKeys = ['u', 'v', 'w', 'x', 'y', 'z'];
var objValues = [
['1', '2', '3'],
['a', 'b', 'c'],
['foo', 'bar', 'baz'],
[1, 3, 2],
['test', 'try', 'catch'],
['Hello', 'World'],
];
var range = createRange(objKeys, objValues);
range.map(v => document.write(JSON.stringify(v).big()))
Related
I have an array of objects and I'm trying to combine like keys and add the values. So X should be 0, Y should be 1, and B should be 3. Thanks for any help!!!!
const arr = [{X: -1}, {Y: 1}, {X: -4}, {B: 3}, {X: 5}];
let result = {};
for (let i = 0; i < arr.length; i++) {
var item = arr[i];
for (var key in item) {
if (!(key in result))
parseInt(item);
result[key] = [];
result[key] += item[key];
}
}
console.log(result);
I expected X to be 0 but instead it is returning 5.
You can reduce each item (object) by grabbing the key and assigning the added previous value with the current value.
const input = [ {X: -1}, {Y: 1}, {X: -4}, {B: 3}, {X: 5} ];
let response = input.reduce((obj, item) => {
return ((key) => Object.assign(obj, {
[key] : (obj[key] || 0) + item[key] // Add previous with current
}))(Object.keys(item)[0]);
});
console.log(response);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Result
{
"X": 0,
"Y": 1,
"B": 3
}
Fun Code Golf Experiment
I changed Object.assign(o,{[k]:(o[k]||0)+e[k]}) to ({...o,[k]:(o[k]||0)+e[k]}) by utilizing the spread operator to save 10 bytes.
r=i=>i.reduce((o,e) =>(k=>({...o,[k]:(o[k]||0)+e[k]}))(Object.keys(e)[0])) // 74 bytes
console.log(r([{X:-1},{Y:1},{X:-4},{B:3},{X:5}]))
.as-console-wrapper { top: 0; max-height: 100% !important; }
You could use Array.prototype.reduce with Object.entries to group by key in order to summate the values.
Example below (check the comments for more details):
const arr = [{
X: -1
}, {
Y: 1
}, {
X: -4
}, {
B: 3
}, {
X: 5
}];
//Iterate the object els in the arr
const map = arr.reduce((accum, el) => {
//Destructure the object into some clearly defined variables
const [
[key, value]
] = Object.entries(el);
//Check the key against the map
if (accum[key] != null) {
//Add the value to the existing map value
accum[key] += value;
} else {
//Set the initial value in the map
accum[key] = value;
}
return accum;
}, {});
console.log(map);
Here is inner loop changed such that we access the key, if exists, it's used; otherwise it's initialized to zero. Then value is added.
const arr = [{X: -1}, {Y: 1}, {X: -4}, {B: 3}, {X: 5}];
let result = {};
for (let i = 0; i < arr.length; i++) {
var item = arr[i];
for (var key in item) {
result[key] = (result[key] || 0) + item[key] // changed here
}
}
console.log(result);
{X: 0, Y: 1, B: 3}
Simple solution:
const arr = [{X: -1}, {Y: 1}, {X: -4}, {B: 3}, {X: 5}];
let result = {};
for (let i = 0; i < arr.length; i++) {
var item = arr[i];
for (var key in item) {
if (result[key]) { // if key exists
result[key] += parseInt(item[key]);
} else { // if key doesn't exist
result[key] = parseInt(item[key]);
}
}
}
console.log(result);
a bit later but:
const arr = [{X: -1}, {Y: 1}, {X: -4}, {B: 3}, {X: 5}];
const result = arr.reduce((acc, item) =>{
let currentKey = Object.keys(item)[0]
return acc[currentKey] ? acc[currentKey] += item[currentKey] : acc[currentKey] = item[currentKey], acc
}, {})
console.log(result)
Is there a built-in lodash function to take this:
let params = ['foo', 'bar', 'baz', 'zle'];
let newArray = [];
params.forEach((element, index) => {
let key = "name" + index;
newArray.push({ key: element })
});
console.log(newArray);
And expected output should be like this:
var object = {
a: {
name1: "foo",
name2: "bar",
},
b: {
name1: "baz",
name2: "zle",
}
}
May this help you. I know that you should provide more information that what do really want to implement. but i got an idea that might help you.
let params = ['foo', 'bar', 'baz', 'zle'];
const keys = ['a', 'b', 'c', 'd']
let data = {}
while(params.length){
const [a, b] = params.splice(0, 2)
const key = keys.splice(0, 1)
data[key] = {
name1: a,
name2: b,
}
}
console.log(data)
output will be:
{
a: {
name1: "foo",
name2: "bar",
},
b: {
name1: "baz",
name2: "zle",
}
}
You can convert your array into Json like format that can be done like this
var jsonObj = {};
for (var i = 0 ; i < sampleArray.length; i++) {
jsonObj["position" + (i+1)] = sampleArray[i];
}
I have solved the issue. Solution for this issue is
let colLength = 4;
let breakableArray = _.chunk(data, 4);
breakableArray.forEach((arrayObject,key)=>{
let object = new Object();
for(let i = 0; i < colLength; i++) {
object[i] = arrayObject[i] != undefined ? arrayObject[i] : "-";
}
finalObject[key] = object;
})
I do have a collection like this:
{
a: {
x: 1,
y: 2,
},
b: {
x: 3,
y: 4,
}
}
And I want to transpose like this:
{
x: {
a: 1,
b: 2,
},
y: {
a: 3,
b: 4,
}
}
Today i'm using
var result = {};
for (var c in value)
for (var r in value[c])
result[r][c] = value[c][r];
return result;
But what is the "lodash way" to do it?
You can use _.forOwn if you really want to use Lodash:
const value = {a:{x:1,y:2},b:{x:3,y:4}};
let result = {};
_.forOwn(value, (v, c) => _.forOwn(v, (w, r) => (result[r] = result[r] || {})[c] = w));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
I have a horrible looking array which looks like this:
EDIT:
array = [
{
Letters: [{ Letter: 'A' }, { Letter: 'B' }, { Letter: 'C' }],
Numbers: [{ Number: '1' }, { Number: '2' }, { Number: '3' }]
},
null,
{
Letters: [{ Letter: 'D' }, { Letter: 'E' }, { Letter: 'F' }, { Letter: 'G' }, { Letter: 'H' }],
Numbers: [{ Number: '4' }, { Number: '5' }, { Number: '6' }, { Number: '7' }]
}
];
And want the array to look like this:
flattenedArray = [a,b,c,1,2,3,d,e,f,g,h,4,5,6,7]
Unfortunately I cannot change the original formatting because that is the form received when merging two API responses that I am getting.
I have tried using:
var flattenedArray = [].concat.apply([], array);
But it just presents the array in the same format it was entered in.
I was wondering if anybody had any advice?
EDIT:
I have tried implementing the suggestions given - thank you so much for your help. It seems it is a problem with the format of the list - unfortunately using the chrome console which is in a 'tree' format I cannot see the direct structure of the array output.
Thank you for all your help!
EDIT 2: See above for the actual array, thank you for showing me how to see this!
If you have lodash, you can use:
_.flattenDeep(array)
You can also checkout their source code for ides on how to implement yourself if you prefer.
Edit for the new request of nested arrays/objects and the flattening, you could use a combined approach with testing for the type of an element.
var array = [{ Letters: [{ Letter: 'A' }, { Letter: 'B' }, { Letter: 'C' }], Numbers: [{ Number: '1' }, { Number: '2' }, { Number: '3' }] }, null, { Letters: [{ Letter: 'D' }, { Letter: 'E' }, { Letter: 'F' }, { Letter: 'G' }, { Letter: 'H' }], Numbers: [{ Number: '4' }, { Number: '5' }, { Number: '6' }, { Number: '7' }] }],
result = array.reduce(function iter(r, a) {
if (a === null) {
return r;
}
if (Array.isArray(a)) {
return a.reduce(iter, r);
}
if (typeof a === 'object') {
return Object.keys(a).map(k => a[k]).reduce(iter, r);
}
return r.concat(a);
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Old request and the immortal question how to flat a nested array.
var flat = (r, a) => Array.isArray(a) ? a.reduce(flat, r) : r.concat(a),
inputArray = array = [[['a', 'b', 'c'], [1, 2, 3]], [], [['d', 'e', 'f', 'g', 'h'], [4, 5, 6, 7]]],
outputArray = inputArray.reduce(flat, []);
console.log(outputArray);
You can create recursive function using forEach() that will return new array.
var array = [[['a','b','c'],[1,2,3]],[],[['d','e','f','g','h'],[4,5,6,7]]]
function flat(data) {
var r = []
data.forEach(e => Array.isArray(e) ? r = r.concat(flat(e)) : r.push(e));
return r;
}
console.log(flat(array))
You can also use reduce() instead of forEach()
var array = [[['a','b','c'],[1,2,3]],[],[['d','e','f','g','h'],[4,5,6,7]]]
function flat(data) {
return data.reduce((r, e) => Array.isArray(e) ? r = r.concat(flat(e)) : r.push(e) && r, [])
}
console.log(flat(array))
As #Bergi suggested you can use reduce() like this.
data.reduce((r, e) => r.concat(Array.isArray(e) ? flat(e) : [e]), [])
It's nice to use a recursive function for such cases:
arr = [[['a','b','c'],[1,2,3]],[],[['d','e','f','g','h'],[4,5,6,7]]];
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
result = result.concat(Array.isArray(arr[i])? flatten(arr[i]) : [arr[i]]);
}
return result;
}
console.log(flatten(arr));
You could try the flatten function in Ramda.
R.flatten([1, 2, [3, 4], 5, [6, [7, 8, [9, [10, 11], 12]]]]);
//=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Your Array format is not correct, you are missing commas(,). This is correct array.
var array = [[['a','b','c'],[1,2,3]],[],[['d','e','f','g','h'],[4,5,6,7]]];
var array = [[['a','b','c'],[1,2,3]],[],[['d','e','f','g','h'],[4,5,6,7]]];
var result = flatten(array);
function flatten(array) {
var flat = [];
if(array !== undefined){
var flat = [];
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Array) {
flat = flat.concat(flatten.apply(null, arguments[i]));
} else {
flat.push(arguments[i]);
}
}
}
return flat;
}
console.log(result);
No one thought of splicing in-place?
function flatten(array){
for (var i = 0; i < array.length; i++) {
if(array[i] instanceof Array){
array.splice.apply(array,[i,1].concat(array[i]));
i--;
}
};
return array;
}
One iteration, no recursion.
Implement flatten function using recursion and spread operator.
const a = [1,[2,[3,4],[5]],6];
const flatten = (arr) => {
const res = []
for(let i=0;i<arr.length;i++) {
if(!Array.isArray(arr[i])) res.push(arr[i]);
else res.push(...flatten(arr[i]));
}
return res;
}
console.log(flatten(a));
function steamrollArray(arr) {
var tmp = [];
arr.forEach(function(val){
if(Array.isArray(val))
tmp = tmp.concat(steamrollArray(val));
else
tmp.push(val);
});
console.log(tmp);
return tmp;
}
steamrollArray([1, [2], [3, [[4]]]]);
let arr = [1,2,[3,4]]
/* let newarr = arr.flat(); */
let newarr = Object.values(arr);
let arr2 = []
for(let val of Object.values(arr)) {
if(!Array.isArray(val)){
console.log(val)
arr2.push(val)
}
for ( let val2 of Object.values(val)){
arr2.push(val2)
}
}
console.log(arr2)
I have an array like so:
var array = [
{
name: "a",
value: 1
},
{
name: "a",
value: 2
},
{
name: "a",
value: 3
},
{
name: "b",
value: 0
},
{
name: "b",
value: 1
}
];
And I need an array like this:
var newarray = [
{
name: "a",
value: 2
},
{
name: "b",
value: 0.5
}
]
Where the new array has each unique name as an object with the average value.
Is there an easy way to accomplish this?
You'll have to loop through the array, computing the sum and counts for each object. Here's a quick implementation:
function average(arr) {
var sums = {}, counts = {}, results = [], name;
for (var i = 0; i < arr.length; i++) {
name = arr[i].name;
if (!(name in sums)) {
sums[name] = 0;
counts[name] = 0;
}
sums[name] += arr[i].value;
counts[name]++;
}
for(name in sums) {
results.push({ name: name, value: sums[name] / counts[name] });
}
return results;
}
Demonstration
Note, this kind of thing can be made much easier if you use a library like Underscore.js:
var averages = _.chain(array)
.groupBy('name')
.map(function(g, k) {
return {
name: k,
value: _.chain(g)
.pluck('value')
.reduce(function(x, y) { return x + y })
.value() / g.length
};
})
.value();
Demonstration
var array = [
{
name: "a",
value: 1
},
{
name: "a",
value: 2
},
{
name: "a",
value: 3
},
{
name: "b",
value: 0
},
{
name: "b",
value: 1
}
];
var sum = {};
for(var i = 0; i < array.length; i++) {
var ele = array[i];
if (!sum[ele.name]) {
sum[ele.name] = {};
sum[ele.name]["sum"] = 0;
sum[ele.name]["count"] = 0;
}
sum[ele.name]["sum"] += ele.value;
sum[ele.name]["count"]++;
}
var result = [];
for (var name in sum) {
result.push({name: name, value: sum[name]["sum"] / sum[name]["count"]});
}
console.log(result);
You can do it with Alasql library with one line of code:
var newArray = alasql('SELECT name, AVG([value]) AS [value] FROM ? GROUP BY name',
[array]);
Here I put "value" in square brackets, because VALUE is a keyword in SQL.
Try this example at jsFiddle
Here is a ES2015 version, using reduce
let arr = [
{ a: 1, b: 1 },
{ a: 2, b: 3 },
{ a: 6, b: 4 },
{ a: 2, b: 1 },
{ a: 8, b: 2 },
{ a: 0, b: 2 },
{ a: 4, b: 3 }
]
arr.reduce((a, b, index, self) => {
const keys = Object.keys(a)
let c = {}
keys.map((key) => {
c[key] = a[key] + b[key]
if (index + 1 === self.length) {
c[key] = c[key] / self.length
}
})
return c
})
And a possible solution using ECMA5 (as we seem to be missing one)
var sums = {},
averages = Object.keys(array.reduce(function (previous, element) {
if (previous.hasOwnProperty(element.name)) {
previous[element.name].value += element.value;
previous[element.name].count += 1;
} else {
previous[element.name] = {
value: element.value,
count: 1
};
}
return previous;
}, sums)).map(function (name) {
return {
name: name,
average: this[name].value / this[name].count
};
}, sums);
On jsFiddle
October 2020, I think this is the shortest way (ES6+)
const getAveragesByGroup = (arr, key, val) => {
const average = (a, b, i, self) => a + b[val] / self.length;
return Object.values(
arr.reduce((acc, elem, i, self) => (
(acc[elem[key]] = acc[elem[key]] || {
[key]: elem[key],
[val]: self.filter((x) => x[key] === elem[key]).reduce(average, 0),
}),acc),{})
);
};
console.log(getAveragesByGroup(array, 'name', 'value'))
Try by yourself :)