array cross-over loop in javascript - javascript

let's say i have this code :
let variants = []
let variant = {
variantName: 'Size',
variantItems: ['XL','MD','SM']
}
variants.push(variant)
variant = {
variantName: 'Color',
variantItems: ['Red','Blue']
}
variants.push(variant)
okay, now how do i output it to something like this :
and the variants array can contain many variant object
and if there is one more object inside variants array :
variant = {
variantName: 'Material',
variantItems: ['Plastic','Wood', 'Ceramic']
}
variants.push(variant)
and it will be outputted like this :
Please help, i very appreciate your help...

You can use a recursive function with Array#map, and Array#concat to flatten the results:
const variants = [{"variantName":"Size","variantItems":["XL","MD","SM"]},{"variantName":"Color","variantItems":["Red","Blue"]},{"variantName":"Material","variantItems":["Plastic","Wood","Ceramic"]}];
const addVariants = (variants) => {
// destructure the 1st item array, and the rest of the variants
const add = ([{ variantName, variantItems }, ...variants], row = []) =>
// iterate the variants and flatten
[].concat(...variantItems.map((variantItem) => {
// create a new array for the current row, and the variant string
const currRow = [...row, `${variantName}: ${variantItem}`];
// if there are more variants, invoke add with the remaining variants and the current row, or join the array to a string (the end result)
return variants.length ? add(variants, currRow) : currRow.join(', '); // instead of currRow.join(', ') replace with [currRow] if you want an array
}));
return add(variants);
}
const result = addVariants(variants);
console.log(result);

This is a functional ES6 approach.
let variants = [{
variantName: "Size",
variantItems: [
"XL",
"MD",
"SM"
]
},
{
variantName: "Color",
variantItems: [
"Red",
"Blue"
]
}];
let crossJoined = new Array(variants.reduce((product, variant) => (product * variant.variantItems.length), 1))
.fill(0)
.reduce(crossJoin => {
crossJoin.data.push(crossJoin.currentIndexes.map((itemIndex, variantIndex) => `${variants[variantIndex].variantName}: ${variants[variantIndex].variantItems[itemIndex]}`).join(", "));
let incrementableIndex = variants.length - crossJoin.currentIndexes
.slice()
.reverse()
.findIndex((itemIndex, variantIndex) => variants[variants.length - variantIndex - 1].variantItems.length > itemIndex + 1) - 1;
crossJoin.currentIndexes[incrementableIndex]++;
crossJoin.currentIndexes = crossJoin.currentIndexes.map((value, index) => (index > incrementableIndex
? 0
: value));
return (crossJoin.currentIndexes.length == variants.length
? crossJoin
: crossJoin.data);
}, {
data: [],
currentIndexes: new Array(variants.length).fill(0)
}).join("\n");
console.log(crossJoined);
First, an array is created with a length of all variantItems array lengths multiplied, then it is zero-filled. Next, we reduce it to another array.
crossJoin is the aggregator that holds an object of this structure most of the time:
{
data: [ … ],
currentIndexes: [ 1, 3 ] // Corresponds to variants[0].variantItems[1]
// and variants[1].variantItems[3]
}
That is “most of the time”, until the end, where currentIndexes won’t be used anymore and only its data property is returned.
So in each reduce iteration, we first push a new entry to crossJoin.data, using crossJoin.currentIndexes and the indexes of that array. Removing .join(", ") will result in an entry of the structure
[ "Size", "XL" ]
Next, we need to increment the numbers in crossJoin.currentIndexes:
[ 0, 0 ] should be incremented to [ 0, 1 ], because there is a second color at the index 1. [ 0, 1 ] should be incremented to [ 1, 0 ], because there is no third color, but a second size, but then we need the first color again, and so on. The last valid index array is [ 2, 1 ] which corresponds to the third size and the second color, which is the last combination.
incrementableIndex is the last possible index that can still be incremented. Once incremented, all subsequent indexes have to be 0.
You see those variants.length - something - 1 twice, because you need to find the first index from the end, so you have to reverse (a copy of — hence the slice) the array, then re-interpret the found index as an index from the start again.
The return returns this crossJoin object for the next iteration. The condition crossJoin.currentIndexes.length == variants.length applies to the very end, where no index can be incremented anymore. It adds a NaN to the currentIndexes array, so the lengths don’t match, and instead of filtering out or preventing the NaN, I just ignored and discarded it altogether.
The output will be an array of all combinations. You can .join("\n") it to make a string where each combination is separated by a line-break, or you can use .forEach to append each combination to a list, for example.

