Array.reduce on a multidimensional array to array of objects - javascript

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)

Related

inserting the elements of a list into an object

I have a list. Like this;
0: {value: 50, key: 'A'}
1: {value: 10, key: 'B'}
2: {value: 15, key: 'C'}
3: {value: 20, key: 'D'}
4: {value: 25, key: 'E'}
I want to convert this list into an object.
for example; the output should be like this:
let data = [{
A: 50,
B: 10,
C: 15,
D: 20,
E: 25
}]
I need to handle this issue on the javascript side, but because I am not familiar with javascript, the solutions I found did not work, I usually always output this way.
[ { A:50 }, { B: 10 }, { C: 15 } { D: 20 }, { E: 25 } ]
You can use Object.fromEntries after converting the array to an array of key-value pairs with Array#map.
let arr = [{value: 50, key: 'A'},{value: 10, key: 'B'},{value: 15, key: 'C'},{value: 20, key: 'D'},{value: 25, key: 'E'}];
let res = Object.fromEntries(arr.map(x => [x.key, x.value]));
console.log(res);
You can convert an array into an object using the reduce method:
const x = [
{value: 50, key: 'A'},
{value: 10, key: 'B'},
{value: 15, key: 'C'},
{value: 20, key: 'D'},
{value: 25, key: 'E'}
]
function convert(arr) {
return arr.reduce((acc, item) => {
acc[item.key] = item.value;
return acc;
}, {})
}
console.log(convert(x))

Count the number of occurrences and add it to grouped objects in a JSON array

Here's my input value -
var input = [
{
status: 'yes',
data: ['a', 'b', 'c', 'd'],
score: 2,
rank: 2,
},
{
status: 'yes',
data: ['a', 'b', 'c'],
score: 9,
rank: 2,
},
{
status: 'yes',
data: ['a', 'b', 'c'],
score: 8,
rank: 2,
},
{
status: 'no',
data: ['a', 'b', 'c', 'd'],
score: 12,
rank: 3,
},
{
status: 'no',
data: ['a', 'b', 'c'],
score: 9,
rank: 3,
},
{
status: 'no',
data: ['a', 'b', 'c', 'd'],
score: 5,
rank: 3,
},
]
And here's what I'm trying to get as an output value -
[
{
status: 'yes',
data: ['a', 'b', 'c', 'd'],
occurrence: 1,
rank: 2,
},
{
status: 'yes',
data: ['a', 'b', 'c'],
occurrence: 2,
rank: 2,
},
{
status: 'no',
data: ['a', 'b', 'c', 'd'],
occurrence: 2,
rank: 3,
},
{
status: 'no',
data: ['a', 'b', 'c'],
occurrence: 1,
rank: 3,
},
]
The idea is to -
Remove the score parameter from all the objects
Add the occurrence parameter to all the objects
Assign the "occurrence" value i.e. the number of times we see the same data being repeated as per each of the status
Here's the code that I'm using (adopted from the 2nd half of this solution) -
const res = Array.from(input.reduce((acc, {score, ...r}, index) => {
const key = JSON.stringify(r);
const current = acc.get(key) || {...r, occurrence: 0};
return acc.set(key, {...current, occurrence: current.occurrence + index});
}, new Map).values());
console.log(res);
But that's producing an unexpected output that looks like this -
[
{
"status": "yes",
"data": ["a","b","c","d"],
"rank": 2,
"occurrence": 0
},
{
"status": "yes",
"data": ["a","b","c"],
"rank": 2,
"occurrence": 3
},
{
"status": "no",
"data": ["a","b","c","d"],
"rank": 3,
"occurrence": 8
},
{
"status": "no",
"data": ["a","b","c"],
"rank": 3,
"occurrence": 4
}
]
There's something that I'm missing to get the correct occurrences & I can't for the life of me understand what.
I will do that this way
const
input =
[ { status: 'yes', data: [ 'a', 'b', 'c', 'd' ], score: 2, rank: 2 }
, { status: 'yes', data: [ 'a', 'b', 'c' ], score: 9, rank: 2 }
, { status: 'yes', data: [ 'a', 'b', 'c' ], score: 8, rank: 2 }
, { status: 'no', data: [ 'a', 'b', 'c', 'd' ], score: 12, rank: 3 }
, { status: 'no', data: [ 'a', 'b', 'c' ], score: 9, rank: 3 }
, { status: 'no', data: [ 'a', 'b', 'c', 'd' ], score: 5, rank: 3 }
]
, result = input.reduce((a,{status,data,rank}) =>
{
let same = a.find( x => x.status === status
&& x.rank === rank
&& JSON.stringify(x.data) === JSON.stringify(data)
)
if (!same)
a.push({ status, data: [...data], occurrence: 1, rank })
else
same.occurrence++
return a
},[])
console.log( result )
.as-console-wrapper {max-height: 100%!important;top:0}
I really didn't catch what did you want to do with occurence value. In your example you were showing:
status: 'no', data: ['a', 'b', 'c', 'd'], occurrence: 2,
And why is here occurence equals 2 whereas 'no' is the first status with this value?
At first try to remove current.occurrence + index and leave just current.occurrence if it will be always zero it will be correct to make next steps to calculate what you want.
I doubt that you need index there. Try to explain, and it helps to provide a decision.

