Get combination of all elements in JavaScript Object - javascript

I'm trying to write a program that generates every single item based on my JSON structure and giving each combination an individual number. I found a function on here that so far does its job listing out every combination for me, however I cannot decipher the code to the point I actually even understand what it does. It gives me all the items, listen like Key : Value but honestly I have no idea what part of the code does what and I cannot access it in order to build in my giving them unique numbers. This is the code that I found on here (lost the thread to it):
function getCartesian(object) {
return Object.entries(object).reduce((r, [k, v]) => {
var temp = [];
r.forEach(s =>
(Array.isArray(v) ? v : [v]).forEach(w =>
(w && typeof w === 'object' ? getCartesian(w) : [w]).forEach(x =>
temp.push(Object.assign({}, s, { [k]: x }))
)
)
);
return temp;
}, [{}]);
}
var input = { bookSet: { book: ["book1", "book2", "book3"], title: ["title1", "title2"], author: ["author1"], publisher: ["publisher1"] } },
cartesian = { eachBook: getCartesian(input.bookSet) };
It's just written in a too advanced syntax for me to remotely understand where I have to insert myself to make any calculations. I guess what I'm asking for would be either an explanation or a somewhat more understandable and modifyable code. I definitely need to run through all elements like this is doing and the output looks great from what I could tell so far, I just need to somehow calculate the keys and have an output of a number derived of each object's elements' combined keys.
An example would be book 243 for title 2, author 4 and publisher 3. I hope anyone can make sense of this. Thanks a lot!
EDIT: Included my own data and desired output. The combinations I displayed don't need to make sense.
var Product = {
json: { Product : {
assortment: [
{
name: "Yoghurt",
Flavor: ["natural", "honey", "stracciatella"],
Kind: ["greek", "soy"],
},
{
name: "Sauce",
},
{
name: "Milk Drink",
}
],
Brand: ["Oatly", "Dannon"],
Containment: ["Cup", "Jar"]
}}};
My output I'd like to generate the combinations of all of those and ultimately calculate the numbers on the right in the following screenshot

Given C_1 and C_2 two sets
The cartesian product of C_1 and C_2
is given by C_1 x C_2 = {(c_1_i,c_2_j) for c_1_i in C_1, c_2_j in C_2}
You can build C_1 x C_2 x C_3 by considering (C_1 x C_2) (that you calculated before) and "adjoining" each elem of C_3 to a tuple of C_1 x C_2
And so forth
const cartesianProduct = (C, D) => {
const res = []
C.forEach(c => {
D.forEach(d => {
// in case the tuple contains only one element (the initialization)
// make the elmeent into a tuple
const tuple = Array.isArray(c) ? c : [c]
res.push([...tuple,d])
})
})
return res
}
const nCartesianProduct = (Cs_n) => {
// we adjoin each elem of C_i and we "grow"
return Cs_n.reduce((res, C_i) => cartesianProduct(res, C_i))
}
console.log(nCartesianProduct([['b1', 'b2', 'b3'], ['t1', 't2'], ['a'], ['p']]))