Use .each loop on variants array. Take another array and add object using size, color and material. So it will become combined array of your requirement. Then print new array data as per requirement using .each loop .

Related

NODEJS - Prevent duplicate keys from being added into an object array

I have a function I am trying to use to not add duplicates (Later on will combine)
function arrayCombine(arrayOfValues, arrayOfValues2) {
for (var arrName in arrayOfValues2) {
if (arrayOfValues.indexOf(arrName)==-1) arrayOfValues.push([arrName, arrayOfValues2[arrName]]);
}
return arrayOfValues;
}
The arrays are lets say:
arrayOfValues
[
[ 'test', 11 ],
[ 'test2', 13 ],
[ 'test3', 16 ],
]
arrayOfValues2
[
[ 'test4', 12 ],
[ 'test2', 25 ],
]
When I try to combine these, it does NOT remove the duplicate test2 here. It pushes it anyways.
This does not occur if the number does not exist so I assume when I'm checking for INDEXOF, there has to be a way to check for only the named value and not the numbered value too. What I mean is:
function arrayCombine(arrayOfValues, arrayOfValues2) {
for (var arrName in arrayOfValues2) {
if (arrayOfValues.indexOf(arrName)==-1) arrayOfValues.push(arrName);
}
return arrayOfValues;
}
Did work originally.
How can I have it only 'check' the name? In the future I will also combine but for now I just want to make sure no duplicate names get added.
Since objects only allow unique keys it may be simpler to use one to collate your data from both arrays. By concatenating the arrays, and then reducing over them to create an object, you can add/combine each nested arrays values as you see fit. Then, to get an array back from the function use Object.values on the object.
const arr1=[["test",11],["test2",13],["test3",16]],arr2=[["test4",12],["test2",25]];
// Accepts the arrays
function merge(arr1, arr2) {
// Concatentate the arrays, and reduce over that array
const obj = arr1.concat(arr2).reduce((acc, c) => {
// Destructure the "key" and "value" from the
// nested array
const [ key, value ] = c;
// If the "key" doesn't exist on the object
// create it and assign an array to it, setting
// the second element to zero
acc[key] ??= [ key, 0 ];
// Increment that element with the value
acc[key][1] += value;
// Return the accumulator for the next iteration
return acc;
}, {});
// Finally return only the values of the object
// which will be an array of arrays
return Object.values(obj);
}
console.log(merge(arr1, arr2));
Additional documentation
Logical nullish assignment
Destructuring assignment

Fill empty values of a nested array with specific value Javascript

