Array 'map' vs 'forEach' - functional programming - javascript

I have an array of objects:
let reports = [{ inbound_calls: [...], outbound_calls: [...], outbound_national_calls: [...] },...];
What is the best way to create a new array and assign into a variable:
1st approach - one loop:
let inbound_calls = []; outbound_national_calls = [], outbound_calls = [];
reports.forEach((e) => {
inbound_calls.push(e.inbound_calls);
outbound_national_calls.push(e.outbound_national_calls);
outbound_calls.push(e.outbound_calls);
})
2nd approach:
let inbound_calls = this.reports.map((report) => report.inbound_calls)
let outbound_national_calls = this.reports.map((report) => report.outbound_national_calls)
let outbound_calls = this.reports.map((report) => report.outbound_calls)
I'm starting to learn functional programming, and want to apply it to my code, I would go with first approach (one loop), but as I did research about functional programming I think the second one is the right way (much cleaner) but, I'm not sure, what is less expensive operation?

If your ultimate goal is to create three variables out of the object, you may use object destructuring as follows. No loops required.
let reports = {
inbound_calls: [1, 2, 3],
outbound_calls: [4, 5, 6],
outbound_national_calls: [7, 8, 9]
};
let {inbound_calls, outbound_calls, outbound_national_calls} = reports;
console.log(inbound_calls);
console.log(outbound_calls);
console.log(outbound_national_calls);

If you want to copy the arrays, just use Array#slice (the 0 passed is optional as it is the default start index so you can omit it if you want) like:
let inbound_calls = reports.inbound_calls.slice(0),
outbound_national_calls = reports.outbound_national_calls.slice(0),
outbound_calls = reports.outbound_calls.slice(0);
or Array.from like:
let inbound_calls = Array.from(reports.inbound_calls),
outbound_national_calls = Array.from(reports.outbound_national_calls),
outbound_calls = Array.from(reports.outbound_calls);

What you're essentially doing is a matrix transposition:
const report = (inbound_calls, outbound_calls, outbound_national_calls) =>
({ inbound_calls, outbound_calls, outbound_national_calls });
const reports = [report(1,2,3), report(4,5,6), report(7,8,9)];
const transpose = reports =>
report( reports.map(report => report.inbound_calls)
, reports.map(report => report.outbound_calls)
, reports.map(report => report.outbound_national_calls) );
console.log(transpose(reports));
Now, depending upon your application the fastest way to transpose a matrix might be not to transpose it at all. For example, suppose you have a matrix A and its transpose B. Then, it holds that for all indices i and j, A[i][j] = B[j][i]. Consider:
const report = (inbound_calls, outbound_calls, outbound_national_calls) =>
({ inbound_calls, outbound_calls, outbound_national_calls });
const reports = [report(1,2,3), report(4,5,6), report(7,8,9)];
// This is equivalent to transpose(reports).outbound_calls[1]
const result = reports[1].outbound_calls;
console.log(result);
That being said, your second approach is IMHO the most readable.

Related

Comparing elements one array with another of different lengths

Consider an array1 of n no. of features
array1=[feat1,feat2,...,featn]
and array2 of m no. of linearRings
array2=[ring1,ring2,...,ringm].
Now write a program in javascript such that every element of array2 is compared with every element of array1.
PS: please suggest an approach apart from nested for loop.
The approach I tried:
features.map(feature => {
linearRings.map(linearRing => {
const singleFeature = getTurfFeature(feature);
const pointOfLinearRing = point(
transform(linearRing.getFirstCoordinate(), 'EPSG:3857', 'EPSG:4326')
);
const checkForOverlap = booleanIntersects(pointOfLinearRing, singleFeature);
checkForOverlap && feature.getGeometry().appendLinearRing(linearRing);
});
});
You can use Set & store the value of the first array here. Then you can check the result of point function and verify if it is present in set
const array1 = ['feat1', 'feat2', 'featn']
const set = new Set();
const array2 = ['ring1', 'ring2', 'ringm'];
array1.forEach(feature => {
const singleFeature = getTurfFeature(feature);
if (!set.has(singleFeature)) {
set.add(singleFeature)
}
})
array2.map(linearRing => {
const pointOfLinearRing = point(
transform(linearRing.getFirstCoordinate(), 'EPSG:3857', 'EPSG:4326')
);
// do whatever you want to do here
return set.has(pointOfLinearRing);
});