Here is my attempt to lay in a simple terms:
Lets assume an example of
const sets = [ [1], [1,2], [1,2,3] ]
Possible combinations may be logged as following:
1 1 1 1 2 1
1 1 2 -> 1 2 2
1 1 3 1 2 3
Lets think of it as a clock, where last row will increase the value of previous row, once it reaches its maximum. In another words: lets increase i position of the last row and when over the limit -> drop it to zero and increase sibling instead, where if sibling is over the top -> repeat.
Consider the following code:
let sets = [[1,2], [1,2,3], [1,2,3,4], [1,2,3,4,5] ];
let state = sets.map( () => 0 );
console.log(sets, state);
function handleIncreament(i){
if( state[i] >= sets[i].length){
if(i-1 < 0) {
console.log('end of the row');
return false;
}
state[i] = 0;
state[i-1] += 1;
return handleIncreament(i-1);
}
else {
return true;
}
}
while( handleIncreament(state.length - 1) ){
console.log( state );
state[state.length - 1]++;
}
Above will log as follows:
(4) [Array(2), Array(3), Array(4), Array(5)] (4) [0, 0, 0, 0]
(4) [0, 0, 0, 0]
(4) [0, 0, 0, 1]
(4) [0, 0, 0, 2]
(4) [0, 0, 0, 3]
(4) [0, 0, 0, 4]
(4) [0, 0, 1, 0]
(4) [0, 0, 1, 1]
(4) [0, 0, 1, 2]
...
(4) [1, 2, 3, 4]
end of the row
4
With that lets apply it to your example:
const test = { bookSet: { book: ["book1", "book2", "book3"], title: ["title1", "title2"], author: ["author1"], publisher: ["publisher1"] } };
sets = Object.values(test.bookSet);
state = sets.map( () => 0 );
console.log(sets, state);
const matches = [];
while( handleIncreament(state.length - 1) ){
const match = sets[0][state[0]] + ' ' + sets[1][state[1]] + ' ' + sets[2][state[2]] + ' ' + sets[3][state[3]];
matches.push( match );
state[state.length - 1]++
}
console.log(matches);
And expect to get the following:
["book1 title1 author1 publisher1", "book1 title2 author1 publisher1", "book2 title1 author1 publisher1", "book2 title2 author1 publisher1", "book3 title1 author1 publisher1", "book3 title2 author1 publisher1"]

You could take the above data without superfluous parts and simplify the result by creating a flat array of the nested properties.
The numbers of the result picture are not incuded, because of the missing relation of each value to the given data set.
function getCartesian(object) {
return Object.entries(object).reduce((r, [k, v]) => {
var temp = [];
r.forEach(s =>
(Array.isArray(v) ? v : [v]).forEach(w =>
(w && typeof w === 'object' ? getCartesian(w) : [w]).forEach(x =>
temp.push(Object.assign({}, s, { [k]: x }))
)
)
);
return temp;
}, [{}]);
}
var data = {
assortment: [
{
name: "Yoghurt",
Flavor: ["natural", "honey", "stracciatella"],
Kind: ["greek", "soy"],
},
{
name: "Sauce",
},
{
name: "Milk Drink",
}
],
Brand: ["Oatly", "Dannon"],
Containment: ["Cup", "Jar"]
},
result = getCartesian(data)
.map(({ assortment: { name, Flavor = '', Kind = '' }, d = '', Brand, f = '', Containment, h = '', i = '', j = '' }) =>
[name, Flavor, Kind, d, Brand, f, Containment, h, i, j]);
console.log(result.length);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

JavaScript: better/cleaner functional way to find item from a nested array