I have a function called Action which receives an array parameter like this.
[
['X','','O'],
['O','','O'],
['X','X','']
]
I want this function to return an array like this where each empty section of the previous array is filled individually with a specific value, e.g Y.
[
[
['','Y',''],
['','',''],
['','','']
],
[
['','',''],
['','Y',''],
['','','']
],
[
['','',''],
['','',''],
['','','Y']
]
]
I know I can do that with forEach but it needs nested forEach which I think isn't very optimal. Is there any better way of doing that?
This should work:
let arr = [
['X','','O'],
['O','','O'],
['X','X','']
]
let res = []
arr.map((item, index)=>{
item.map((sub, indx) =>{
if(sub === ""){
let array_=
[
['','',''],
['','',''],
['','','']
];
array_[index][indx] = "Y";
res.push(array_)
}
})
})
console.log(res)
You could avoid nesting loops with the following steps
flatten the matrix into a 1-d array
get the indices of the empty sections
create new flatten 1-d arrays with empty indices from previous result marked as 'Y'
transform flatten 1-d arrays back to 3x3 matrix
const arr = [
["X", "", "O"],
["O", "", "O"],
["X", "X", ""],
]
const res = arr
.flat() // flatten the array
.map((section, i) => [section, i])
.filter(([section, _]) => section === "")
.map(([_, i]) => i) // get the indices of empty sections
.map(sectionIdx =>
Array.from({ length: 9 }, (_, i) => (i === sectionIdx ? "Y" : ""))
) // create new flattened array with empty indices marked as 'Y'
.map(flattenedArr => [
flattenedArr.slice(0, 3),
flattenedArr.slice(3, 6),
flattenedArr.slice(6, 9),
]) // turn back flatten array into 3x3 matrix
console.log(res)
I feel compelled to give the trivial answer, which is to continue using nested .forEach. This results in very readable code, which is, in my experience, more valuable than high performance code most of the time.
I'd begin by looping over each row and column of your input. Every time you encounter an matching cell (i.e. empty string, in your case), clone the input structure (using .map to '') and replace the matching element with the desired replacement string (e.g. 'Y').
const input = [
['X','','O'],
['O','','O'],
['X','X','']
]
const result = [];
input.forEach((row, iRow) => row.forEach((col, iCol) => {
if (col == '') {
const clone = input.map(row => row.map(col => ''));
clone[iRow][iCol] = 'Y';
result.push(clone);
}
}));
console.log(result);

How can I add the objects in multiple arrays along their index

I have an array of objects
[
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}]
]
That I need to reduce to [{data:3},{data:6},{data:9}] via addition.
Objects of index 0 are added, objects of index 1 are added, and objects of index 2 are added.
Is there a Javascript function like reduce that can manage this?
The previous answer is good but it only works if your arrays are always the same size.
For example, having this initial input would break the code:
[
[{data:1},{data:2},{data:3}, {data:4}],
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}]
]
To fix that you could add a check inside the reducer, to make sure the next array has an object at that index.
Another problem is if you have this initial input:
[
[{data:1},{data:2},{data:3}, {data:4}],
[{data:1},{data:2},{data:3}, {data:4}, {data:5}],
[{data:1},{data:2},{data:3}]
]
The last object in the second array would be ignored because the initial reducer only takes into consideration the length of the first array.
To handle those exceptions, you could use this refactored code (based on #mickl answer):
// Initial input with different format
const initialInput = [
[{data:1},{data:2},{data:3}, {data:4}],
[{data:1},{data:2},{data:3}, {data:4}, {data:5}],
[{data:1},{data:2},{data:3}]
];
// Sort and reverse to get the array with most items first
const sortedInput = initialInput.sort().reverse();
// Finally use the refactored reducer
const result = sortedInput.reduce((arr,cur) => {
return arr.map((val, i) => {
// This "if" checks if the next array has an item on the same index of previous array
// Which is not the case, for example, from the second to the third item
if (cur[i] && cur[i].data) {
return { data: val.data + cur[i].data }
} else {
return { data: val.data}
}
})
});
console.log(result)
You can use array.reduce to aggregate the data across multiple arrays and array.map to sum up the values since it takes an arrow function where second parameter represents an index of currently processed element:
let input = [
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}],
[{data:1},{data:2},{data:3}]
];
let result = input.reduce((arr,cur) =>
arr.map((val, i) => ({ data: val.data + cur[i].data })));
console.log(result);

Converting a string into an array of arrays