How to implement a for nested in a for in a more modern ES6 way?

Someone asked me to solve this problem: Return only those objects whose property "enrollmentId" is in another number array: So I came up with this, and it works, however, I'm using a for inside a for, and I was wondering what's the best approach to this kind of problem. Please, check it out
const companions = [
{name:"camila", enrollmentId:1},
{name:"oscar", enrollmentId:2},
{name:"rupertina", enrollmentId:3}
];
const participants = [7,2,4]
const finalResult = [];
for(i=0; i< companions.length; i++){
let alumno = companions[i];
for(j=0; j< participants.length; j++){
let participante = participants[j];
if(alumno.enrollmentId == participante){
finalResult.push(alumno);
}
}
}
console.log(finalResult)
Filter the original array by whether the enrollmentId of the object being iterated over is included in the participants.
const companions = [
{name:"camila", enrollmentId:1},
{name:"oscar", enrollmentId:2},
{name:"rupertina", enrollmentId:3}
];
const participants = [7, 2, 4]
const finalResult = companions.filter(obj => participants.includes(obj.enrollmentId));
console.log(finalResult)
I would use Array.filter to omit invalid rows, and Array.includes for the validity test
const companions = [
{name:"camila", enrollmentId:1},
{name:"oscar", enrollmentId:2},
{name:"rupertina", enrollmentId:3}
];
const participants = [7,2,4];
const finalResult = companions.filter(companion => {
return participants.includes(companion.enrollmentId);
});
console.log(finalResult);
If you want the ES6 way, go with code_monk code BUT imma up code_monk code, will all do respect (I cannot add a comment to his code as I am new to stack overflow and do not have enough reputation.) BUT an arrow function ( => ) does not need the keyword return nor the function budy {}, because it is implicit that a value will be returned after the =>
const companions = [
{name:"camila", enrollmentId:1},
{name:"oscar", enrollmentId:2},
{name:"rupertina", enrollmentId:3}
];
const participants = [7,2,4];
const finalResult = companions.filter(companion => participants.includes(companion.enrollmentId));
console.log(finalResult);

Given an array of objects, count how many (possibly different) properties are defined

