I'm making a small tool for handling translations for a website. I already got this code working, but I feel there should be some more elegant and readable way using array methods (mine looks like a mess...).
Basically, I'll get input in the format shown in code (data_import, this is just fake data for testing). It has 4 columns [translationTag, uniqueId, languageId, translation]. Order of rows is same for every language and there is same number of rows for each language. Number of languages may change from 2 upwards.
the desired output would be like this:
const data_import = [
['aaa', {id:1, langId:1, finnish:'tuntematon'}, {id:5, langId:4, english:'unknown'}, {id:9, langId:6, swedish:'okänd'}],
['bbb', {id:2, langId:1, finnish:'auto'}, {id:6, langId:4, english:'car'}, {id:10, langId:6, swedish:'bil'}],
['ccc', {id:3, langId:1, finnish:'polkupyörä'}, {id:7, langId:4, english:'bicycle'}, {id:11, langId:6, swedish:'cykel'}],
['ddd', {id:4, langId:1, finnish:'rullalauta'}, , {id:8, langId:4, english:'skateboard'}, {id:12, langId:6, swedish:'skateboard'}]
];
Here is my code that 'works' but is ugly and unreadable...
export const language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const data_import = [
['aaa', 1, 1, 'tuntematon'],
['bbb', 2, 1, 'auto'],
['ccc', 3, 1, 'polkupyörä'],
['ddd', 4, 1, 'rullalauta'],
['aaa', 5, 4, 'unknown'],
['bbb', 6, 4, 'car'],
['ccc', 7, 4, 'bicycle'],
['ddd', 8, 4, 'skateboard'],
['aaa', 9, 6, 'okänd'],
['bbb', 10, 6, 'bil'],
['ccc', 11, 6, 'cykel'],
['ddd', 12, 6, 'skateboard']];
export const data = process_test(data_import);
function process_test(data) {
const numberOfCols = data[0].length;
const idIndex = numberOfCols - 2;
const arr_result = []
let rowMax = 0;
let rowMaxMulti = 0;
let langIdLast = 0;
data.forEach((row, index) => {
// if = add non-language cols and first language column
if(row[idIndex] === data[0][idIndex]) {
rowMax = index + 1;
const transItem = row.slice(0, idIndex-1);
transItem.push({ id:row[idIndex], langId:row[idIndex], [language[row[idIndex] - 1]]:row[idIndex + 1] });
arr_result[index] = transItem;
langIdLast = row[idIndex];
}
// add other languages to datarow
else {
const transItem = { id:row[idIndex - 1], langId:row[idIndex], [language[row[idIndex] + 1]]:row[idIndex + 1] };
if(langIdLast !== row[idIndex]) rowMaxMulti++;
arr_result[index - rowMax * rowMaxMulti].push(transItem);
langIdLast = row[idIndex];
}
})
return(arr_result);
}
It would be simpler to reduce into an object indexed by the translation tag, and then get that object's values. On each iteration, create an array for the translation tag if it doesn't already exist in the accumulator. Identify the language name from the langId of the current item, and push the new object to the array:
const language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const data_import = [
['aaa', 1, 1, 'tuntematon'],
['bbb', 2, 1, 'auto'],
['ccc', 3, 1, 'polkupyörä'],
['ddd', 4, 1, 'rullalauta'],
['aaa', 5, 4, 'unknown'],
['bbb', 6, 4, 'car'],
['ccc', 7, 4, 'bicycle'],
['ddd', 8, 4, 'skateboard'],
['aaa', 9, 6, 'okänd'],
['bbb', 10, 6, 'bil'],
['ccc', 11, 6, 'cykel'],
['ddd', 12, 6, 'skateboard']];
const data = Object.values(data_import.reduce((a, [tTag, id, langId, word]) => {
if (!a[tTag]) a[tTag] = [tTag];
const langName = language[langId - 1];
a[tTag].push({ id, langId, [langName]: word });
return a;
}, {}));
console.log(data);
You can group the data by tag with Array.prototype.reduce and map it out to the desired format with Object.keys
const language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const data_import = [['aaa', 1, 1, 'tuntematon'],['bbb', 2, 1, 'auto'],['ccc', 3, 1, 'polkupyörä'],['ddd', 4, 1, 'rullalauta'],['aaa', 5, 4, 'unknown'],['bbb', 6, 4, 'car'],['ccc', 7, 4, 'bicycle'],['ddd', 8, 4, 'skateboard'],['aaa', 9, 6, 'okänd'],['bbb', 10, 6, 'bil'],['ccc', 11, 6, 'cykel'],['ddd', 12, 6, 'skateboard']];
const grouped = data_import.reduce((all, [tag, id, langId, tran]) => {
if (!all.hasOwnProperty(tag)) all[tag] = [];
all[tag].push({id, langId, [language[langId-1]]: tran});
return all;
}, {});
const result = Object.keys(grouped).map(tag => [tag, ...grouped[tag]]);
console.log(result);
You could also use
Array.prototype.map()
Array.prototype.filter()
new Set([])
destructuring assignment
const data_import = [["aaa",1,1,"tuntematon"],["aaa",5,4,"unknown"],["aaa",9,6,"okänd"],["bbb",6,4,"car"],["bbb",2,1,"auto"],["bbb",10,6,"bil"],["ccc",11,6,"cykel"],["ccc",7,4,"bicycle"],["ccc",3,1,"polkupyörä"],["ddd",8,4,"skateboard"],["ddd",4,1,"rullalauta"],["ddd",12,6,"skateboard"]],
language = ['Finnish', 'Estonia', 'Polish', 'English', 'Spanish', 'Swedish'];
const keys =[...new Set(data_import.map(v => v[0]))];
let result = keys.map(key => [key, data_import.filter(v => v[0] == key).map(v => {
return {
id: v[1],
langId: v[2],
[language[v[2]-1]]: v[3]
}
})]);
console.log(result)
.as-console-wrapper {max-height: 100% !important;top: 0;}
Related
I have a data structure that looks like this:
let a = [
["base-certificate", 60, 3, 2022],
["case-certificate", 1, 3, 2022],
["standard-certificate", 7, 3, 2022],
];
I want to transform it to look like this:
[{x:[3, 2022], base: 60, case:1, standard: 7}]
I tried using the map method on the array like:
let a = [
["base-certificate", 60, 3, 2022],
["case-certificate", 1, 3, 2022],
["standard-certificate", 7, 3, 2022],
];
let result = a.map((elem) => {
let obj = {};
elem.forEach((e, i) => {
obj["x"] = [e[2], e[3]];
obj[e[0]] = e[1];
});
return obj;
});
console.log(result);
The code above did not get the desired output.
what is the best way to go about this?
You don't need both the map and the forEach, you can remove the outer map.
Also if you want to remove the "-certificate" part, you can use a split.
Something like this should work
let a = [
["base-certificate", 60, 3, 2022],
["case-certificate", 1, 3, 2022],
["standard-certificate", 7, 3, 2022],
];
let obj = {};
a.forEach((e, i) => {
obj["x"] = [e[2], e[3]];
obj[e[0].split('-')[0]] = e[1];
});
console.log(obj);
map() is for creating an array. You just want one object, you should declare it outside the loop and use forEach() to fill it in. You don't need nested loops.
You can use replace() to remove -certificate from the names.
let a = [
["base-certificate", 60, 3, 2022],
["case-certificate", 1, 3, 2022],
["standard-certificate", 7, 3, 2022],
];
let obj = {};
a.forEach((e, i) => {
obj["x"] = [e[2], e[3]];
obj[e[0].replace('-certificate', '')] = e[1];
});
console.log(obj);
let a = [
["base-certificate", 60, 3, 2022],
["case-certificate", 1, 3, 2022],
["standard-certificate", 7, 3, 2022],
];
let obj = {};
a.forEach((e) => {
obj["x"] = [e[2], e[3]];
obj[e[0].replace('-certificate',"")] = e[1];
});
console.log([obj]);
var array1 = [{issueCount: 16, failCount: 38, id: 1},
{issueCount: 15, failCount: 37, id: 2},
{issueCount: 15, failCount: 34, id: 3}];
var array2 = [{id: 1, totalAttempts: 57},
{id: 2, totalAttempts: 59},
{id: 3, totalAttempts: 67},
{id: 4, totalAttempts: 59}];
I have two arrays. From the above arrays, I need to calculate failure Percentage using the (array1. fail count/array2.totalAttempts) * 100 [id is common between two arrays]. And the final array wants in the below format.
outputArray = [{id: 1, issueCount: 16, failCount: 38, percentage: 66.66},
{id: 2, issueCount: 15, failCount: 37, percentage: 62.71},
{id: 3, issueCount: 15, failCount: 34, percentage: 50.74}];
Thanks in advance.
You can achieve this with a simple for loop.
Just check if the id exists in the second array, if so make your calculations.
const array1 = [{issueCount: 16, failCount: 38, id: 1},
{issueCount: 15, failCount: 37, id: 2},
{issueCount: 15, failCount: 34, id: 3}];
const array2 = [{id: 1, totalAttempts: 57},
{id: 2, totalAttempts: 59},
{id: 3, totalAttempts: 67},
{id: 4, totalAttempts: 59}];
const outputArray = [];
array1.forEach(i1 => {
const i2 = array2.find(i => i.id === i1.id);
if(i2) {
outputArray.push({
id: i1.id,
issueCount: i1.issueCount,
failCount: i1.failCount,
percentage: (i1.failCount / i2.totalAttempts) * 100
});
}
});
console.log(outputArray)
You can do:
const array1 = [{issueCount: 16, failCount: 38, id: 1},{issueCount: 15, failCount: 37, id: 2},{issueCount: 15, failCount: 34, id: 3}]
const array2 = [{id: 1, totalAttempts: 57},{id: 2, totalAttempts: 59},{id: 3, totalAttempts: 67},{id: 4, totalAttempts: 59}]
const mergedArrays = Object.values([...array1, ...array2].reduce((a, c) => (a[c.id] = { ...a[c.id], ...c }, a), {}))
const outputArray = mergedArrays
.filter(o => o.issueCount && o.totalAttempts)
.map(({ id, issueCount, failCount, percentage, totalAttempts }) => ({
id,
issueCount,
failCount,
percentage: Math.round(failCount / totalAttempts * 100 * 100) / 100
}))
console.log(outputArray)
Thank you all for your posts. I have also find the solution below.
outputArray = [];
array1.forEach(function(dataItem1, idx) {
var array2Items = array2[idx];
var outputItems = {};
if (dataItem1 && array2Items){
if(dataItem1.id == array2Items.id){
outputItems.id = dataItem1.id;
outputItems.issueCount = dataItem1.issueCount;
outputItems.failCount = dataItem1.failCount;
outputItems.percentage = ((dataItem1.failCount/array2Items.totalAttempts)*100).toFixed(2);
outputArray.push(outputItems);
}
}
});
console.log(outputArray);
let array1 = [{id: 1, name:'xyz', inclusions: [43,23,12]},{id: 2, name: 'abc',inclusions:[43, 12, 90]},{id: 3, name:'pqr', inclusions: [91]}];
let array 2 = [43, 12, 90, 45];
Now i want to get all the elements of array1, which has inclusions present in array2.
So output would be something:
result = [{id: 1, name:'xyz', inclusions: [43,23,12]},{id: 2, name: 'abc',inclusions:[43, 12, 90]}
I am using two for loops, which i don't want. How can i achieve it using filter and includes.
Filter by whether .some of the items of the object being iterated over are included:
let array1 = [{id: 1, name:'xyz', inclusions: [43,23,12]},{id: 2, name: 'abc',inclusions:[43, 12, 90]},{id: 3, name:'pqr', inclusions: [91]}];
let array2 = [43, 12, 90, 45];
const filtered = array1.filter(({ inclusions }) => inclusions.some(
num => array2.includes(num)
));
console.log(filtered);
I know there are multiple ways to remove duplicates from arrays in javascript, the one i use is
let originalArray = [1, 2, 3, 4, 1, 2, 3, 4]
let uniqueArray = array => [...new Set(array)]
console.log(uniqueArray) -> [1, 2, 3, 4]
what i want is something similar but instead of removing the duplicates, to replace it with whatever string or number i want, like this
console.log(uniqueArray) -> [1, 2, 3, 4, "-", "-", "-", "-"]
this has to work with any order, like
[1, 2, 3, 3, 4, 5, 7, 1, 6]
result -> [1, 2, 3, "-", 4, 5, 7, "-", 6]
i tested this solution
const arr = [1, 2, 3, 1, 2, 3, 2, 2, 3, 4, 5, 5, 12, 1, 23, 4, 1];
const deleteAndInsert = uniqueList => {
const creds = uniqueList.reduce((acc, val, ind, array) => {
let { count, res } = acc;
if (array.lastIndexOf(val) === ind) {
res.push(val);
} else {
count++;
};
return { res, count };
}, { count: 0, res: [] });
const { res, count } = creds;
return res.concat(" ".repeat(count).split(" "));
};
console.log(deleteAndInsert(arr));
but only adds it at the end of the uniques, and also, only works with numbers
i want it to work with strings too, like dates as an example
["2021-02-22", "2021-02-23", "2021-02-22", "2021-02-28"]
You could still use a Set and check if the value is in the set.
const
unique = array => array.map((s => v => !s.has(v) && s.add(v) ? v : '-')(new Set));
console.log(...unique([1, 2, 3, 4, 1, 2, 3, 4]));
console.log(...unique([1, 2, 3, 3, 4, 5, 7, 1, 6]));
Just create new Array, use 1 set to control which element appeared, if one element appears more than 1, push new one character like '-'
let originalArray = [1, 2, 3, 4, 1, 2, 3, 4];
let newArray = [];
let set = new Set();
for (let i = 0; i < originalArray.length; i++) {
if(!set.has(originalArray[i])) {
newArray.push(originalArray[i]);
set.add(originalArray[i]);
} else {
newArray.push('-');
}
}
console.log(newArray);
You could do it with reduce
const dashDupes = array => array.reduce((acc, e) => {
if(acc.idx[e])
acc.arr.push('-')
else{
acc.arr.push(e);
}
acc.idx[e] = true;
return acc;
},{idx:{},arr:[]}).arr
console.log(...dashDupes([1, 2, 3, 4, 1, 2, 3, 4]))
console.log(...dashDupes([1, 2, 3, 3, 4, 5, 7, 1, 6]))
This is a very simple approach to the problem:
function uniqueReplace(arr, rep) {
let res = [];
for (x of arr) {
res.push(res.includes(x) ? rep : x);
}
return res;
}
console.log(...uniqueReplace([1, 2, 3, 4, 1, 2, 3, 4], '-'));
console.log(...uniqueReplace([1, 2, 3, 3, 4, 5, 7, 1, 6], '-'));
How can I sum vertically all data from an array of arrays?
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array1.data.map(function(value, index) {
return value + array2.data[index];
}, 0);
});
console.log(result)
The output should be the vertical sum of arrays.
[3,6,9,12,15,18,21,24]
The problem is that array1 return always as undefined.
You code is almost correct but with 1 issues.
You are looping on accumulator. This will be an array of number in second iteration. Instead loop over array2 or current item.
Idea of .reduce is to have same signature for all iteration. If you do not pass default value for accumulator, first iteration will be of type Array<{ label: string, data: Array<number>}> and second iteration will be just Array<number>. So you can skip behavior for first iteration by passing default value as []. Now the calculation will break as array[n] will be undefined. For this, you can use a default value of 0.
So your calculation will look like:
value + (array1[index] || 0)
Following is a sample:
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
var result = arrayOfArrays.reduce(function(array1, array2) {
return array2.data.map(function(value, index) {
return value + (array1[index] || 0);
}, 0);
}, []);
console.log(result)
Use the index/key of map and add to the previous value.
const arrayOfArrays = [{label:'First Value', data:[1,2,3,4,5,6,7,8]},{label:'Second Value', data:[1,2,3,4,5,6,7,8]},{label:'Third Value', data:[1,2,3,4,5,6,7,8]}];
const res = arrayOfArrays.reduce((acc, cur) => (cur.data.map((i, k) => {acc[k] = acc[k] ? acc[k] += i : i}), acc), [])
console.log(res)
you're using reduce in a wrong way, but heres a for loop that does the same job:
arrayOfArrays = [{
label:'First Value', data:[1,2,3,4,5,6,7,8]},{
label:'Second Value', data:[1,2,3,4,5,6,7,8]},{
label:'Third Value', data:[1,2,3,4,5,6,7,8]
}];
const newArr = [];
for(let x = 0; x < arrayOfArrays[0].length; x++){
newArr.push(arrayOfArrays[0].data[x]+arrayOfArrays[1].data[x]+arrayOfArrays[2].data[x])
}
console.log(newArr); // new array
You can flatten the array by looping the array of objects and pushing the data property to a new array, then use reduce/map on the flattened data:
arrayOfArrays = [
{label:'First Value', data:[1,2,3,4,5,6,7,8]},
{label:'Second Value', data:[1,2,3,4,5,6,7,8]},
{label:'Third Value', data:[1,2,3,4,5,6,7,8]}
];
var data = [];
arrayOfArrays.forEach((element)=> {
data.push(element.data)
})
var sum = (r, a) => r.map((b, i) => a[i] + b);
var result = data.reduce(sum);
console.log(result);
Which outputs:
[3, 6, 9, 12, 15, 18, 21, 24]
Working fiddle
If you know that the length of each array is same. you can do as follows
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
let out = arrayOfArrays.reduce((acc, {data}) => acc.map((e, i) => e+data[i]), new Array(8).fill(0));
console.log(out)
You are passing the wrong accumulator which should be an array also in wrong place, it must be with reduce not with map
var result = arrayOfArrays.reduce(function (array1, array2) {
return array1.map(function (value, index) {
return value + array2.data[index];
});
}, Array(8).fill(0));
I would do it like this:
Introduce a helper transport function:
const transport = (arr) => arr[0].map((col, i) => arr.map(row => row[i]));
Get a proper matrix:
const matrix = arrayOfArrays.map(el => el.data)
Then the task becomes trivial:
const res = transport(matrix).map(arr => arr.reduce((x, y) => x + y))
// > (8) [3, 6, 9, 12, 15, 18, 21, 24]
You could take advantage of function generators in case you need to later transform or alterate the values, or just iterate them without needing the entire result set.
In this solution, a function generator is used and the logic applied is:
Get the array with the longest length (assuming length might change)
Get all the elements at index i from 0 to longest length and yield their sum.
arrayOfArrays = [{
label: 'First Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Second Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
},
{
label: 'Third Value',
data: [1, 2, 3, 4, 5, 6, 7, 8]
}
];
/**
Sums elements of arrays inside the array vertically.
*/
function* sumVertically(arr) {
// Get the longest array.
const longestArrayLength = arr.sort(({length: l1}, {length: l2}) => l1 - l2)[0].length;
// Acquire all elements at index [i] of each array and sum them. Yield the sum.
for (let i = 0; i < longestArrayLength; i++) yield arr.map(e => e[i]).reduce((a,b) => a + b, 0);
}
const result = [...sumVertically(arrayOfArrays.map(i => i.data))];
console.log(result);