lodash - Remove columns from datatable (2D matrix) if all values are null - javascript

I have a 2D matrix like this one where the first row is the columns names and other rows are values.
var datatable = [
["a", "b", "c", "d"], //first row are columns names
[ 1, 0, null, 3 ], //other rows are values
[ 6, null, null, 8 ]
];
I would like to remove columns when all values are null as the expected result below:
var datatable = [
["a", "b", "d"], //first row are columns names
[ 1, 0, 3 ], //other rows are values
[ 6, null, 8 ]
];
The numbers of rows and columns can vary. If there is a compact and fast way to achieve it with lodash that's perfect.

Use .flow() that creates a function that transposes the array using _.unzip(), rejects arrays that have all null values, and then unzips the array back to the original form:
const { flow, partialRight: pr, unzip, reject, tail, every, isNull } = _; // convert to imports
const fn = flow(
unzip,
pr(reject, flow(tail, pr(every, isNull))),
unzip,
);
const datatable = [
["a", "b", "c", "d"], //first row are columns names
[ 1, 0, null, 3 ], //other rows are values
[ 6, null, null, 8 ]
];
const result = fn(datatable);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
And the terser lodash/fp solution:
const { flow, unzip, reject, tail, every, isNull } = _; // convert to imports
const fn = flow(
unzip,
reject(flow(
tail,
every(isNull)
)),
unzip,
);
const datatable = [
["a", "b", "c", "d"], //first row are columns names
[ 1, 0, null, 3 ], //other rows are values
[ 6, null, null, 8 ]
];
const result = fn(datatable);
console.log(result);
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>

You could get first the columns with all null values and then filter the rows.
var datatable = [["a", "b", "c", "d"], [1, 0, null, 3], [6, null, null, 8]],
cols = datatable
.slice(1) // omit header
.reduce((r, a) => a.map((v, i) => r[i] || v !== null), []);
datatable = datatable.map(a => a.filter((_, i) => cols[i]));
console.log(datatable);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Here you have my approach using map(), filter() and some().
var datatable = [
["a", "b", "c", "d"],
[ 1, 0, null, 3 ],
[ 6, null, null, 8 ]
];
let res = datatable.map(
x => x.filter((_, idx) => datatable.slice(1).some(arr => arr[idx] !== null))
);
console.log(res);

Slightly longer code, but similar idea.
var datatable = [
["a", "b", "c", "d"],
[1, 0, null, 3],
[6, null, null, 8]
];
/* first create a simple key val map as {a:[1,6],b:[0,null] etc to weed out the keys with all nulls */
let keyValMap = datatable.reduce((acc,arr,index) => {
if(index===0) {
arr.forEach(key => {
if(!acc[key])
acc[key]=[]
});
} else {
Object.keys(acc).map((key,index) => acc[key].push(arr[index]));
}
return acc;
},{});
// now extract only those keys that do not have every element as null
let validKeys = Object.keys(keyValMap).filter(key => !keyValMap[key].every(i => i===null));
// pivot back from keyValMap the values based on validKeys
let updatedDataTable = [validKeys, ...validKeys.map(key => keyValMap[key]).reduce((acc,val)=>{
val.forEach((elem,i) => {
if(!acc[i]) acc[i]=[];
acc[i].push(elem)
});
return acc;
},[]) ]
console.log(updatedDataTable);

Related

Adding object value array to separate array object

I have an array of objects, and and object with arrays as value. I'm trying to take the array from the object and add it as a value to a new key in the objects in the array. When you run the example below you see the object key is instead added as an array of its characters and I'm guessing I'm using Object.values() incorrectly?
So instead of the output being like;
{
"arrkey1": "arrvalue1",
"arrkey2": "arrvalue2",
"newStuff": [
"o",
"b",
"j",
"k",
"e",
"y",
"1"
]
}
How do I instead get what I want like;
{
"arrkey1": "arrvalue1",
"arrkey2": "arrvalue2",
"newStuff": [
"objValue1",
"objValue2",
"objValue3"
]
}
let arr1 = [
{
'arrkey1': 'arrvalue1',
'arrkey2': 'arrvalue2'
},
{
'arrkey3': 'arrvalue3',
'arrkey4': 'arrvalue4'
},
{
'arrkey5': 'arrvalue5',
'arrkey6': 'arrvalue6'
}
];
const obj1 = {
'objkey1': [
'objValue1',
'objValue2',
'objValue3'
],
'objkey2': [
'objValue4',
'objValue5',
'objValue6'
]
};
for (const item in obj1) {
for (let i = 0, x = arr1.length; i < x; i++) {
arr1[i].newStuff = Object.values(item);
}
}
console.log(arr1);
This example inserts a copy of obj1.objkey1 into each element of arr1:
let arr1 = [
{
'arrkey1': 'arrvalue1',
'arrkey2': 'arrvalue2'
},
{
'arrkey3': 'arrvalue3',
'arrkey4': 'arrvalue4'
},
{
'arrkey5': 'arrvalue5',
'arrkey6': 'arrvalue6'
}
];
const obj1 = {
'objkey1': [
'objValue1',
'objValue2',
'objValue3'
],
'objkey2': [
'objValue4',
'objValue5',
'objValue6'
]
};
const combined = arr1.map(item => ({newStuff: [...obj1.objkey1], ...item}));
console.log(combined);

