How can I dynamically add more values in the sets defined as follows in Javascript:
var data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
What I want to do is similar to the following:
(key, value) = read_a_row_from_table_of_values;
if (key is present in data1) // could be by iterating all the keys in data
{
data1[key][elems].push(value); // this is the point of concern (how to push values)
}
As I am feeding this data to another tool (UpSet), so this data structure (in the form of name and elems) has to be followed. Details about that can be found here.
If you're only ever adding 1 or 2 items (i.e. such that O(n) insert time is acceptable) then this solution (based on #mplungjan's now-deleted comment) should work for you:
var data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
function add( data1, name, value ) {
const exists = data1.find( e => e.name === name );
if( exists ) {
exists.elems.push( value );
}
else {
data1.push( { name, elems: [value] } );
}
}
add( data1, 'S4', 1 );
add( data1, 'S3', 2 );
add( data1, 'S5', 3 );
If you'll be performing mass operations on data1 then you should convert it to a Map first (note that Array.prototype.map and Map<K,V> are completely different things):
function convertData1ToMap( data1 ) {
const m = new Map();
for( const e of data1 ) {
m.set( e.name, e.elems );
}
return m;
}
function convertMapToData1( m ) {
const arr = [];
for( const k of map.keys() ) {
const elems = map.get( k );
arr.push( { name: k, elems } );
}
return arr;
}
//
const m = convertData1ToMap( data1 );
// Mass insert:
for( let i = 4; i < 10 * 1000; i++ ) {
const name = 'S' + i.toString();
if( m.has( name ) ) {
m.get( name ).push( i );
}
else {
m.set( name, [i]);
}
}
// Recreate `data1`:
data1 = convertMapToData1( m );
Entries and Find will work
Basically
data1.find(elem => elem.name===key).elems.push(value)
We can alas not do a oneliner if the newValues may contain a key not in the data1 array
const data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
const newValues = {"S1": 4,"S2":5, "S6": 6 }
Object.entries(newValues)
.forEach(([key, value]) => {
const found = data1.find(elem => elem.name === key);
if (found) found.elems.push(value);
});
console.log(data1);
If you want to merge, you need to add missing keys
const data1 = [
{ name: 'S1', elems: [0, 1, 2] },
{ name: 'S2', elems: [1, 2, 3] },
{ name: 'S3', elems: [0, 2, 4] },
];
const newValues = {"S1": 4,"S2":5, "S6": 6 }
Object.entries(newValues)
.forEach(([key, value]) => {
const found = data1.find(elem => elem.name === key);
if (found) found.elems.push(value);
else data1.push({name:key,elems: [value]});
})
console.log(data1);
Related
I have a nested object look like this:
let obj = {
F:{
asian: {
"35-44": 1,
"55-": 1,
},
"asian/black": {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
M:{
asian: {
"35-44": 1,
"55-": 1,
},
white: {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
}
And I want to flatten the object to this:
res = {
F: 6,
M: 6,
asian: 4,
"asian/black": 4,
white: 4,
"0-24": 2,
"35-44": 4,
"45-54": 4,
"55-": 2,
}
That every value in res should be the sum of the deepest object values(F, M) and object values with the same key(0-24, 35-44...). I feel this can be done using recursion and just can't get it right. The code I write:
let returnVal = 0
const flatten = (obj, prefix = '', res = {}) => {
return Object.entries(obj).reduce((r, [key, val]) => {
if(typeof val === 'object'){
flatten(val, key, r)
} else {
res[key] = val
returnVal = val;
}
if (key in res) {
res[key] += returnVal
} else {
res[key] = 0
res[key] += returnVal
}
return r
}, res)
}
console.log(flatten(obj))
it will output:
result = {
"0-24": 2,
"35-44": 2,
"45-54": 4,
"55-": 2,
F: 2,
M: 2,
asian: 2,
"asian/black": 2,
white: 2,
}
F, M, and some other keys are not correct. Thanks!
Another, perhaps simpler, approach is as follows:
const consolidate = (obj, path = [], results = {}) =>
Object .entries (obj) .reduce ((results, [k, v]) =>
Object (v) === v
? consolidate (v, [...path, k], results)
: [...path, k] .reduce (
(results, n) => ({...results, [n] : (results[n] || 0) + v}),
results
),
results)
const data = {F: {asian: {"35-44": 1, "55-": 1}, "asian/black": {"0-24": 1, "35-44": 1, "45-54": 2}}, M: {asian: {"35-44": 1, "55-": 1}, white: {"0-24": 1, "35-44": 1, "45-54": 2}}}
console .log (consolidate (data))
.as-console-wrapper {min-height: 100% !important; top: 0}
We recursively track paths taken through the object, such as ['F', 'asian/black', '45-54'] or ['M', 'white'] or simply ['f'] as well as an object containing the final results. When we the value at the current node is an object, we recur, adding the current property name to the path. When it's not (for this data it must therefore hit a number), we hit a base case in which we take each node in the current path, and update the results object by adding that number to the value for the node in the results object, or setting it to the current value if that value doesn't exist.
There is a potential issue with the default parameters, as described in another Q & A. If someone tried to map the consolidate function directly over an array of input objects, it would fail. If this is a concern, it's easy enough to swap the default parameters for a wrapper function:
const _consolidate = (obj, path, results) =>
Object .entries (obj) .reduce ((results, [k, v]) =>
Object (v) === v
? _consolidate (v, [...path, k], results)
: [...path, k] .reduce (
(results, n) => ({...results, [n] : (results[n] || 0) + v}),
results
),
results)
const consolidate = (obj) =>
_consolidate (obj, [], {})
const data = {
F: {
asian: {
"35-44": 1,
"55-": 1,
},
"asian/black": {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
M: {
asian: {
"35-44": 1,
"55-": 1,
},
white: {
"0-24": 1,
"35-44": 1,
"45-54": 2,
},
},
};
const isObject = obj => Object.prototype.toString.call(obj) === "[object Object]";
function nestKeys(obj, parent = "") {
return Object.keys(obj).map(key => {
const k = parent.length ? [parent, key].join(".") : key;
if (!isObject(obj[key])) {
return k;
}
return nestKeys(obj[key], k);
}).flat();
}
function flatObj(obj) {
const map = {};
const keys = nestKeys(obj);
keys.forEach(nestedKey => {
const splited = nestedKey.split(".");
const val = splited.reduce((acc, cur) => acc[cur], obj);
splited.forEach(k => {
map[k] = (map[k] || 0) + val;
})
});
return map;
}
console.log(flatObj(data));
this.state = {
array: [1, 2, 3],
objects: [{ id: 1 }, { id: 2 }, { id: 3 }]
}
How can I change the specific value of an object or array in the state without setStating the whole array/object?
something like
this.setState({ array[2]: 5 })
this.setState({ object[0].id: 0 })
You could use a helper function to set an element at an index and return that newly updated array
const array = [1, 2, 3]
const object = [{id: 1}, {id: 2}, {id: 3}]
const setElementAtIndex = (index, value, array) => [
...array.slice(0, index),
value,
...array.slice(index + 1)
]
console.log(setElementAtIndex(0, 99, array))
console.log(setElementAtIndex(1, 99, array))
console.log(setElementAtIndex(2, 99, array))
console.log(setElementAtIndex(0, { ...object[0], id: 0 }, object))
this.setState({ array: setElementAtIndex(2, 5, array) })
this.setState({ object: setElementAtIndex(0, { ...object[0], id: 0 }, object) })
I would use map.
const state = {
array: [1,2,3],
objects: [{id: 1}, {id: 2}, {id: 3}]
}
const newArray = state.array.map((v, i) => i === 2 ? 5 : v);
const newObjects = state.objects.map((v, i) => i === 0 ? {...v, id: 0} : v);
console.log(newArray);
console.log(newObjects);
// this.setState({ ...this.state, array: newArray });
// this.setState({ ...this.state, objects: newObjects });
I'm trying to create a function to process a list of numbers relating to depth using recursion or loops in JavaScript.
The following "input" needs to be processed into the "output", and it needs to work for arbitary lists.
One thing to note is that numbers increase by either 0 or 1 but may decrease by any amount.
var input = [0, 1, 2, 3, 1, 2, 0]
var output =
[ { number: 0, children:
[ { number: 1, children:
[ { number: 2, children:
[ { number: 3, children: [] } ]
}
]
}
, { number: 1, children:
[ { number: 2, children: [] } ]
}
]
}
, { number: 0, children: [] }
]
I worked it out myself, although it needs some refinement.
var example = [0, 1, 2, 2, 3, 1, 2, 0]
var tokens = []
var last = 0
const createJSON = (input, output) => {
if (input[0] === last) {
output.push({ num: input[0], children: [] })
createJSON(input.splice(1), output)
}
else if (input[0] > last) {
last = input[0]
output.push(createJSON(input, output[output.length-1].children))
}
else if (input[0] < last) {
var steps = input[0]
var tmp = tokens
while (steps > 0) {
tmp = tmp[tmp.length-1].children
steps--
}
tmp.push({ num: input[0], children: [] })
createJSON(input.splice(1), tmp)
}
}
createJSON(example, tokens)
console.log(tokens)
In fact, it's a very simple problem to solve...
var input = [0, 1, 2, 3, 1, 2, 0]
, output = []
, parents = [output]
;
for(el of input)
{
let nv = { number:el, children:[] }
parents[el].push( nv )
parents[++el] = nv.children // save the #ddress of children:[] for adding items on
}
console.log( output )
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a functional solution based on recursion and Array.prototype.reduce:
const data = [0, 1, 2, 3, 1, 2, 0]
const last = xs => xs.length > 0 ? xs[xs.length - 1] : undefined
const lastD = (d, t, i = 0) => i > d ? t : lastD(d, last(t).children, i + 1)
const buildIt = xs => xs.reduce(
(a, x) =>((x === 0 ? a : lastD(x - 1, a)).push({ number: x, children: [] }), a),
[]
)
console.log(buildIt(data))
.as-console-wrapper { max-height: 100% !important; top: 0; }
Note: This solution does not depend on the mutation of additional variables for bookkeeping purposes.
Turns out the actual problem was significantly simpler to solve than my initial misinterpretation!
How to make function take multiple variables from an array passed in as parameter?
Edited
For example:
Achieve this:
const inputObj = [
['Anna', 10, 'Monday'],
['Anna', 15, 'Wednesday'],
['Beatrice', 8, 'Monday'],
['Beatrice', 11, 'Wednesday'],
['Anna', 4, 'Wednesday'],
['Beatrice', 5, 'Monday'],
['Beatrice', 16, 'Monday']
]
// expected output:
const outputObj = [
[ 'Anna', 10, 'Monday' ],
[ 'Anna', 19, 'Wednesday' ],
[ 'Beatrice', 29, 'Monday' ],
[ 'Beatrice', 11, 'Wednesday' ]
]
const arr = [0, 2]
const someFunction = (obj, v, a) => {
const result = obj.reduce((acc, cur) => {
const key = `${cur[a[0]]}|${cur[a[1]]}`
if(!acc[key]) acc[key] = cur
else acc[key][1] += cur[v]
return acc
}, {})
return Object.values(result)
}
console.log(someFunction(inputObj, 1, arr))
with this:
const arr = [0, 2, 3, ...] // basically the array could contain any number of items.
const someFunction = (obj, v, objParams) => {
const result = obj.reduce((acc, cur) => {
const key = ???
...
}, {})
}
So that the function can be reused and it accepts custom-sized arrays, check if the column numbers in the array are the same, then adds the sum of the column that is passed in as v?
How to declare the variables from the objParams to achieve the same result as the code above does?
Also how to add v in the middle of cur?
Assuming objParams is an array of unknown size (strings in this example):
const objParams = ["c1", "c2", "c3"];
const key = objParams.join(']}|${cur[');
const built = '${cur[' + key + ']';
Built is:
${cur[c1]}|${cur[c2]}|${cur[c3]
With ES6 you can use the spread operator in the argument definition.
More reading about spread operator on MDN
function sum(...args) {
return args.reduce((result, value) => result + value, 0)
}
const numbers = [1, 2, 3];
console.log('sum', sum(2, 2));
console.log('sum', sum(...numbers));
console.log('sum', sum(1, 2, 1, ...numbers));
// get single args before accumulating the rest
function sum2(foo, bar, ...args) {
return args.reduce((result, value) => result + value, 0)
}
console.log('sum2', sum2(2, 2));
console.log('sum2', sum2(...numbers));
console.log('sum2', sum2(1, 2, 1, ...numbers));
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)