I am trying to write a find function to find items from matched items from a potentially nested array (without having to flat the array first) and I am trying to write in a FP way.
Here is my attempt:
const nestedArray = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
function findTarget(arr, predicate) {
const helper = ([x, ...xs]) =>
x === undefined
? null
: predicate(x)
? x
: Array.isArray(x)
? helper(x) ?? helper(xs)
: helper(xs)
return helper(arr)
}
findTarget(nestedArray, (item) => item.id === 5)
I think it works but it is not super readable and I am sure there are better ways to write such a function.
Here's how I would implement this using recursion:
function findTarget(value, predicate) {
const isArray = Array.isArray(value);
// Base case: if value is not array and predicate matches, we found a match
if (!isArray) {
if (predicate(value)) return value;
return null;
}
// value must be an array, so run recursion and see if value exists
for (const item of value) {
const foundItem = findTarget(item, predicate);
if (foundItem !== null) {
return foundItem;
}
}
// nothing found
return null;
}
does the same thing that your code does and imo looks cleaner.
Since your example is calling predicate(x) in the first place, it will return a false positive when matching an array with an id: 5 property, so the Array.isArray(x) should go first to avoid this:
const nestedArray = [
Object.assign([{ id: 1 }], { id: 5 }),
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }], null, [[{ id: 5 }]]],
{ id: 6 },
]
function findTargetLoop (arr, match) {
if (!Array.isArray(arr))
return arr && match(arr) ? arr : null;
let item, i = 0;
while (!(item = findTargetLoop(arr[i++], match)) && i < arr.length);
return item ?? null;
}
const findTargetFunc = (arr, match, next) =>
(next = ([item, ...rest]) =>
Array.isArray(item) ? next(item) ?? next(rest)
: item && match(item) ? item
: rest.length ? next(rest) : null)(arr);
const match = item => item.id === 5;
console.log('with iterations', findTargetLoop(nestedArray, match));
console.log('pure functional', findTargetFunc(nestedArray, match));
Here's one approach I can think of. It uses the init function as a sentinel value to distinguish whether the element being searched for has already been found. Before returning, it invokes the accumulated value which is either () => undefined, or () => curr capturing the first element that matches the predicate.
const flatFind = (array, predicate) => {
const init = () => undefined
const reducer = (prev, curr) => (
prev === init
? Array.isArray(curr)
? curr.reduce(reducer, init)
: predicate(curr)
? () => curr
: init
: prev
)
return array.reduce(reducer, init)()
}
const nestedArray = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
console.log(flatFind(nestedArray, item => item.id === 5))
Your form is a good start but the helper is unnecessary and the order of conditions should be changed. Here we use inductive reasoning -
If x is undefined, there is nothing left to match. Return no match.
(inductive) x is defined. If x is an array, find within x or find within xs
(inductive) x is defined and x is a non-array. If x matches predicate f, return the match
(inductive) x is defined and x is a non-array that does not match the predicate f. Find within the sub-problem, xs.
const find = ([x, ...xs], f) =>
x === undefined // 1
? null
: Array.isArray(x) // 2
? find(x, f) ?? find(xs, f)
: f(x) // 3
? x
: find(xs, f) // 4
const t = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
console.log(find(t, (item) => item?.id === 5)) // { id: 5 }
console.log(find(t, (item) => item?.id === 9)) // null
Note, the behavior of your findTarget checks child arrays against the predicate and allows for return of arrays that match the predicate. This is inconsistent behaviour as it's not possible to match the outermost array and the predicate checks for .id properties on arrays. The find solution above avoids this by changing the order of conditions 2 and 3. If you want the ability to return array matches with your function, you can change the order to 1,3,2,4.

Javascript function to get 3 objects based by props

