Related
interface FormValues {
key: string;
value: any;
}
const array: FormValues[] = [
{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 'str' // string
},
{
key: 'C',
value: { a: 1, b: '2' } // object
},
{
key: 'C',
value: ['a','2'] // array
},
{
key: 'C',
value: ['a','2'] // array
}
{
key: 'B',
value: true // boolean
}
]
I want to filter the objects based on field value, which can have a value of any type.
I tried to do it like this; my solution is not working for nested object checks.
const key = 'value';
const arrayUniqueByKey = [...new Map(array.map(item => [item[key], item])).values()];
output :
[{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 'str' // string
},
{
key: 'C',
value: { a: 1, b: '2' } // object
},
{
key: 'C',
value: ['a','2'] // array
},
{
key: 'B',
value: true // boolean
}]
You need to decide what makes two distinct objects "equal". In JavaScript, all built-in comparisons of objects (which includes arrays) are by reference. That means ['a','2'] === ['a','2'] is false because two distinct array objects exist, despite having the same contents. See How to determine equality for two JavaScript objects? for more information.
I will take the approach that you would like two values to be considered equal if they serialize to the same value via a modified version of JSON.stringify() where the order of property keys are guaranteed to be the same (so {a: 1, b: 2} and {b: 2, a: 1} will be equal no matter how those are stringified). I use a version from this answer to do so:
function JSONstringifyOrder(obj: any, space?: number) {
var allKeys: string[] = [];
var seen: Record<string, null | undefined> = {};
JSON.stringify(obj, function (key, value) {
if (!(key in seen)) {
allKeys.push(key); seen[key] = null;
}
return value;
});
allKeys.sort();
return JSON.stringify(obj, allKeys, space);
}
And now I can use that to make the keys of your Map:
const arrayUniqueByKey = [...new Map(array.map(
item => [JSONstringifyOrder(item[key]), item]
)).values()];
And you can verify that it behaves as you'd like:
console.log(arrayUniqueByKey);
/* [{
"key": "A",
"value": 1
}, {
"key": "A",
"value": "str"
}, {
"key": "C",
"value": {
"a": 1,
"b": "2"
}
}, {
"key": "C",
"value": [
"a",
"2"
]
}, {
"key": "B",
"value": true
}] */
Playground link to code
This will combine any duplicate keys, creating a new property values to hold the array of combined values (from like keys).
const array = [{key: 'A', value: 1},{key: 'A', value: 'str'},{key: 'C', value: { a: 1, b: '2'}},{key: 'B',value: true}]
const arrayUniqueByKey = [array.reduce((b, a) => {
let f = b.findIndex(c => c.key === a.key)
if (f === -1) return [...b, a];
else {
b[f].values = [...[b[f].value], a.value];
return b
}
}, [])];
console.log(arrayUniqueByKey)
You can use Array.prototype.reduce() combined with JSON.stringify() and finaly get the result array of values with Object.values()
const array = [{key: 'A',value: 1,},{key: 'A',value: 1,},{key: 'A',value: 'str',},{key: 'C',value: { a: 1, b: '2' },},{key: 'C',value: ['a', '2'],},{key: 'C',value: ['a', '2'],},{key: 'B',value: true}]
const result = Object.values(array.reduce((a, c) => ((a[JSON.stringify(c)] = c), a), {}))
console.log(result)
I have a function that takes as an argument a list and must return the elements of the list into an array.
For example in case of the input:
{value: 1, rest: {value: 2, rest: null}}
The output should looke like:
[1, 2]
This is how I tried to solve it:
function listToArray(list){
var arr = [];
for (var node = list; node; node = node.rest) {
arr.unshift(node);
}
return arr;
}
console.log(listToArray({value: 1, rest: {value: 2, rest: null}}));
And the output I get is:
[{value: 2, rest: null}, {
value: 1
rest: {value: 2, rest: null}
}]
Does anyone know what should I change to make it work?
You were just missing the .value from node.
function listToArray(list){
var arr = [];
for (var node = list; node; node = node.rest) {
arr.unshift(node.value);
}
return arr;
}
console.log(listToArray({value: 1, rest: {value: 2, rest: null}}));
Note you might want push instead of unshift.
You can use recursion to grab all the inner object values.
var obj = {value: 1, rest: {value: 2, rest: null}};
var list = objToList(obj, 'value', 'rest');
console.log(list);
function objToList(obj, valueField, targetField) {
return objToListInner(obj, valueField, targetField, []);
}
function objToListInner(obj, valueField, targetField, list) {
if (isObject(obj)) {
list.push(obj[valueField]);
objToListInner(obj[targetField], valueField, targetField, list)
}
return list;
}
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}
A bit of code golf. ;)
let obj = {value: 1, rest: {value: 2, rest: null}},
list = objToList(obj, 'value', 'rest');
console.log(list);
function objToList(o, v, t, l) {
return o ? objToList(o[t], v, t, (l||[]).concat(o[v])) : (l||[])
}
.as-console-wrapper {
top: 0;
max-height: 100% !important;
}
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)
In my poker app I have an array of hands, each hand being array of randomly selected card objects with value and suit:
[ [ { value: 5, suit: 's' },
{ value: 4, suit: 's' },
{ value: 6, suit: 'c' },
{ value: 11, suit: 'd' },
{ value: 12, suit: 'c' } ],
[ { value: 9, suit: 'd' },
{ value: 12, suit: 'h' },
{ value: 8, suit: 'c' },
{ value: 12, suit: 's' },
{ value: 2, suit: 's' } ],
[ { value: 4, suit: 'h' },
{ value: 6, suit: 's' },
{ value: 10, suit: 'c' },
{ value: 3, suit: 'd' },
{ value: 7, suit: 'd' } ] ]
To prepare the hands for evaluation I want to use Array.reduce to return an array of hand objects. So the output would be:
[
{
values: [5, 4, 6, 11, 12],
suits: ['s', 's', 'c', 'd', 'c']
},
{
values: [9, 12, 8, 12, 2],
suits: ['d', 'h', 'c', 's', 's']
},
{
values: [4, 6, 10, 3, 7],
suits: ['h', 's', 'c', 'd', 'd']
}
]
I tried implementing this with nested forEach's, but its failing and I don't know why. I have two console.log's within which output as expected, but in the end hands is identical to the input.
let temp = []
hands.forEach((el) => {
temp = el
el = {}
el.values = []
el.suits = []
console.log(el) //expected output
temp.forEach((obj) => {
el.values.push(obj.value)
el.suits.push(obj.suit)
console.log(el) //expected output
})
})
console.log(hands) //same as original
You have to be thinking about the shape of your input data (DATA) and output (DATA')
Note 1:1 relationship between HAND and HAND' meaning we will use Array.prototype.map for one transformation. On the other hand, CARD has a N:1 relationship with HAND' meaing we will use Array.prototype.reduce for that transformation
So keep in mind while we're working, we will be doing a map and a reduce
const data =
[ [ { value: 5, suit: 's' },
{ value: 4, suit: 's' },
{ value: 6, suit: 'c' },
{ value: 11, suit: 'd' },
{ value: 12, suit: 'c' } ],
[ { value: 9, suit: 'd' },
{ value: 12, suit: 'h' },
{ value: 8, suit: 'c' },
{ value: 12, suit: 's' },
{ value: 2, suit: 's' } ],
[ { value: 4, suit: 'h' },
{ value: 6, suit: 's' },
{ value: 10, suit: 'c' },
{ value: 3, suit: 'd' },
{ value: 7, suit: 'd' } ] ]
let output =
data.map(cards =>
cards.reduce(({values, suits}, {value, suit}) => ({
values: [...values, value],
suits: [...suits, suit]
}), {values: [], suits: []}))
console.log(output)
Now of course that looks a little dense so it would be nice if we could dial down the complexity a bit. By making some curried adapters for map and reduce we can express a function that performs your transformation quite nicely
const data =
[ [ { value: 5, suit: 's' },
{ value: 4, suit: 's' },
{ value: 6, suit: 'c' },
{ value: 11, suit: 'd' },
{ value: 12, suit: 'c' } ],
[ { value: 9, suit: 'd' },
{ value: 12, suit: 'h' },
{ value: 8, suit: 'c' },
{ value: 12, suit: 's' },
{ value: 2, suit: 's' } ],
[ { value: 4, suit: 'h' },
{ value: 6, suit: 's' },
{ value: 10, suit: 'c' },
{ value: 3, suit: 'd' },
{ value: 7, suit: 'd' } ] ]
const map = f => xs => xs.map(f)
const reduce = f => y => xs => xs.reduce(f, y)
const handAppendCard = ({values, suits}, {value, suit}) => ({
values: [...values, value],
suits: [...suits, suit]
})
const makeHands =
map (reduce (handAppendCard) ({values:[], suits:[]}))
let output = makeHands (data)
console.log(output)
That's just one way to approach the problem. I hope you were able to learn something from it ^_^
There you go - a solution using nested Array.prototype.reduce functions:
var array=[[{value:5,suit:'s'},{value:4,suit:'s'},{value:6,suit:'c'},{value:11,suit:'d'},{value:12,suit:'c'}],[{value:9,suit:'d'},{value:12,suit:'h'},{value:8,suit:'c'},{value:12,suit:'s'},{value:2,suit:'s'}],[{value:4,suit:'h'},{value:6,suit:'s'},{value:10,suit:'c'},{value:3,suit:'d'},{value:7,suit:'d'}]];
var result = array.reduce(function(p, c) {
p.push(c.reduce(function(a, b) {
a.values.push(b.value);
a.suits.push(b.suit);
return a;
}, {values: [],suits: []}));
return p;
},[]);
console.log(result);
.as-console-wrapper {top: 0;max-height: 100%!important;}
You can use reduce flat to extract nested arrays. If you pass Infinity, no matter how deep they are, they'll be extracted.
const result = flatten([1,2,[3,4],[5,6,7]]) // result: [1,2,3,4,5,6,7]
Everything becomes one-dimensional array
Here's a simple solution with string concat and reduce. You could try something like:
var reduced = [];
//here a is your initial array
for(var i=0; i<a.length;i++){
reduced.push(a[i].reduce(function(prev,curr){
var obj={value:prev.value+','+curr.value,suit:prev.suit+','+curr.suit};return obj}));
}
console.log(reduced)
EDIT: As per #Barmar comment this returns string. If you want an array you could do:
for(var i=0; i<a.length;i++){
var tempElm =a[i].reduce(function(prev,curr) {
var obj= {value:prev.value+','+curr.value,suit:prev.suit+','+curr.suit};return obj});
tempElm['value'] = tempElm['value'].split();
tempElm['suit']= tempElm['suit'].split();
reduced.push(tempElm);
}
console.log(reduced)
EDIT 2: With the fair criticism for the above fix (which adds an over head of converting string to array) You could directly create array instead as follows:
var reduced = [];
for(var i=0; i<a.length;i++){
var valArray = []; var suitArray=[];
var tempElm = a[i].reduce(function(prev,curr) {
valArray.push(curr.value);suitArray.push(curr.suit);
var obj= {value:valArray,suit:suitArray};
return obj;
},null);
console.log(reduced)
function arrayToList(array) {
var list = null
for (var i = 0; i < array.length; i++)
list = {value: array[i], rest: list};
return list;
}
console.log(arrayToList([10, 20, 30]));
// {value: 30, rest:{ value:20, rest:{value: 10, rest: null}}
I've looked at this function from eloquent javascript exercise for hours, and can't get it why the result are reversed. Any help would be appreciated.
Here's what's happening when you are iterating in your for-loop:
First iteration:
i = 0;
list = {value:10, rest:null}
Second iteration:
i = 1;
list = {value:20, rest:{value:10, rest: null}}
Third iteration:
i = 2;
list = {value:30, rest:{value:20, rest:{value:10, rest: null}}}
On each iteration, you are nesting your list object within itself:
list = {value: array[i], rest: list};
Set an output in the loop, than you can see how it works:
function arrayToList(array) {
var list = null
for(var i = 0; i < array.length; i++) {
list = {value: array[i], rest: list};
console.log(list);
console.log('\n');
}
return list;
}
arrayToList([10, 20, 30]);
/* Output
{ value: 10, rest: null }
{ value: 20, rest: { value: 10, rest: null } }
{ value: 30, rest: { value: 20, rest: { value: 10, rest: null } } }
*/
You have set list = null.
The first time in the loop, list is "{ value: 10, rest: null }"
-> "null" nested within.
The second time in the loop, list is "{ value: 20, rest: { value: 10, rest: null } }"
-> "{ value: 10, rest: null }" nested within.
For the last time, list is "{ value: 30, rest: { value: 20, rest: { value: 10, rest: null } } }"
-> "{ value: 20, rest: { value: 10, rest: null } }" nested within.