How to subtract two array values (from different keys) using a ramda pipe?

In a ramda pipe, I want to subtract the values of two array keys, to ultimately end up with an array of those differences.
For example, consider the following mice_weights array. I want to get an array with the differences weight_post minus weight_pre, for the male mice only.
const mice_weights = [
{
"id": "a",
"weight_pre": 20,
"weight_post": 12,
"is_male": true
},
{
"id": "b",
"weight_pre": 25,
"weight_post": 19,
"is_male": false
},
{
"id": "c",
"weight_pre": 15,
"weight_post": 10,
"is_male": true
},
{
"id": "d",
"weight_pre": 30,
"weight_post": 21,
"is_male": false
}
]
So based on this answer, I can construct 2 equivalent pipes, get_pre() and get_post():
const R = require("ramda");
filter_males = R.filter(R.path(["is_male"])) // my filtering function
const get_pre = R.pipe(
filter_males,
R.map(R.prop("weight_pre"))
)
const get_post = R.pipe(
filter_males,
R.map(R.prop("weight_post"))
)
res_pre = get_pre(mice_weights) // [20, 15]
res_post = get_post(mice_weights) // [12, 10]
const res_diff = res_pre.map((item, index) => item - res_post[index]) // taken from: https://stackoverflow.com/a/45342187/6105259
console.log(res_diff); // [8, 5]
Although [8, 5] is the expected output, I wonder if there's a shorter way using ramda's pipe such as:
// pseudo-code
const get_diff = R.pipe(
filter_males,
R.subtract("weight_pre", "weight_post")
)
get_diff(mice_weights) // gives [8, 5]
Is it possible to achieve something similar using ramda? Perhaps there's a built-in functionality for such a task?
To get a the weight diff in a single object, create a function using R.pipe that takes the relevant props values with R.props, and applies them to R.subtract.
Now you can create a functions that filters the items, and maps the objects using weight calculation function:
const { pipe, props, apply, subtract, filter, prop, map, } = R
const calcWeightDiff = pipe(
props(['weight_pre', 'weight_post']),
apply(subtract)
)
const fn = pipe(
filter(prop('is_male')),
map(calcWeightDiff)
)
const mice_weights = [{"id":"a","weight_pre":20,"weight_post":12,"is_male":true},{"id":"b","weight_pre":25,"weight_post":19,"is_male":false},{"id":"c","weight_pre":15,"weight_post":10,"is_male":true},{"id":"d","weight_pre":30,"weight_post":21,"is_male":false}]
const result = fn(mice_weights)
console.log(result) // gives [8, 5]
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Sorry, I don't know about ramda pipes, but this is a trivial matter for array filtering and mapping.
const get_diff = (n, v) => // this takes a field and value to filter
mice_weights
.filter(f => f[n] === v) // this keeps only datasets that have the field/value combo you're seeking
.map(f => f.weight_pre - f.weight_post) // this gets the diff
const mice_weights = [{
"id": "a",
"weight_pre": 20,
"weight_post": 12,
"is_male": true
},
{
"id": "b",
"weight_pre": 25,
"weight_post": 19,
"is_male": false
},
{
"id": "c",
"weight_pre": 15,
"weight_post": 10,
"is_male": true
},
{
"id": "d",
"weight_pre": 30,
"weight_post": 21,
"is_male": false
}
]
const get_diff = (n, v) => mice_weights.filter(f => f[n] === v).map(f => f.weight_pre - f.weight_post)
console.log(get_diff('is_male', true)) // gives [8, 5]
I would propose to use props and reduceRight functions in order to achieve that:
const getProps = R.props(['weight_pre', 'weight_post'])
const subtract = R.reduceRight(R.subtract)(0)
const get_diff = R.pipe(
R.filter(R.path(['is_male'])),
R.map(R.pipe(getProps, subtract))
)
console.log(get_diff(mice_weights));

Merge arrays from different objects with same key