I am trying to make function to get top3 objects from an array based by props. My site can't load up so i think this function runs endlessly but i cant figure out why.
renderItems = (items) => {
let top3 = []
let tempArr = items
let allNumbers = []
while (top3.length < 3){
allNumbers = []
for(let i = 0; i < tempArr.length; i++){
allNumbers = [...allNumbers, tempArr[i].hearts]
}
const result = tempArr.filter(i => i.hearts === Math.max(...allNumbers))
top3 = [...top3, ...result]
let countOfdeleted = 0
for(let i = 0; i < result.length; i++){
tempArr.splice(result[i].id-countOfdeleted, 1)
countOfdeleted++
}
for(let i = 0; i < tempArr.length; i++){
tempArr[i].id = i
}
}
console.log(top3);
}
This answer is based on the assumption that 'items' is an array of objects and that each object in 'items' will have at-least 2 props namely 'id' and 'hearts'.
Further, there is no clarity on the significance of 'countOfdeleted' and 'tempArr'. Hence, it is assumed
that one needs to know how many elements of the 'items' array were not included (in the top3) as 'countOfdeleted'
that the remaining objects need to have their 'id' updated (based on index)
With the aforementioned assumptions, the below should implement the required logic:
const items = [
{ id: 0, hearts: 5 }, { id: 1, hearts: 4 }, { id: 2, hearts: 5 },
{ id: 3, hearts: 3 }, { id: 4, hearts: 5 }, { id: 5, hearts: 2 },
{ id: 6, hearts: 2 }, { id: 7, hearts: 1 }, { id: 8, hearts: 4 }
];
const getTop3 = (arr = items) => {
const maxVal = arr.reduce((fin, itm) => itm.hearts > fin ? itm.hearts : fin, 0);
const topAll = arr.filter(obj => obj.hearts === maxVal);
const sansTop3 = arr
.filter(obj => obj.hearts !== maxVal)
.map((obj, idx) => ({...obj, id: idx}));
console.log('countOfDeleted: ', arr.length - (topAll.length > 3 ? topAll.length : 3));
console.log('rest with updated-id (tempArr): ', sansTop3);
return topAll.length > 3 ? topAll.slice(0, 3) : [...topAll];
};
console.log('\ntop3:\n^^^^\n', getTop3());
Approach / Explanation
Find the 'maximum-value' (maxVal) based on the prop ('hearts')
Find all objects which have the props matching maxVal (stored in array 'topAll')
[Optional: Gather remaining 'items' elements and update their 'id' in 'sansTop3' array, to match the 'tempArr' in the question]
[Optional: Determine the number of 'items' deleted, to match countOfdeleted in the question]
If more than 3 elements have props ('heart') matching 'maxVal', return only 3; otherwise, return the all top-value element/s

How to get object property list data