Flatten nested objects, keeping the properties of parents

I have a data structure that has this shape:
[
{
a: "x",
val: [
{ b: "y1", val: [1, 2, 3] },
{ b: "y2", val: [4, 5, 6] },
],
},
];
An example with 3 levels:
[
{
a: "x",
val: [
{ b: "y1", val: [
{c: "z1", val: [1, 2]}
] },
{ b: "y2", val: [
{ c: "z2", val: [3, 4] },
{ c: "z3", val: [5, 6, 7] },
{ c: "z4", val: [8] }
] },
],
},
];
Each object always has the same level of nesting, and I know the max depth of nesting in advance. We also know the names of the keys in advance: we know that the key at level 1 will be named a, the one at level 2 will be named b, and so on.
I'm looking to create a function that transforms the first example into:
[
{
a: "x",
b: "y1",
val: [1, 2, 3],
},
{
a: "x",
b: "y2",
val: [4, 5, 6],
},
];
that is, a flat array with values and keys inherited from parents.
I've got a solution which works for the first example:
const res = [
{
a: "x",
val: [
{ b: "y1", val: [1, 2, 3] },
{ b: "y2", val: [4, 5, 6] },
],
},
].flatMap((x) => x.val.flatMap((d) => ({ a: x.a, ...d })));
console.log(res);
but I'm struggling to turn it into a recursive function.
Thank you in advance for your help!
You could have a look to the arrays and if no objects inside return an object, otherwise map val property by storing other properties.
const
isObject = o => o && typeof o === 'object',
flat = array => {
if (!array.every(isObject)) return { val: array };
return array.flatMap(({ val, ...o }) => {
const temp = flat(val);
return Array.isArray(temp)
? temp.map(t => ({ ...o, ...t }))
: { ...o, ...temp };
});
},
data0 = [{ a: "x", val: [{ b: "y1", val: [1, 2, 3] }, { b: "y2", val: [4, 5, 6] }] }],
data1 = [{ a: "x", val: [{ b: "y1", val: [{ c: "z1", val: [1, 2] }] }, { b: "y2", val: [{ c: "z2", val: [3, 4] }, { c: "z3", val: [5, 6, 7] }, { c: "z4", val: [8] }] }] }];
console.log(flat(data0));
console.log(flat(data1))
.as-console-wrapper { max-height: 100% !important; top: 0; }

Convert list object into array