I have a string of data which look like this
0E-11 GERBER CA 960350E-110.0215.500000000 0E-12 TEHAMA CA 960900E-11214.800000000
I want to convert this string into an array of arrays.
Please note that after every 4 elements this array should be divided into new array and the end result should look like this:
Desire Results:
this.tblData: Array(2)
0: ["0E-11,"GERBER", "CA", "960350E-110.0215.500000000"]
1:["0E-12", "TEHAMA", "CA" ,"960900E-11214.800000000"]
Thanks
You can use the remainder operator and a forEach loop on that string to build an array of arrays, where each nested array is created every n steps:
var result = [];
"0E-11 GERBER CA 960350E-110.0215.500000000 0E-12 TEHAMA CA 960900E-11214.800000000".split(" ").forEach(function(element, index, array) {
if( (index + 1) % 4 === 0) {
result.push([
array[index-3],
array[index-2],
array[index-1],
array[index]
]);
}
});
console.log(result);
You can use reduce for such purposes
let result = "0E-11 GERBER CA 960350E-110.0215.500000000 0E-12 TEHAMA CA 960900E-11214.800000000"
.split(" ") // split the string based on the spaces
.reduce((current, item) => {
if (current[current.length - 1].length === 4) {
// in case the result array has already 4 items in it
// push in a new empty array
current.push([]);
}
// add the item to the last array
current[current.length - 1].push(item);
// return the array, so it can either be returned or used for the next iteration
return current;
}, [ [] ]); // start value for current would be an array containing 1 array
console.log(result);
It starts by splitting your string by spaces, creating an array of the values, and then we can transform the result using the reduce function.
The reduce function will take the second parameter as a start value for the current argument, which will start as an array containing 1 empty array.
Inside the reducer it first check if the last item in the array has a length of 4, in case it does, add the next sub array to the array, and will then push the current item inside the last array.
The result will then be an array containing your arrays
There is no need to use modulus operator, simply increment the loop's counter by 4:
var original = [
'0E-11',
'GERBER',
'CA',
'960350E-110.0215.500000000',
'0E-12',
'TEHAMA',
'CA',
'960900E-11214.800000000'
];
var result = [];
for (var i = 0; i < original.length; i += 4) {
result.push([
original[i],
original[i+1],
original[i+2],
original[i+3],
]);
}
console.log(result);
Output: [ [ '0E-11', 'GERBER', 'CA', '960350E-110.0215.500000000' ],
[ '0E-12', 'TEHAMA', 'CA', '960900E-11214.800000000' ] ]
This assumes that the data is 4 element aligned.

Find Duplicate Array within Array

Given an array of arrays, what would be the efficient way of identifying the duplicate item?
var array = [
[
11.31866455078125,
44.53836644772605
],
[ // <-- Here's the duplicate
11.31866455078125,
44.53836644772605
],
[
11.371536254882812,
44.53836644772605
],
[
11.371536254882812,
44.50140292110874
]
]
I've been working on this with lodash as an accepted dependency, and I get how to just return the "unique" list using _.uniqWith and _.isEqual:
_.uniqWith(array,_.isEqual)
With would give the "unique" version of the list:
[
[ 11.31866455078125, 44.53836644772605 ],
[ 11.371536254882812, 44.53836644772605 ],
[ 11.371536254882812, 44.50140292110874 ]
]
But rather than just reporting the unique elements, I need just the element that is duplicated, and ideally the index of the first occurrence.
Is this actually covered in the lodash library by some combination of methods that I'm missing? Or am I just going to have to live with writing loops to compare elements.
Probably just overtired on this, so fresh eyes on the problem would be welcome.
Trying not to rewrite functions if there are library methods that suit, so I basically am stuck with:
Returning just the duplicate or at least the comparison difference with the "unique list".
Basically identifying the "index of" an array within an array. Though I suppose that can be a filter reduction with _.isEqual once the duplicate item is identified.
Trying also to avoid creating an object Hash/Map and counting the occurrences of keys here as well, or at least not as a separate object, and as something that can be done functionally "in-line".
Lodash gives a lot of useful functions to achieve finding the first duplicate index.
Using the _.findIndex() and _.isEqual() the following code will find the first duplicate index:
var duplicateIndex = _.findIndex(array, function(value, index, collection) {
var equal = _.isEqual.bind(undefined, value);
return _.findIndex(collection.slice(0, index), equal) !== -1;
});
or a bit faster but more verbose:
var duplicateIndex = _.findIndex(array, function(value, index, collection) {
var equal = _.isEqual.bind(undefined, value);
return _.findIndex(collection, function(val, ind) {
return ind < index && equal(val);
}) !== -1;
});
Notice that if no duplicate exists, -1 will be returned.
In a few words the algorithm iterates through array and looks back if the current element does not exist already. If it does, just return the current iteration index.
Please check the working demo.
Here's an approach that uses uniqWith(), and difference():
_.indexOf(array, _.head(_.difference(array, _.uniqWith(array, _.isEqual))));
The basic idea is:
Use uniqWith() to remove the duplicates from array.
Use difference() to compare array against the duplicate-free version. This gets us an array of the duplicates.
Use head() to get the first item of the array. This is the duplicate that we're interested in.
Use indexOf() to find the index of the duplicate, in this case it's 1.
However, if you need the index of the original, and not it's duplicate, we have to make some adjustments:
var duplicate = _.head(_.difference(array, _.uniqWith(array, _.isEqual)));
_.findIndex(array, _.unary(_.partial(_.isEqual, duplicate)));
We're still using uniqWith(), and difference() to find the duplicate. But now, we're using findIndex() to get the index. The reason is that we need to use isEqual() to find the first position of the duplicate, not the second. We construct the predicate using partial() and unary(). The result this time is 0.
You can just use plain javascript to do that, it's not that hard, here is my implementation
for (let i = 0; i < array.length; i++) {
for (let j = i + 1; j < array.length; j++) {
// quick elimination by comparing sub-array lengths
if (array[i].length !== array[j].length) {
continue;
}
// look for dupes
var dupe = true;
for (var k = 0; k < array[i].length; k++) {
if (array[i][k] !== array[j][k]) {
dupe = false;
break;
}
}
// if a dupe then print
if (dupe) {
console.debug("%d is a dupe", j);
}
}
}
The nice part about this implementation is that it will print you multiple times that an array at an index is a dupe for multiple dupes, you can use that fact to count your dupes in each index!
This is actually a very efficient way to do this because the inner for loop (j) always runs from the next position of the outer loop (i). so you half your check count.
And here is a plunk
I don't know how to do this other than to just write the algorithm yourself. Both this answer and the other posted ones aren't very efficient but should be fine:
function findIndex(array, startingIndex, value) {
var predicate = _.partial(_.isEqual, value);
var arraySubset = array.slice(startingIndex+1);
var index = arraySubset.findIndex(predicate);
return index === -1 ? index : index+startingIndex+1;
}
function findDuplicates(array) {
return array.map((value, index) => {
return {
value,
index: findIndex(array, index, value)
};
}).filter(info => info.index !== -1);
}
findDuplicates([1, 2, 3, 4, 1, [ 3 ], [ 4 ], [ 3 ] ]);
// [ { value: 1, index: 4 }, { value: [ 3 ], index: 7 } ] // [ { value: 1, index: 4 }, { value: [ 3 ], index: 7 } ]
This basically creates a map of the array, calling .findIndex() on the remainder of the array, noting down the index of any duplicates, returning information about every item that has a duplicate and what the index of the duplicate is.
One nice thing about this is that it will work for triplicates or any amount of occurrences of a value.
I believe constructing a LUT is one of the most efficient ways when it comes to making comparisons. The following method constructs a LUT by utilizing Array.prototype.reduce() and eventually mutates the original array by removing not only one but all duplicate elements regardless of how many there are.
var arr = [
[
11.31866455078125,
44.53836644772605
],
[
11.31866455078125,
44.53836644772605
],
[
11.371536254882812,
44.53836644772605
],
[
11.371536254882812,
44.50140292110874
]
];
arr.reduce((p,c,i)=> { var prop = c[0]+"" + c[1]+"";
p[prop] === void 0 ? p[prop] = i : p.dups.push(i);
return p;
},{dups:[]}).dups.reverse().forEach( i => arr.splice(i,1))
document.write('<pre>' + JSON.stringify(arr, 0, 2) + '</pre>');
However if you would like to have a new array by keeping the original then obviously it would be much faster procedure.

Categories

Resources