In a GeoJSON file, some properties are shared by all "features" (element) of the entire collection (array). But some properties are defined only for a subset of the collection.
I've found this question: [javascript] counting properties of the objects in an array of objects, but it doesn't answer my problem.
Example:
const features =
[ {"properties":{"name":"city1","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4] ...]}},
{"properties":{"name":"city2","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4] ...]}},
{"properties":{"name":"city3"},"geometry":{"type":"multiPolygon","coordinates":[[[1,2],[3,4] ...]]}},
// ... for instance 1000 different cities
{"properties":{"name":"city1000","zip":1234,"updated":"May-2018"}, "geometry":{"type":"polygon","coordinates":[...]}}
];
expected result: a list a all existing properties and their cardinality, letting us know how (in)complete is the data-set. For instance:
properties: 1000, properties.name: 1000, properties.zip: 890, properties.updated: 412,
geometry: 1000, geometry.type: 1000, geometry.coordinates: 1000
I have a (rather complicated) solution, but I do suspect that some people have already faced the same issue (seems a data science classic), with a better one (performance matters).
Here is my clumsy solution:
// 1: list all properties encountered in the features array, at least two levels deep
const countProps = af => af.reduce((pf,f) =>
Array.from(new Set(pf.concat(Object.keys(f)))), []);
// adding all the properties of each individual feature, then removing duplicates using the array-set-array trick
const countProp2s = af => af.reduce((pf,f) =>
Array.from(new Set(pf.concat(Object.keys(f.properties)))), []);
const countProp2g = af => af.reduce((pf,f) =>
Array.from(new Set(pf.concat(Object.keys(f.geometry)))), []);
// 2: counting the number of defined occurrences of each property of the list 1
const countPerProp = (ff) => pf => ` ${pf}:${ff.reduce((p,f)=> p+(!!f[pf]), 0)}`;
const countPerProp2s = (ff) => pf => ` ${pf}:${ff.reduce((p,f)=> p+(!!f.properties[pf]), 0)}`;
const countPerProp2g = (ff) => pf => ` ${pf}:${ff.reduce((p,f)=> p+(!!f.geometry[pf]), 0)}`;
const cardinalities = countProps(features).map((kk,i) => countPerProp(ff)(kk)) +
countProp2s(features).map(kk => countPerProp2s(ff)(kk)) +
countProp2g(features).map(kk => countPerProp2g(ff)(kk));
Therefore, there are three issues:
-step 1: this is much work (adding everything before removing most of it) for a rather simple operation. Moreover, this isn't recursive and second level is "manually forced".
-step 2, a recursive solution is probably a better one.
-May step 1 and 2 be performed in a single step (starting to count when a new property is added)?
I would welcome any idea.
The JSON.parse reviver and JSON.stringify replacer can be used to check all key value pairs :
var counts = {}, json = `[{"properties":{"name":"city1","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},{"properties":{"name":"city2","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},{"properties":{"name":"city3"},"geometry":{"type":"multiPolygon","coordinates":[[[1,2],[3,4]]]}},{"properties":{"name":"city1000","zip":1234,"updated":"May-2018"}, "geometry":{"type":"polygon","coordinates":[]}} ]`
var features = JSON.parse(json, (k, v) => (isNaN(k) && (counts[k] = counts[k] + 1 || 1), v))
console.log( counts, features )
Consider trying the following. It is just one reduce, with a couple of nested forEach's inside. It checks whether the keys for indicating the count exist in the object to be returned, and if not creates them initialized to 0. Then whether those keys existed or not to begin with, their corresponding values get incremented by 1.
Repl is here: https://repl.it/#dexygen/countobjpropoccur2levels , code below:
const features =
[ {"properties":{"name":"city1","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},
{"properties":{"name":"city2","zip":1234}, "geometry":{"type":"polygon","coordinates":[[1,2],[3,4]]}},
{"properties":{"name":"city3"},"geometry":{"type":"multiPolygon","coordinates":[[[1,2],[3,4]]]}},
{"properties":{"name":"city1000","zip":1234,"updated":"May-2018"}, "geometry":{"type":"polygon","coordinates":[]}}
];
const featuresCount = features.reduce((count, feature) => {
Object.keys(feature).forEach(key => {
count[key] = count[key] || 0;
count[key] += 1;
Object.keys(feature[key]).forEach(key2 => {
let count2key = `${key}.${key2}`;
count[count2key] = count[count2key] || 0;
count[count2key] += 1;
});
});
return count;
}, {});
console.log(featuresCount);
/*
{ properties: 4,
'properties.name': 4,
'properties.zip': 3,
geometry: 4,
'geometry.type': 4,
'geometry.coordinates': 4,
'properties.updated': 1 }
*/
Use polymorphic serialization of json using jackson. It will look something like below. Your base interface will have all common properties and for each variation create sub types. Count on each type will give what you need
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="name") #JsonSubTypes({ #JsonSubTypes.Type(value=Lion.class, name="lion"), #JsonSubTypes.Type(value=Tiger.class, name="tiger"), }) public interface Animal { }

2-dimensional arrays in ES6

Long story short, i'm looking for a way to create and fill 2D arrays using ES6, in an effort to avoid for loops. The created array should contain all 0s. I've tried many different approaches so i cant post all of them.
var [r, c] = [5, 5];
var m = Array(r).fill(Array(c).fill(0));
This works but it creates a bunch of instances of the same array, and adding slice Array(r).fill(Array(c).fill(0).slice()); doesn't help either.
I also tried creating the empty arrays and then looping trough them but that's a whole different problem, you apparently can't forEach() or map() an empty array, and i couldn't even loop through a filled one efficiently.
Am i missing something here? Are a whole lot of for loops the best way to approach this? It looks really messy and overly long. Any help appreciated.
Doing this worked for me:
var [r, c] = [5, 5];
var m = Array(r).fill().map(()=>Array(c).fill(0));
Basically just filling it with a dummy value so you can map over it
You could use Array.from that takes a callback function and inside return arrays with 0's using fill method.
const arr = Array.from(Array(2), () => Array(5).fill(0))
console.log(arr)
Or you could just create array where each element is number of elements in sub-array and then use map and fill methods.
const arr = [5, 5].map(e => Array(e).fill(0))
console.log(arr)
For those needing the same thing but with undefined as each value this also works.
const x = 100;
const y = 100;
const grid = [...new Array(x)].map(() => [...new Array(y)]);
To fill the array simply map the inner value.
This will make an array filled with 0 for each value.
const x = 100;
const y = 100;
const grid = [...new Array(10)].map(() => [...new Array(10)].map(() => 0));
const nthArray = (n) => Array.from(Array(n), () => Array(5).fill(0))
const arr = nthArray(3);
console.log(arr);

What is the javascript equivalent of numpy argsort?

I want to sort the imgUrl array by click count. I have two arrays.
clickCount = [5,2,4,3,1]
imgUrl = ['1.jpg','2.jpg','3.jpg','4.jpg','5.jpg']
In numpy it is easy. I use order = np.argsort(clickCount) then I create another array newArray = [imgUrl[i] for i in order].
How do I achieve the same effect in javascript (preferably vanilla)?
You can use a Schwartzian transform also known as Decorate-Sort-Undecorate (DSU) in python.
DSU:
Decorate - Use Array#Map to enrich each item in the array with the needed sort data
Sort - sort using the added data
Undecorate - extract the sorted data using Array#map again
Demo:
const dsu = (arr1, arr2) => arr1
.map((item, index) => [arr2[index], item]) // add the args to sort by
.sort(([arg1], [arg2]) => arg2 - arg1) // sort by the args
.map(([, item]) => item); // extract the sorted items
const clickCount = [5,2,4,3,1];
const imgUrl = ['1.jpg','2.jpg','3.jpg','4.jpg','5.jpg'];
const result = dsu(imgUrl, clickCount);
console.log(result);
thanks to dankal444 for the refactor to the function
For completeness, here's my solution to the actual answer (providing argsort function), by expanding on Ori's answer with DSU.
Since sort is by default taking the first element, so implementing it as DSU is merely adding an index, sorting it, then taking the indices.
let decor = (v, i) => [v, i]; // set index to value
let undecor = a => a[1]; // leave only index
let argsort = arr => arr.map(decor).sort().map(undecor);
clickCount = [5, 2, 4, 3, 1]
imgUrl = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg']
order = argsort(clickCount);
newArray = order.map(i => imgUrl[i])
console.log(newArray);
Functional approach (like #Ori Drori's code) is always a charm to watch, but in this case, you only need to re-arrange an array's items. I believe that there is a simpler way to go and is a much easier code to read.
const clickCount = [5,2,4,3,1];
const imgUrl = ['1.jpg','2.jpg','3.jpg','4.jpg','5.jpg'];
sortByArrayRefOrder = (data, orderRefArr) => {
let orderedArr = [], i=0;
orderRefArr.map( o => { orderedArr[o-1] = data[i++]});
return orderedArr.reverse();
}
console.log ( sortByArrayRefOrder(imgUrl, clickCount) );

Categories

Resources