I have the following code:
const blueData = {
"items": [
{
"id": 35,
"revision": 1,
"updatedAt": "2021-09-10T14:29:54.595012Z",
},
]
}
const redData = {}
const greenData = {
"items": [
{
"id": 36,
"revision": 1,
"updatedAt": "2021-09-10T14:31:07.164368Z",
}
]
}
let colorData = []
colorData = blueData.items ? [colorData, ...blueData.items] : colorData
colorData = redData.items ? [colorData, ...redData.items] : colorData
colorData = greenData.items ? [colorData, ...greenData.items] : colorData
I am guessing the spread operator is not the right approache here as I'm getting some extra arrays in my final colorData array. I simply want to build a single array of 'items' that contains all of the 'items' from the 3 objects.
Here's a link to that code in es6 console: https://es6console.com/ktkhc3j2/
Put your data into an array then use flatMap to unwrap each .items:
[greenData, redData, blueData].flatMap(d => d.items ?? [])
//=> [ {id: 36, revision: 1, updatedAt: '2021-09-10T14:31:07.164368Z'}
//=> , {id: 35, revision: 1, updatedAt: '2021-09-10T14:29:54.595012Z'}]
If you fancy you could abstract d => d.items ?? [] with a bit of curry (no pun intended ;)
const take = k => o => o[k] ?? [];
Which gives us:
[greenData, redData, blueData].flatMap(take('items'))
We can even go a step further if you ever need to repeat this process with different keys:
const concatBy = fn => xs => xs.flatMap(x => fn(x));
Now it almost feels like you're expressing your intent with words instead of code:
const takeItems = concatBy(take('items'));
takeItems([greenData, redData, blueData]);
//=> [ {id: 36, revision: 1, updatedAt: '2021-09-10T14:31:07.164368Z'}
//=> , {id: 35, revision: 1, updatedAt: '2021-09-
Let's build another function:
const takeFood = concatBy(take('food'));
takeFood([{food: ['🥑', '🥕']}, {food: ['🌽', '🥦']}]);
//=> ['🥑', '🥕', '🌽', '🥦']
Addendum
This is only meant as a potentially useful learning material. My advice is to use flatMap.
This:
[[1, 2], [3, 4]].flatMap(x => x)
//=> [1, 2, 3, 4]
Can also be expressed with reduce. Slightly more verbose but does what it says on the tin:
[[1, 2], [3, 4]].reduce((xs, x) => xs.concat(x), [])
//=> [1, 2, 3, 4]
So to put it simply you could also do:
[greenData, redData, blueData].reduce((xs, x) => xs.concat(x.items ?? []), [])
You can do this using the Logical OR operator which lets you provide a default value if the items field is missing.
const blueData = { items: [ { id: 35, revision: 1, updatedAt: '2021-09-10T14:29:54.595012Z', }, ], };
const redData = {};
const greenData = { items: [ { id: 36, revision: 1, updatedAt: '2021-09-10T14:31:07.164368Z', }, ], };
const colorData = [
...(blueData.items || []),
...(redData.items || []),
...(greenData.items || []),
];
console.log(colorData);
Maybe I'm a little old-fashioned but I'd use concat for that:
The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
const blueData = {
"items": [
{
"id": 35,
"revision": 1,
"updatedAt": "2021-09-10T14:29:54.595012Z",
},
]
}
const redData = {}
const greenData = {
"items": [
{
"id": 36,
"revision": 1,
"updatedAt": "2021-09-10T14:31:07.164368Z",
}
]
}
const colorData = [].concat(blueData.items,redData.items,greenData.items).filter(x => x)
console.log(colorData)
the last filter is for removing undefined values
Like this?
colorData = blueData.items ? [...colorData, ...blueData.items] : colorData
colorData = redData.items ? [...colorData, ...redData.items] : colorData
colorData = greenData.items ? [...colorData, ...greenData.items] : colorData
Output:
[{"id":35,"revision":1,"updatedAt":"2021-09-10T14:29:54.595012Z"},
{"id":36,"revision":1,"updatedAt":"2021-09-10T14:31:07.164368Z"}]
I think you need to add the spread operator also to the colorData array, because if not you are adding the colorData array itself, not its items.
If you want the simplest solution, you can iterate with a for-loop between all arrays. Create a temporary array that will store data found on each index. This is the fastest and the most flexible solution.
var x1 = {
"items": [
{ "testKey1": "testVal" }
]
};
var x2 = {
"items": [
{ "testKey2.0": "testVal2" },
{ "testKey2.1": "testVal2" },
{ "testKey2.2": "testVal2" },
]
};
var x3 = {
"items": [
{ "testKey3.0": "testVal3" },
{ "testKey3.1": "testVal3" }
]
};
function combineArrays(...arrays) {
var tempArray = [];
for (let index in arrays) {
let currentArray = arrays[index];
for (let innerArrayIndex in currentArray) {
tempArray.push(currentArray[innerArrayIndex]);
}
}
return tempArray;
}
var result = combineArrays(x1.items, x2.items, x3.items);
console.log(result);
The solutions using a spread operator do not take into consideration that all the objects will be cloned using a shallow copy. Have a look.

