Beginner programmer here that is trying to build a tool to make my life easier. I am able to pull data from a Google sheet, but it comes out looking like the bellow array with a lot of empty elements both in the elements I want to capture (Person, Person2, etc.) and around them. This is due to how the sheet is formatted and I won't be able to remove the empty cells around it.
var array = [[Person1,, Age1,, Address1], [,,,,], [Person2,, Age2,, Address2], [,,,,] ...]
I assume there is an easy way to filter through the array and remove the empty/null items. But my attempts to use .filter() and nested for loops have not worked. Can anyone help on the best way to get a multidimensional array without the empty items?
you can use reduce function and remove items which are either null or array with zero length
var arr = [["Person1", null, "Age1", null, "Address1"]
, [null, null, null, null, null]
, ["Person2", null, "Age2", null, "Address2"],
[null, null, null, null, ["t"]]]
function reducer(res, item) {
if (!item) return res;
if (Array.isArray(item)) {
var obj = item.reduce(reducer, [])
if (obj.length > 0) {
res.push(obj)
}
return res;
}
res.push(item);
return res;
}
var res = arr.reduce(reducer , [])
console.log(res)
Fortunately you have just a 2D array, which is a list of 1D arrays.
Let's start with an 1D array:
var row = ['a','b',,'c',,];
// via a loop:
var new_row = [];
for (cel in row) if (row[cel]) new_row.push(row[cel]);
console.log(new_row); // ['а', 'b', 'c']
// via a filter() function:
var new_row_f = row.filter((cel) => cel);
console.log(new_row_f); // ['a', 'b', 'c']
Here is a 2D array:
var table = [['a1','b1',,'c1',,],[,,,,,],['a2','b2',,'c2',,]]
// via nested loops:
var new_table = []
for (var row=0; row<table.length; row++) {
var new_row = [];
for (var cel=0; cel<table[row].length; cel++) {
var new_cel = table[row][cel];
if (new_cel) new_row.push(new_cel);
}
if (new_row.join("")!="") new_table.push(new_row);
}
console.log(new_table); // [ [ 'a1', 'b1', 'c1' ], [ 'a2', 'b2', 'c2' ] ]
// via a chain of filter() & map(filter()) functions:
var new_table_f = table.filter(row => row.join("") != "")
.map(row => row.filter((cel) => cel));
console.log(new_table_f); // [ [ 'a1', 'b1', 'c1' ], [ 'a2', 'b2', 'c2' ] ]
Related
I have been looking a simple way to copy/insert/move properties in an object within an array to another object. I came up with a basic logic which does the job perfectly but am not satisfied with this. There has to be a better way, any help here?
var first = [
{
"AGREE_EFF_DATE__0": "02-Aug-2018",
"AGREE_TERM_DATE__0": "30-Apr-2021",
"AGREE_IND__0": "P1",
"P_DBAR_IND__0": "N",
"AGREE_EFF_DATE__1": "01-May-2021",
"AGREE_TERM_DATE__1": null,
"AGREE_IND__1": "NP",
"P_DBAR_IND__1": "N",
"PROVIDER_SPECIALITY__0": "PSYCHOLOGY, CLINICAL",
"PROVIDER_SPECIALITY_CODE__0": "CK"
}
];
var second = [
{
"STATUS": "ACTIVE",
"MEDICARE_NUMBER" : 12345
}
];
for(let i = 0; i < second.length; i++) {
var first_keys = Object.keys(first[i]);
var first_values = Object.values(first[i]);
for(let j = 0; j < first_keys.length; j++) {
second[i][first_keys[j]] = first_values[j];
}
}
console.log(second);
//Output-
[
{
STATUS: 'ACTIVE',
MEDICARE_NUMBER: 12345,
AGREE_EFF_DATE__0: '02-Aug-2018',
AGREE_TERM_DATE__0: '30-Apr-2021',
AGREE_IND__0: 'P1',
P_DBAR_IND__0: 'N',
AGREE_EFF_DATE__1: '01-May-2021',
AGREE_TERM_DATE__1: null,
AGREE_IND__1: 'NP',
P_DBAR_IND__1: 'N',
PROVIDER_SPECIALITY__0: 'PSYCHOLOGY, CLINICAL',
PROVIDER_SPECIALITY_CODE__0: 'CK'
}
]
When possible, you should prefer iteration to manually indexed loops. This means arr.map() or arr.forEach() or arr.reduce(), to name a few.
Also, You can use an object spread to easily merge objects together.
Putting those together, you can reduce this logic to:
const result = first.map((firstObj, i) => ({ ...firstObj, ...second[i] }))
Here we map() over all members of first, which returns a new array where each member is the result of the function. This function takes the array member as the first argument, and the index of that member as the second argument. Then we can use that index to find the corresponding item in the second array.
Then you just spread both objects into a new object to assemble the final result.
var first = [
{ a: 1, b: 2 },
{ a: 4, b: 5 },
];
var second = [
{ c: 3 },
{ c: 6 },
];
const result = first.map((firstObj, i) => ({ ...firstObj, ...second[i] }))
console.log(result)
Which is all perfectly valid typescript as well.
NOTE: there is one difference between my code any yours. Your code modifies the objects in second. My code returns new objects and does not change the contents of second at all.
This is usually the better choice, but it depends on how you use this value and how data is expected to flow around your program.
You need to be careful with iterating, because you can have different count of elements in first and second arrays. So the possible solution will be like this:
const first = [
{
"AGREE_EFF_DATE__0": "02-Aug-2018",
"AGREE_TERM_DATE__0": "30-Apr-2021",
"AGREE_IND__0": "P1",
"P_DBAR_IND__0": "N",
"AGREE_EFF_DATE__1": "01-May-2021",
"AGREE_TERM_DATE__1": null,
"AGREE_IND__1": "NP",
"P_DBAR_IND__1": "N",
"PROVIDER_SPECIALITY__0": "PSYCHOLOGY, CLINICAL",
"PROVIDER_SPECIALITY_CODE__0": "CK"
}
];
const second = [
{
"STATUS": "ACTIVE",
"MEDICARE_NUMBER": 12345
}
];
console.log(mergeAll(first, second));
function mergeAll(firstArray, secondArray) {
const result = [];
const minLength = firstArray.length < secondArray.length ? firstArray.length : secondArray.length;
for (let i = 0; i < minLength; i++) {
result.push({...firstArray[i], ...secondArray[i]});
}
return result;
}
I have an array of objects looking like this
const data = [
{
"Name": "X",
"is_flagged": false
},
{
"Name": "Y",
"is_flagged": true
}
];
and I have a filter array like this
const filters = [true ] // can also be [true,false]
How can I filter the data array based on the filters array values. It should return the records that matches with boolean value/s.
I have tried this way but it doesn't work for boolean values.
let result = data.filter(obj => filters.includes(obj.is_flagged))
var records = filters.map((boolVal) => {
return data.filter((item) => item.is_flagged === boolVal)
})
Not sure what the issue you have been facing, here is the demo :
var data = [ { "Name": "X", "is_flagged": false },{ "Name": 5, "is_flagged": true}];
var filters = [true];
var result = data.filter(val=>filters.includes(val.is_flagged)); // filter out falsy values
console.log(result);
var filters = [true, false];
var result1 = data.filter(val=>filters.includes(val.is_flagged)); // contains both values
console.log(result1);
Let me know if this solves your problem. Thanks!
I have two arrays:
The first contains unique names of fields in a nested array:
[0][0]:Name1
[0][1]:Name2
[0][2]:Name3
etc.
The second contains multiple items with values in a nested array like this:
[0][0] XYZ
[0][1] XYZA
[0][2] XYZ2
[1][0] XYZaa
[1][1] XYZas
[1][2] XYA
etc
What I want to do is to merge it and name it in this way:
[0] Name1: XYZ
[0] Name2: XYZA
[0] Name3: XYZ2
[1] Name1: XYZaa
[1] Name2: XYZas
[1] Name3: XYA
To achieve this I first attempted the following:
var mergedArr = name.concat(data);
That works fine, however I believe I can also use lodash to get closer to what I want:
_.merge(name, data)
and should work fine too.
I was trying to name it by using
_.zipObject
Yet it doesn't work the way I would like
I was trying few options with zip, zipObject, yet non of it gave me expected output.
Edit1:
how I created arrays:
$("#T1020 tr").each(function(x, z){
name[x] = [];
$(this).children('th').each(function(xx, zz){
name[x][xx] = $(this).text();
});
})
$("#T1020 tr").each(function(i, v){
data[i] = [];
$(this).children('td').each(function(ii, vv){
data[i][ii] = $(this).text();
});
})
If I understand your question correctly, you're wanting to zip array1 and array2 into a single array where:
each item of the result array is an object
the keys of each object are values of array1[0], and
the values of each key corresponding nested array of array2
To produce the following:
[
{
"name1": "xyz",
"name2": "xyza",
"name3": "xyz2"
},
{
"name1": "xyzaa",
"name2": "xyzas",
"name3": "xya"
}
]
This can be achieved without lodash; first map each item of array2 by a function where array1[0] is reduced to an object. The reduced object is composed by a key that is the current reduce item, and a value that is taken from the indexed value of the current map item:
const array1 = [
['name1', 'name2', 'name3']
]
const array2 = [
['xyz', 'xyza', 'xyz2'],
['xyzaa', 'xyzas', 'xya']
]
const result = array2.map((item) => {
/* Reduce items of array1[0] to an object
that corresponds to current item of array2 */
return array1[0].reduce((obj, value, index) => {
return { ...obj,
[value]: item[index]
};
}, {});
});
console.log(JSON.stringify(result, null, ' '));
Iterate the values (your array2) and take the sub-array from the keys (array) using the current index and the % operator. This will ensure that if that the keys are taken in a cyclic way (see example with keys2 and values2). Convert to object with _.zipObject:
const fn = (keys, values) => values.map((v, i) => _.zipObject(keys[i % keys.length], v))
const keys1 = [['name1', 'name2', 'name3']]
const values1 = [['xyz', 'xyza', 'xyz2'], ['xyzaa', 'xyzas', 'xya']]
const keys2 = [['name1', 'name2', 'name3'], ['name11', 'name12', 'name13']]
const values2 = [['xyz', 'xyza', 'xyz2'], ['xyzaa', 'xyzas', 'xya'], ['abc', 'def', 'hij']]
console.log(fn(keys1, values1))
console.log(fn(keys2, values2))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
This code removes all null values from array:
var array = [ 0, 1, null, 2, "", 3, undefined, 3,,,,,, 4,, 4,, 5,, 6,,,, ];
var filtered = array.filter(function (el) {
return el != null;
});
console.log(filtered);
But when I try this on an array with nested arrays that have null values, the nulls are not removed:
var array = [ [ 1, null, 2 ], [ 3, null, 4 ], [ 5, null, 6 ] ];
var filtered = array.filter(function (el) {
return el != null;
});
console.log(filtered);
The expected output is:
[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]
Instead of the actual output:
[ [ 1, null, 2 ], [ 3, null, 4 ], [ 5, null, 6 ] ]
How can I change my example to filter null values from the nested arrays?
If your array-of-arrays only has one level, then you can just map it like this:
var filtered = array.map(subarray => subarray.filter(el => el != null));
console.log(filtered);
You need to recursively filter for null, like so:
function removeNull(array) {
return array
.filter(item => item !== null)
.map(item => Array.isArray(item) ? removeNull(item) : item);
}
This function takes an array, and recursively removes all instances of null.
First, I took your solution and wrapped it in a function so that it is able to be called.
Then, after the items are filtered, it's as simple as mapping over the remaining items, checking if each one is an array, and then for each one that is, calling removeNull on it.
EDIT: I had a typo in my code originally, but it should work now.
var arraylist = [0, 1, null, 5];
var i = arraylist.length;
var j =0;
var newlist = [];
while(j < i){
if(arraylist[j] != null){
newlist.push(arraylist[j]);
}
j++;
}
console.log(newlist);
https://jsfiddle.net/L4nmtg75/
var filterFn = function(item) {
if (item instanceof Array) {
// do this if you want to remove empty arrays:
var items = item.splice(0).filter(filterFn);
var length = items.length;
Array.prototype.push.apply(item, items);
return length;
// if you want to keep empty arrays do this:
var items = item.splice(0);
Array.prototype.push.apply(item, items.filter(filterFn))
return true;
}
return item != null;
};
array = array.filter(filterFn);
This will also work on more than 2 level, as it's recursive.
You're examples remove undefined values as well as null values, and your expected output reflects that, so I'm going to assume that you mean you want to recursively remove both undefined and null values. Your example uses a loose equality comparison which means that it will match both null and undefined. While this works, it is much better to be explicit about what you're checking for with strict equality comparison using ===.
You're going to need to use recursion:
Recursion
An act of a function calling itself. Recursion is used to solve problems that contain smaller sub-problems.
- https://developer.mozilla.org/en-US/docs/Glossary/Recursion
This also means that you're going to want to use Array#reduce instead of Array#filter. Use a new array as the accumulator.
Then for each element in the input array where the element is not null or undefined:
if the element is an instance of Array, push the result of calling this function on the element onto the accumulator array,
otherwise push the element onto the accumulator array
Return the accumulator array at the end of the reduce callback as the accumulator
const input = [ [ 1, null, 2 ], null,,,,, [ 3, null, 4 ],,,,, [ 5, null, 6 ],,,,, [ 7, [ 8, undefined, 9 ], 10 ] ]
function recursiveValues(input) {
if(!(input instanceof Array)) return null
return input.reduce((output, element) => {
if(element !== null && element !== undefined) {
if(element instanceof Array) {
output.push(recursiveValues(element))
} else {
output.push(element)
}
}
return output
}, [])
}
const output = recursiveValues(input)
console.log(JSON.stringify(output))
I'm trying to get my head around map functions.
Here is my working code and output using a nested for loop:
var jsonsToAddTo = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var additionalData = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
// Adds a key value pair from sourceJson to targetJson based on a matching value
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
for(thisJson in targetJsonArray){
for(thatJson in sourceJsonArray){
if(targetJsonArray[thisJson][targetKeyToMatch]==sourceJsonArray[thatJson][sourceKeyToMatch]){
console.log('match');
targetJsonArray[thisJson][keyToAdd]=sourceJsonArray[thatJson][keyToAdd];
}
}
}
return targetJsonArray
}
console.log(denormalizeJsonOnKey(jsonsToAddTo,'cat',additionalData,'pk','key3'))
OUTPUT:
[
{ cat: 'k1', key2: 'a', key3: 'data1' },
{ cat: 'k1', key2: 'b', key3: 'data1' },
{ cat: 'k2', key2: 'a', key3: 'data2' },
{ cat: 'k2', key2: 'b', key3: 'data2' },
{ cat: 'k3', key2: 'a', key3: 'data3' }
]
I can't figure out how to handle the nesting using a map function on an array.
Using ES6 can simplify using Array#find() and Object#assign()
var data = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var data2 = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
const mergeData= (arr1, arr2, matchKey, filterKey, includeKey)=>{
arr1.forEach(o => {
const newObj ={};
const match = arr2.find(e => e[filterKey] === o[matchKey])
newObj[includeKey] = match ? match[includeKey] : null;
Object.assign(o, newObj);
});
}
mergeData(data, data2,'cat', 'pk', 'key3')
console.log(data)
Here is a solution that takes advantage of map and object spread to produce a new array with the desired key added into the target array's elements:
var jsonsToAddTo = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var additionalData = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
return targetJsonArray.map(thisJson => {
const addObj = sourceJsonArray.find(thatJson => thatJson[sourceKeyToMatch] === thisJson[targetKeyToMatch]);
return {
...thisJson,
...addObj ? {[keyToAdd]: addObj[keyToAdd]} : {},
}
});
}
console.log(denormalizeJsonOnKey(jsonsToAddTo, 'cat', additionalData, 'pk', 'key3'))
Note that this solution won't mutate the original array, so the jsonsToAddTo variable will be the same after you invoke the function. If you want to replace the original, you can always just re-assign it:
jsonsToAddTo = denormalizeJsonOnKey(jsonsToAddTo, 'cat', additionalData, 'pk', 'key3')
Try this,
using maps for both iteration,
var jsonsToAddTo = [{'cat':'k1','key2':'a'},{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}]
var additionalData = [{'pk':'k1','key3':'data1'},{'pk':'k2','key3':'data2'},{'pk':'k3','key3':'data3'},
]
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
jsonsToAddTo.map((obj,index)=> {
additionalData.map((o,idx)=> {
if(obj[targetKeyToMatch]==o[sourceKeyToMatch]){
obj[keyToAdd]=o[keyToAdd];
}
})
})
return jsonsToAddTo
}
console.log(denormalizeJsonOnKey(jsonsToAddTo,'cat',additionalData,'pk','key3'))
var targetJsonArray = jsonsToAddTo.map(function(json, index) {
additionalData.forEach(function(data) {
if (data.pk === json.cat) {
json.key3 = data.key3;
}
})
return json;
})
Rather than nesting loops here, which will iterate the entire additionalData array for every entry in jsonsToAddTo, I suggest building an object map of the additionalData dataset once at the beginning, and then reference this within a .map on the target dataset:
var jsonsToAddTo = [
{'cat':'k1','key2':'a'},
{'cat':'k1','key2':'b'},
{'cat':'k2','key2':'a'},
{'cat':'k2','key2':'b'},
{'cat':'k3','key2':'a'}
]
var additionalData = [
{'pk':'k1','key3':'data1'},
{'pk':'k2','key3':'data2'},
{'pk':'k3','key3':'data3'},
]
// Adds a key value pair from sourceJson to targetJson based on a matching value
function denormalizeJsonOnKey(targetJsonArray,targetKeyToMatch, sourceJsonArray, sourceKeyToMatch, keyToAdd){
// Build an object of items keyed on sourceKeyToMatch
const sourceJsonMap = sourceJsonArray.reduce((obj, item) => (obj[item[sourceKeyToMatch]]=item, obj), {});
return targetJsonArray.map(item => {
const targetValue = item[targetKeyToMatch];
if (sourceJsonMap.hasOwnProperty(targetValue)) {
item[keyToAdd] = sourceJsonMap[targetValue][keyToAdd];
}
return item;
});
}
console.log(denormalizeJsonOnKey(jsonsToAddTo,'cat',additionalData,'pk','key3'))
Doing it this way should be far more efficient, especially if the dataset you are working on is fairly large.