I have a list object
{
value: 5,
rest: {
value: 10,
rest: {
value: 15,
rest: null
}
}
}
that should be converted into array. I was trying to iterate through the list to take the values and push them into array.
function listToArr(obj){
let arr = []
for (let val in object){
arr.push(Object.values(val))
}
return arr
}
But I am getting [ [ 'v', 'a', 'l', 'u', 'e' ], [ 'r', 'e', 's', 't' ] ]
You'd need to reassign the object inside a loop and access its value property:
console.log(listToArr({ value: 5, rest: { value: 10, rest: { value: 15, rest: null } } }));
function listToArr(obj){
const arr = [];
while (obj.rest) {
arr.push(obj.value);
obj = obj.rest;
}
arr.push(obj.value);
return arr;
}
Since the keys are static, using Object.values or for..in doesn't accomplish anything.
An ES6 style approach:
let listToArr = (obj) => obj.rest ? [obj.value, ...listToArr(obj.rest)] : [obj.value];
console.log(listToArr({ value: 5, rest: { value: 10, rest: { value: 15, rest: null } } }));
There's a nice recursive solution for this as well. Something like:
let a = {
value: 5,
rest: {
value: 10,
rest: {
value: 15,
rest: null
}
}
}
function listToArr(obj, arr){
arr = arr || [];
if (!obj) return arr;
return listToArr(obj.rest, arr.concat(obj.value));
}
console.log(listToArr(a));

sum values in object if multiple keys are the same JS

for example i have 5 objects:
{ row: aa, col: 1, value: 1 }
{ row: bb, col: 2, value: 1 }
{ row: bb, col: 3, value: 1 }
{ row: aa, col: 1, value: 1 }
{ row: aa, col: 2, value: 1 }
i want to sum values if row and col are the same, so the output should be:
{ row: aa, col: 1, value: 2 }
{ row: bb, col: 2, value: 1 }
{ row: bb, col: 3, value: 1 }
{ row: aa, col: 2, value: 1 }
thank you for your help!
tried this:
Sum javascript object propertyA values with same object propertyB in array of objects
You can do this with reduce() and one object to store keys.
var data = [
{ row: 'aa', col: 1, value: 1 },
{ row: 'bb', col: 2, value: 1 },
{ row: 'bb', col: 3, value: 1 },
{ row: 'aa', col: 1, value: 1 },
{ row: 'aa', col: 2, value: 1 }
]
var o = {}
var result = data.reduce(function(r, e) {
var key = e.row + '|' + e.col;
if (!o[key]) {
o[key] = e;
r.push(o[key]);
} else {
o[key].value += e.value;
}
return r;
}, []);
console.log(result)
Just for completeness, with a version for variable keys, an object for grouping the parts and Array#forEach.
var data = [{ row: 'aa', col: 1, value: 1 }, { row: 'bb', col: 2, value: 1 }, { row: 'bb', col: 3, value: 1 }, { row: 'aa', col: 1, value: 1 }, { row: 'aa', col: 2, value: 1 }],
grouped = [];
data.forEach(function (a) {
var key = ['row', 'col'].map(function (k) { return a[k]; }).join('|');
if (!this[key]) {
this[key] = { row: a.row, col: a.col, value: 0 };
grouped.push(this[key]);
}
this[key].value += a.value;
}, Object.create(null));
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }
What I would do is put your objects in an array then iterate over that and check on each iteration if the key of a new object matches that of an old one and load the objects into a separate array if there isn't a match. If it does match then add its value to the value of the old own. I tested the following code and it seems to work how you want.
var array = [{ row: 'aa', col: 1, value: 1 },
{ row: 'bb', col: 2, value: 1 },
{ row: 'bb', col: 3, value: 1 },
{ row: 'aa', col: 1, value: 1 },
{ row: 'aa', col: 2, value: 1 }];
var newArray = [];
for(var x in array) {
for(var y in newArray) {
var found = false;
if(array[x].row == newArray[y].row && array[x].col == newArray[y].col) {
newArray[y].value += array[x].value;
found = true;
break;
}
}
if(!found) {
newArray.push(array[x]);
}
}
console.log(newArray);

Categories

Resources