multidimensional default array - javascript

I've got two multidimensional arrays:
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
The desired result is:
const expected = [
["ad", 50],
["ae", 0],
["af", 0]
]
Here az is ignored because its not valid because its not in the defaultData.
ad is valid and therefore the 0 is overwritten with the value of 50.
How would I best produce this result. Thanks?
Please assume neither array is sorted. The expected result need not be sorted either.
Either vanilla JS or Lodash is fine.

Convert the data to an object (dataByKey) with _.fromPairs(). Map the defaultData, and check if the "key" (index 0) exists in dataByKey. If it is, create a new array with the value from dataByKey. If not return the original array.
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
const dataByKey = _.fromPairs(data)
const result = defaultData.map(o => o[0] in dataByKey ? [o[0], dataByKey[o[0]]] : o)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Use map to iterate over the defaultData, and find to match each iterated array against data:
const defaultData = [['ad', 0],['ae', 0],['af', 0]];
const data = [['az', 20],['ad', 50]];
const expected = defaultData.map(arr => {
// Destructure the first element from `arr`
// and assign it to `code`
const [ code ] = arr;
// `find` the array in `data` where the first element
// matches `code`
const found = data.find(el => el[0] === code);
// If `found` is undefined return the whole array
// otherwise return a new array with the code and the value
// from the found array
return found ? [code, found[1]] : arr;
});
console.log(expected);

You could first create one object from data and then use map method on defaultData to create new array and get value from data by key.
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
].reduce((r, [key, value]) => {
r[key] = (r[key] || 0) + value;
return r;
}, {})
const expected = defaultData.map(([key, value]) => {
return [key, key in data ? data[key] : value]
})
console.log(expected)

Try this out, I am using map() to create a new Array and find() to find elements from the second array.
I compare the first index item of each sub array from the default array, if any of the items from second array has the same element in its first index then I return it, if none matches, nothing is returned.
If I have a matched item then I create a new array using its elements.
If I don't have a matched item I create a new using the default elements of the default item.
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
let newData = defaultData.map(eachDefault => {
let found = data.find(eachData => {
if (eachData[0] == eachDefault[0]) {
return true
}
})
if (found) {
return [...found]
}
return [...eachDefault]
})
console.log(newData)

You can use map/reduce like this:
const defaultData = [
["ad", 0],
["ae", 0],
["af", 0]
]
const data = [
["az", 20],
["ad", 50]
]
const result = defaultData.map(arr => data.reduce(([dKey, dVal], [key, val]) => [ dKey, dKey === key ? val : dVal ], arr));
console.log(result);

Related

Converting Array of Pairs into 2 Separate Arrays Without Iteration

I have an array of pairs that looks like this [[x,y], [x,y] ... ]. I want to format it into an Object where the values are arrays of x and y values like so {keys: [x1, x2, x3 ...], values: [y1, y2, y3 ... ]}.
Are there any array/object operations to complete this operation without iterating through the original list?
The easiest and safest way is to reduce the array to an object, although it requires a loop:
const input = [[3, 300], [2, 200], [1, 100]];
const result = input.reduce((acc, [key, val]) => {
acc.keys.push(key);
acc.values.push(val);
return acc;
}, { keys: [], values: [] });
console.log(result);
I wouldn't actually use the convert to object / Map method (under Original Answer), because it has a serious caveat - duplicate entries the has the same keys would be overridden.
For example:
const input = [[3, 300], [3, 200], [3, 100]];
const obj = Object.fromEntries(input);
const result = { keys: Object.keys(obj), values: Object.values(obj) };
console.log(result);
Original Answer
Building on top of pilchard's answer, I would convert the array to a Map, and then take the Map's keys, and values. I would use a Map, and not an object, because object's keys are always strings, and if the values are integers, the object would also be sorted by their value. A Map would preserve the original type, and won't reorder them.
const input = [[3, 300], [4, 200], [1, 100]];
const map = new Map(input);
const result = { keys: [...map.keys()], values: [...map.values()] };
console.log(result);
An example of converting the same structure to an object:
const input = [[3, 300], [4, 200], [1, 100]];
const obj = Object.fromEntries(input);
const result = { keys: Object.keys(obj), values: Object.values(obj) };
console.log(result);
The process is iterative, but you can hide it by using existing Object methods: Object.fromEntries(), Object.keys(), Object.values()
const input = [['a', 1], ['b', 2], ['c', 3]];
const obj = Object.fromEntries(input);
const result = { keys: Object.keys(obj), values: Object.values(obj) };
console.log(result);
Ori Drori's refinement using a Map is more robust not only for numeric values but for any type.
const input = [[new Date(), { y: 1 }], [new Date(), { y: 2 }], [new Date(), { y: 3 }]];
const map = new Map(input);
const result = { keys: [...map.keys()], values: [...map.values()] };
console.log(result);
console.log(result.keys[0].valueOf());
No space left for losers, but just another way
const input = [[3, 300], [4, 200], [1, 100]];
const result = { keys: Array.from(input, x => x[0]), values: Array.from(input, x => x[1]) };
console.log(result);