Elements of my array are duplicated with .map function

I'm triyng to mix two arrays with .map() and .filter(), but my elements are doubled, tripled and more.
My two arrays are,
a = [
[ "id", 1536 ],
[ "origin", "ModèleLogiciel" ],
[ "target", "ModèleDomaine" ],
[ "type", "CONCERNE" ],
[ "EAI", "reel" ]
]
and
b = [ "EAI", "Fréquence" ]
I want to add the values of the second array in the first one, like this :
a = [
[ "id", 1536 ],
[ "origin", "ModèleLogiciel" ],
[ "target", "ModèleDomaine" ],
[ "type", "CONCERNE" ],
[ "EAI", "reel" ],
[ "Fréquence", "" ]
]
I've done :
a.map((col) => {
return (
b.filter((x) => {
if (col[0] !== x) {
a.push([x, ""]);
}
})
);
});
and my result :
a = [
["id", 1536],
["origin", "ModèleLogiciel"],
["target", "ModèleDomaine"],
["type", "CONCERNE"],
["EAI", "reel"],
["Fréquence", ""],
["EAI", ""],
["Fréquence", ""],
["EAI", ""],
["Fréquence", ""],
["EAI", ""],
["Fréquence", ""],
];
I have to misuse the .map function, I presume. And I have trouble removing duplicates.
Someone have a solution for me :) ?
You should use concat
a.concat(b.map(x => [x, '']))
update:
maybe you want to add b value that only not exist in a. that's why you want to use filter.
you can do it like this:
a.concat(b.filter(x => a.find(y => y[0] == x) == null).map(x => [x, '']))
Use array spread to combine between array a, and the result of mapping array b to the required form:
const a = [["id",1536],["origin","ModèleLogiciel"],["target","ModèleDomaine"],["type","CONCERNE"]]
const b = [ "EAI", "Fréquence" ]
const result = [
...a, // add a
...b.map(v => [v, ""]) // transform b and add it
]
console.log(result)
You could map new arrays and push them to the goven array.
const
a = [["id", 1536], ["origin", "ModèleLogiciel"], ["target", "ModèleDomaine"], ["type", "CONCERNE"]],
b = ["EAI", "Fréquence"];
a.push(...b.map(value => [value, '']));
console.log(a);
.as-console-wrapper { max-height: 100% !important; top: 0; }
First merge using spread operators, then using a map and create a new array.
I would also check if the given input is not an array.
var a = [
["id", 1536],
["origin", "ModèleLogiciel"],
["target", "ModèleDomaine"],
["type", "CONCERNE"],
];
var b = ["EAI", "Fréquence"];
var result = [...a, ...b].map((i) => i instanceof Array ? i : [i, ""]);
console.log(result)
You can use b.forEach() to push new array items into a:
const a = [["id", 1536], ["origin", "ModèleLogiciel"], ["target", "ModèleDomaine"], ["type", "CONCERNE"]];
const b = ["EAI", "Fréquence"];
b.forEach(x => a.push([x, ""]));
console.log(a);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to define id of Object generated from 2D Array

I have generated an object from 2D array.
And then, how to define unique id through each object?
"?" in the following code needs to edit.
data = [
["a", "b", "c"],
["a", "b"]
]
for (i in data) {
data[i] = data[i].map(c => ({id: ?, char:c}) )
}
console.log(data)
The following result format is assumed.
[
0: {
0:{
"id": "?",
"char": "a"
},
1:{
"id": "?",
"char": "b"
},
2:{
"id": "?",
"char": "c"
}
},
1: {
0:{
"id": "?",
"char": "a"
},
1:{
"id": "?",
"char": "b"
}
}
]
I thought iterating, but it seems like to needs to flatten the object,
so I could not use it because I did not want to change the format of the original object.
You can create count variable to make the id
data = [
["a", "b", "c"],
["a", "b"]
]
let count = 0;
for (let i in data) {
data[i] = data[i].map(c => {
count ++;
return {id: count, char:c}
} )
}
console.log(data)
You could map single objects inside and assign the arrays to an object. Take the same approach for the outer array.
var data = [["a", "b", "c"], ["a", "b"]],
result = Object.assign(
{},
data.map(a => Object.assign(
{},
a.map(char => ({ id: '?', char }))
))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Because you want objects rather than arrays, you won't be able to use .map. Because you have both parent objects and nested objects, you might use reduce twice:
const data = [
["a", "b", "c"],
["a", "b"]
];
const output = data.reduce((a, arr, i) => {
a[i] = arr.reduce((obj, char, j) => {
obj[j] = { char, id: '?' };
return obj;
}, {});
return a;
}, {});
console.log(output);

Categories

Resources