I have a list of objects. Every object has a property which is a list of elements:
{ name : 'Club 01'
, id : 1
, form : 45
, points : 0
, tactics : 'neutral'
, played : 0
, gameset : 0
, playedWith : [ 8, 1, 2, 3 ]
}
I want to go through the list and console log all existing elements:
for (let a = 0; a<clubs.width; a++) {
for (let b = 0; b<clubs[a].playedWith.width; b++) {
console.log(clubs[a].playedWith[b]);
}
}
when i do it for one item, this works. however when i do it with a loop as aboce, this brings me to
undefined
Whats wrong with my code? How do i console log all items within playedWith property?
Elikill58 is right. Arrays have length property, not width property.
So your code would work well this way:
for (let a = 0; a < clubs.length; a++){
for (let b = 0; b < clubs[a].playedWith.length; b++){
console.log(clubs[a].playedWith[b]);
}
}
Also, if you want to iterate through all items in the array, just for the sake of simplicity, you can write it like so:
for (const club of clubs) {
for (const width of club.playedWith) {
console.log(width);
}
}
You have to use length instead of width for both loop.
Here is an example :
var clubs = [
{ name : 'Club 01'
, id : 1
, form : 45
, points : 0
, tactics : 'neutral'
, played : 0
, gameset : 0
, playedWith : [ 8, 1, 2, 3 ]
}
];
for (let a = 0; a < clubs.length; a++) {
for (let b = 0; b < clubs[a].playedWith.length; b++) {
console.log(clubs[a].playedWith[b]);
}
}
let b = {
name: 'Club 01',
id: 1,
form: 45,
points: 0,
tactics: 'neutral',
played: 0,
gameset: 0,
playedWith: [8, 1, 2, 3],
move: function() {
return `
${ this.name }and
${this.id }and
${ this.form }and
${ this.points }and
${ this.tactics }and
${ this.played }and
${ this.gameset }and
${ this.playedWith }`
}
};
console.log(b.move())
for (var w in b) {
console.log(${w}:${b.move()})
}
Your best bet is to just do this:
Object.keys(data).forEach((key, i) => {
console.log("Property: " + key, "\nValue: ", data[key])
})
This will give you the property and value. You can also tweak this to have more robust logic for finding or parsing different data types.
You have an array in one of your properties. You can handle it like this.
let clubs = [ {}, {} ]
clubs.forEach((club, i) => {
if (club && Array.isArray(club) || typeof club !== 'object') {
return;
// Stop loop bc club is not an object
}
Object.keys(club).forEach((key, i) => { // Iterate through all the object properties
console.log("Property: " + key, "\nValue: ", club[key])
if (Array.isArray(club[key]) { // Add more conditions or look for certain property names (keys).
console.log('Length of array from property: ', club[key].length)
club[key].map(el => console.log(key + ' value: ' + el))
// You can choose how you want to handle this.
/*
Expected output:
playedWith value: 8
playedWith value: 1
playedWith value: 2
playedWith value: 3
*/
}
})
})
you cant get the array length using width.
try this,
var clubs = [{name: 'Club 01', id:1, form: 45, points: 0, tactics: 'neutral', played: 0, gameset: 0, playedWith: [8,1,2,3]}]
for (let club of clubs) {
for(let playedWothId of club.playedWith){
console.log(playedWothId);
}
}

Struggling to loop through an array of objects

in particular I want to access answer's numerical value, so that later on to sum them up.
previously tried length, which is not working for objects.
here is my data:
const qData = [
{
id: 0,
question: "question 1",
answers: [
{ value: 1, text: "rafael" },
{ value: 2, text: "dontaelo" },
{ value: 3, text: "leonardo" },
{ value: 4, text: "michelangelo" }
]
},
{
id: 1,
question: "question 2",
answers: [
{ value: 1, text: "rafael" },
{ value: 2, text: "dontaelo" },
{ value: 3, text: "leonardo" },
{ value: 4, text: "michelangelo" }
]
}
];
export default qData;
I attempted to sum the answer values like so:
handleShowScore = () => {
var i, newScore;
var a = qData.answers;
for (i = 0; i < a.length; i++) {
newScore = newScore + a[i].value;
}
}
qData is an array and doesn't have the property answers, so qData.answers will not work and return undefined.
You first have to loop over the questions, then in each question you'll have to loop over the answers:
const qData = [{id:0,question:"question 1",answers:[{value:1,text:"rafael"},{value:2,text:"dontaelo"},{value:3,text:"leonardo"},{value:4,text:"michelangelo"}]},{id:1,question:"question 2",answers:[{value:1,text:"rafael"},{value:2,text:"dontaelo"},{value:3,text:"leonardo"},{value:4,text:"michelangelo"}]}];
let sum = 0;
for (const question of qData) {
for (const answer of question.answers) {
sum += answer.value;
}
}
console.log(sum);
You could do this reduce the amount of loops if you use flatMap. Pulling up the answers into one large array.
const qData = [{id:0,question:"question 1",answers:[{value:1,text:"rafael"},{value:2,text:"dontaelo"},{value:3,text:"leonardo"},{value:4,text:"michelangelo"}]},{id:1,question:"question 2",answers:[{value:1,text:"rafael"},{value:2,text:"dontaelo"},{value:3,text:"leonardo"},{value:4,text:"michelangelo"}]}];
const answers = qData.flatMap(question => question.answers);
let sum = 0;
for (const answer of answers) {
sum += answer.value;
}
console.log(sum);
Instead of using a for loop to sum the values you could also use reduce, which iterates over an array reducing it to a single value.
const qData = [{id:0,question:"question 1",answers:[{value:1,text:"rafael"},{value:2,text:"dontaelo"},{value:3,text:"leonardo"},{value:4,text:"michelangelo"}]},{id:1,question:"question 2",answers:[{value:1,text:"rafael"},{value:2,text:"dontaelo"},{value:3,text:"leonardo"},{value:4,text:"michelangelo"}]}];
const answers = qData.flatMap(question => question.answers);
const sum = answers.reduce((sum, answer) => sum + answer.value, 0);
console.log(sum);
The sample below does what you want, if I understood your question correctly
// Loop through all questions. Each question is stored in "q".
qData.forEach((q) => {
console.log('qData:', q);
​
// Make an array of all answer values
const answerValues = q.answers.map((a) => {
return a.value;
});
console.log('answerValues:', answerValues);
​
// Sum all answer values together
const totalValues = answerValues.reduce((a, b) => a + b, 0)
console.log('totalValues: ', totalValues);
});
If you want to have the sum of a specific ID
// Find specific ID.
const question = qData.find((q) => q.id === 0);
// Make an array of all answer values
const answerValues = question.answers.map((a) => {
return a.value;
});
console.log('answerValues:', answerValues);
// Sum all answer values together
const totalValues = answerValues.reduce((a, b) => a + b, 0)
console.log('totalValues: ', totalValues);
It's kind of a quiz, right? Let's assume my answers are:
const myAnswers = [{question: 'question 1', myAnswer:'rafael'},{question: 'question 2', myAnswer:'dontaelo'}]
I should have 1 point from the first question, and 2 from the second. We need to go through 2 loops: 1 to find the corresponding question, 1 to find the corresponding number of points:
const myPoints = myAnswers.map(answer => qData.find(question => question.question === answer.question).answers.find(possibleAnswer => possibleAnswer.text === answer.myAnswer).value)
Thats gives me [ 1, 2 ]. Now we need to to the sum with reduce:
const reducer = (accumulator, currentValue) => accumulator + currentValue;
const myScore = myPoints.reduce(reducer, 0);
I have 3 points ;-)