Multi Array filter based on another array

I have a multi-array (arr1) and an arrey (arr2)
var arr1 = [["Id", "Sku", "Name"], [10, "xxx", "yyy"], [20, "aaa", "eee"]];
var arr2 = ["Id", "Name"];
I would like that when the values in arr1[0] is not an element of the arr2 all the index value will be removed, so I will get this output:
var arr3 = if arr1[0] = arr2
console.log(arr3); // [["Id", "Name"], [10, "yyy"], [20, "eee"]];
Any help?
First, build a list of the indexes to remove by seeing if the key exists in arr2. Then, return arr1 with those indexes filtered out of each element.
function filterUnusedElement(inputElement) {
const indexesToRemove = inputElement.reduce((acc, val, index) => arr2.includes(val) ? acc : [...acc, index], []);
return arr1.map(i => i.filter((_,index) => !indexesToRemove.includes(index)));
}
console.log(filterUnusedElement(arr1[0]));
// [["Id", "Name"], [10, "yyy"], [20, "eee"]]

How to get all index of an array to new array

I have an array of arrays that I am trying to map. But I am having an issue with my mapping because mapping all indexes not just the one I want it to be mapping. I figured I can work my way around this by creating a new array of only of the data I am trying to map.
How can I push all index 2 into a new array? I have not been able to find an example of what I am attempting to do.
I am expecting an outcome of a new array that equals [231, 431, 481]
Here is an example of my code:
const array = [ ["name", 1, 231], [ "name", 2, 431], ["name", 3, 481] ]
console.log(array)
let percentage = array.map(function (num, i) {
return 100 * ((num - array[i - 1]) / (array[i - 1]));
});
You can do this
const array = [ ["name", 1, 231], [ "name", 2, 431], ["name", 3, 481] ]
const arrayNew = array.map(x => x[2])
// arrayNew === [231, 431, 481]
Check the inline comments
const array = [
["name", 1, 231],
["name", 2, 431],
["name", 3, 481],
];
// If you know, there will be 3 elements in array
console.log(array.map(([, , last]) => last));
console.log(array.map(({ 2: last }) => last));
// For any size of sub array, get the last element in map
console.log(array.map((arr) => arr[arr.length - 1]));

max with condition in Javascript

I have two arrays:
search array: [['#S!', 1, 1], ['#$#', 2, 5], ['#S!', 10, 12], ['#$#', 21, 5]]
and key array: ['#S!','#$#']
I want to look up into search array based on the key array element and create a resultant array which looks like this:
[[key array element,max while lookup for value at index 1 in search array, max while lookup for value at index 2 in search array], [...]]
Here is my code for the same:
let resultant = [];
keys.forEach((ele, ind) => {
resultant[ind] = [
ele,
Math.max(searchArray.filter(element => element[0] === ele)),
Math.max(searchArray.filter(element => element[0] === ele))
];
});
Now I am confused in these statements:
Math.max(newSet.filter(element => element[0] === ele)),
Math.max(newSet.filter(element => element[0] === ele))
Because filter will return the entire array but I want to find max of element with index 1 and in second statement I want to return max of element with index 2 which have the element with index 0 same as the key which I have provided.
Here is one simple test case:
search Array: [["A",1,2],["A",12,23],["A",11,23],["A",14,42],["A",71,32],["B",113,42],["B",145,62],["C",91,32],["C",14,222],["C",111,2]]
keys Array: ["A","B","C"]
Output: [["A",71,42],["B",145,62],["C",111,222]]
As you can see max corresponding to the elements are mapped to the same elements. Can someone help me with this? Is there a better or more optimized algorithm for the same than what I am using?
You could take a dynamic approach with an object for the wanted keys.
function max(search, keys) {
const temp = search.reduce((r, [key, ...data]) => {
if (!r[key]) r[key] = [key, ...data];
else data.forEach((v, i) => { if (r[key][i + 1] < v) r[key][i + 1] = v; });
return r;
}, {});
return keys.map(key => temp[key]);
}
console.log(max([['#S!', 1, 1], ['#$#', 2, 5], ['#S!', 10, 12], ['#$#', 21, 5]], ['#S!','#$#']));
console.log(max([["A", 1, 2],["A", 12, 23],["A", 11, 23], ["A", 14, 42], ["A", 71, 32], ["B", 113, 42], ["B", 145, 62], ["C", 91, 32], ["C", 14, 222], ["C", 111, 2]], ["A", "B", "C"]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try use js array flatten method to do this,
let searchArray = [["A",1,2],["A",12,23],["A",11,23],["A",14,42],["A",71,32],["B",113,42],["B",145,62],["C",91,32],["C",14,222],["C",111,2]];
let keysArray = ["A","B","C"];
console.clear();
let output = [];
keysArray.forEach(item => {
let groupSearchArray = searchArray.filter(f => f[0] === item);
let sortedArray = groupSearchArray.flat(1).filter(f => f !== item).sort().reverse();
output.push([item, sortedArray[0], sortedArray[1]]);
});
console.log(output);

How can I turn an array into an object with the name being the first value in the array and the properties an array of the subarrays?

What is the best way to take a multidimensional array with an unknown list of elements and group it into an object to remove repeated values in the first element of the subarray:
For example, I'd like to turn this:
const arr = [[a, 1, 4], [b, 3, 4], [c, 1, 7], [a, 2, 5], [c, 3, 5]]
Into this:
arrResult = {a:[[1, 4],[2, 5]], b:[[3, 4]], c:[[1, 7],[3, 5]]}
I thought about sorting this and then splitting it or running some kind of reduce operation but couldn't figure out exactly how to accomplish it.
You only need to use reduce (and slice), no need for sorting or splitting
var arr = [['a', 1, 4], ['b', 3, 4], ['c', 1, 7], ['a', 2, 5], ['c', 3, 5]];
var arrResult = arr.reduce((result, item) => {
var obj = result[item[0]] = result[item[0]] || [];
obj.push(item.slice(1));
return result;
}, {});
console.log(JSON.stringify(arrResult));
You can use reduce like this:
const arr = [["a", 1, 4], ["b", 3, 4], ["c", 1, 7], ["a", 2, 5], ["c", 3, 5]];
var result = arr.reduce((obj, sub) => {
var key = sub[0]; // key is the first item of the sub-array
if(obj[key]) obj[key].push(sub.slice(1)); // if the there is already an array for that key then push this sub-array (sliced from the index 1) to it
else obj[key] = [sub.slice(1)]; // otherwise create a new array that initially contain the sliced sub-array
return obj;
}, {});
console.log(result);
you could use reduce and destructuring like this:
const arr = [['a', 1, 4],['b', 3, 4],['c', 1, 7],['a', 2, 5],['c', 3, 5]]
function sub(arr) {
return arr.reduce((obj, [key, ...value]) => {
obj[key] ? obj[key].push(value) : obj[key] = [value]
return obj
}, {})
}
console.log(sub(arr));
I like this solution better because it abstracts away the collation but allows you to control how items are collated using a higher-order function.
Notice how we don't talk about the kind or structure of data at all in the collateBy function – this keeps our function generic and allows for it to work on data of any shape.
// generic collation procedure
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
// generic head/tail functions
const head = ([x,...xs]) => x
const tail = ([x,...xs]) => xs
// collate by first element in an array
const collateByFirst = collateBy (head)
// your custom function, using the collateByFirst collator
// this works much like Array.prototype.reduce
// the first argument is your accumulator, the second argument is your array value
// note the acc=[] seed value used for the initial empty collation
const foo = collateByFirst ((acc=[], xs) => [...acc, tail(xs)])
const arr = [['a', 1, 4], ['b', 3, 4], ['c', 1, 7], ['a', 2, 5], ['c', 3, 5]]
let collation = foo(arr);
console.log(collation.get('a')) // [ [1,4], [2,5] ]
console.log(collation.get('b')) // [ [3,4] ]
console.log(collation.get('c')) // [ [1,7], [3,5] ]
Of course you could write it all in one line if you didn't want to give names to the intermediate functions
let collation = collateBy (head) ((acc=[], xs) => [...acc, tail(xs)]) (arr)
console.log(collation.get('a')) // [ [1,4], [2,5] ]
Lastly, if you want the object, simply convert the Map type to an Object
let obj = Array.from(collation).reduce((acc, [k,v]) =>
Object.assign(acc, { [k]: v }), {})
console.log(obj)
// { a: [ [1,4], [2,5] ],
// b: [ [3,4] ],
// c: [ [1,7], [3,5] ] }
Higher order functions demonstrate how powerful generic procedures likes collateBy can be. Here's another example using the exact same collateBy procedure but performing a very different collation
const collateBy = f => g => xs => {
return xs.reduce((m,x) => {
let v = f(x)
return m.set(v, g(m.get(v), x))
}, new Map())
}
const collateEvenOdd = collateBy (x => x % 2 === 0 ? 'even' : 'odd')
const sumEvenOdd = collateEvenOdd ((a=0, b) => a + b)
let data = [2,3,4,5,6,7]
let collation = sumEvenOdd (data)
let even = collation.get('even')
let odd = collation.get('odd')
console.log('even sum', even) // 2 + 4 + 6 === 12
console.log('odd sum', odd) // 3 + 5 + 7 === 15

Categories

Resources