How to get the product in a Array<Model> using typescript

i have a Array,like:
{
"10-08": [
{
"Items": [
{
"RoomNum": 2
}
],
"Dates": {
"From": "2019-10-03T16:00:00.000Z",
"To": "2019-10-08T16:00:00.000Z"
}
}
],
"10-09":[
{
"Items": [
{
"RoomNum": 3
}
],
"Dates": {
"From": "2019-10-07T16:00:00.000Z",
"To": "2019-10-11T16:00:00.000Z"
}
}
],
the differenceInDays is from date-fns.js
now, i want get
(items.RoomNum * differenceInDays(item.TravelDates.To,item.TravelDates.From))
+(items.RoomNum * differenceInDays(item.TravelDates.To,item.TravelDates.From))...
and so on.
For example:
i want get:
2*5 +3*4
how can i do?
This is a great opportunity to use the reduce feature that is a function of Javascript arrays. This function reduces an array down to 1 value.
The syntax looks like this:
const array = [1, 2, 3, 4, 5];
const sum = array.reduce(function(sumSoFar, currentElement) {
return sumSoFar + currentElement;
}, 0 /* initial value of sumSoFar */);
console.log(sum); // 15
So, in your case since you've grouped items together already, the code is a bit more involved, but still not difficult. The final price could be calculated like this:
Object.keys(groups).reduce(function(sum, group) {
return sum + differenceInDays(groups[group][0].Dates.To, groups[group][0].Dates.From) * groups[group][0].Items.reduce(function(subSum, item) {
return subSum + item.RoomNum;
}, 0);
}, 0);
This works because of the distributive property of multiplication that states a * (b + c) = a * b + a * c - we can sum the number of rooms and multiply that by the number of days, and it both is equivalent to the formula you posted and yields cleaner/shorter code.
i think i need this code:
private buildNightNum(
groups: Dictionary<Model[]>,
item: string
) {
let differenceFromTo = groups[item]
.filter(t => t.Dates)
.reduce(
(a, b) => a + differenceInDays(b.Dates.To, b.Dates.From),
0
);
let roomNum = groups[item]
.filter(t => t.Items.filter(s => s.RoomNum))
.reduce(
(a, b) =>
a +
b.Items.filter(s => s.RoomNum)
.map(s => s.RoomNum)
.reduce((c, d) => c + d, 0),
0
);
return differenceFromTo* roomNum ;
}

Categories