Ramda.js transducers: average the resulting array of numbers - javascript
I'm currently learning about transducers with Ramda.js. (So fun, yay! 🎉)
I found this question that describes how to first filter an array and then sum up the values in it using a transducer.
I want to do something similar, but different. I have an array of objects that have a timestamp and I want to average out the timestamps. Something like this:
const createCheckin = ({
timestamp = Date.now(), // default is now
startStation = 'foo',
endStation = 'bar'
} = {}) => ({timestamp, startStation, endStation});
const checkins = [
createCheckin(),
createCheckin({ startStation: 'baz' }),
createCheckin({ timestamp: Date.now() + 100 }), // offset of 100
];
const filterCheckins = R.filter(({ startStation }) => startStation === 'foo');
const mapTimestamps = R.map(({ timestamp }) => timestamp);
const transducer = R.compose(
filterCheckins,
mapTimestamps,
);
const average = R.converge(R.divide, [R.sum, R.length]);
R.transduce(transducer, average, 0, checkins);
// Should return something like Date.now() + 50, giving the 100 offset at the top.
Of course average as it stands above can't work because the transform function works like a reduce.
I found out that I can do it in a step after the transducer.
const timestamps = R.transduce(transducer, R.flip(R.append), [], checkins);
average(timestamps);
However, I think there must be a way to do this with the iterator function (second argument of the transducer). How could you achieve this? Or maybe average has to be part of the transducer (using compose)?
As a first step, you can create a simple type to allow averages to be combined. This requires keeping a running tally of the sum and number of items being averaged.
const Avg = (sum, count) => ({ sum, count })
// creates a new `Avg` from a given value, initilised with a count of 1
Avg.of = n => Avg(n, 1)
// takes two `Avg` types and combines them together
Avg.append = (avg1, avg2) =>
Avg(avg1.sum + avg2.sum, avg1.count + avg2.count)
With this, we can turn our attention to creating the transformer that will combine the average values.
First, a simple helper function that allow values to be converted to our Avg type and also wraps a reduce function to default to the first value it receives rather than requiring an initial value to be provided (a nice initial value doesn't exist for averages, so we'll just use the first of the values instead)
const mapReduce1 = (map, reduce) =>
(acc, n) => acc == null ? map(n) : reduce(acc, map(n))
The transformer then just needs to combine the Avg values and then pull resulting average out of the result. n.b. The result needs to guard for null values in the case where the transformer is run over an empty list.
const avgXf = {
'##transducer/step': mapReduce1(Avg.of, Avg.append),
'##transducer/result': result =>
result == null ? null : result.sum / result.count
}
You can then pass this as the accumulator function to transduce, which should produce the resulting average value.
transduce(transducer, avgXf, null, checkins)
I'm afraid this strikes me as quite confused.
I think of transducers as a way of combining the steps of a composed function on sequences of values so that you can iterate the sequence only once.
average makes no sense here. To take an average you need the whole collection.
So you can transduce the filtering and mapping of the values. But you will absolutely need to then do the averaging separately. Note that filter then map is a common enough pattern that there are plenty of filterMap functions around. Ramda doesn't have one, but this would do fine:
const filterMap = (f, m) => (xs) =>
xs .flatMap (x => f (x) ? [m (x)] : [])
which would then be used like this:
filterMap (
propEq ('startStation', 'foo'),
prop ('timestamp')
) (checkins)
But for more complex sequences of transformations, transducers can certainly fit the bill.
I would also suggest that when you can, you should use lift instead of converge. It's a more standard FP function, and works on a more abstract data type. Here const average = lift (divide) (sum, length) would work fine.
Related
Add sum of all numbers in an array and print the final result
please don't kill me right away, because some topics are already here in the forum. I've read and tried a lot. I just don't get any further with my beginner knowledge. My problem is the following: I have 4 arrays, the length of the arrays can have a different length each time. Now I would like to add the sum of all 4 arrays and get a final result. I just can't really get any further than I want. Please bear with me and help me. Here my example : var MyData = '{"M":[{"M":{"MD":995,"PT":1,"TT":17,"D":0,"TID":44,"T":0,"HW":109,"KD":0,"TA":[4,17,77,17010,84,5,6,6,6,5,"xxxx",0,0,128,6,-1,0,34,[],0],"SD":1187,"OD":117,"SA":[1,65,67,194,187,7,8,7,6,6,"yyyy",0,0,-1,-1,-1,0,95,[],0]},"UM":{"PD":0,"TD":0,"L":{"ID":23,"ID":2,"VIS":14,"N":"bbbb","GD":102,"L":17,"W":1429,"D":113,"SPR":9,"Q":[[1647,1,2,5,-1,[[4,40,[114.0]],[5,46,[74.3]],[18,89,[43.6]],[116,34,[42.2]]],-1,-1,0,-1,-1,3,[1,5,3090,[7222,32,5,310,[[312,46,[11.4]],[311,55,[12.8]],[34,72,[19.7]],[30,28,[8.4]]],0]]],[187,2,2,5,-1,[[1,9,[82.4]],[2,86,[86.3]],[112,67,[28.1]],[19,35,[22.0]]],-1,-1,0,-1,-1,3,[2,6,2970,[7127,32,6,3240,[[311,28,[8.5]],[314,67,[18.7]],[32,83,[17.3]],[315,46,[8.0]]],0]]],[14650745,3,2,5,-1,[[3,97,[116.8]],[6,55,[74.8]],[109,42,[24.8]],[116,65,[67.0]]],-1,-1,0,-1,-1,3,[3,6,3590,[77381,32,6,3130,[[313,33,[6.3]],[310,83,[21.1]],[312,61,[13.8]],[311,36,[9.8]]],0]]],[14673,4,2,5,-1,[[7,82,[16.1]],[115,40,[21.0]],[121,87,[215,871.0]],[112,48,[22.4]]],-1,-1,0,-1,-1,3,[4,6,3570,[72077,32,2,2710,[[312,44,[11.0]],[311,52,[12.3]],[313,37,[6.8]],[38,38,[8.8]]],0]]],[14681483230,5,2,0,0,[[21,[5.0]]],1070,-1,0,630006,-1,2],[14649672824,6,2,15,-1,[[808,82,[47.1]],[811,77,[28.1]],[812,50,[20.0]],[815,35,[18.4]],[20018,100,[1.0]],[20016,7,[2060,12.0,2061,12.0,2062,12.0,2063,12.0,2064,12.0,2065,12.0,2066,12.0,2067,12.0,2068,12.0,148,12.0,2069,12.0,149,12.0,2070,12.0,150,12.0,151,12.0,2071,12.0,2072,12.0,2073,12.0,2074,12.0,2075,12.0,2076,12.0,2077,12.0,2078,12.0,2079,12.0,2020,12.0,2021,12.0,2022,12.0,2023,12.0,2024,12.0,2025,12.0,2026,12.0,2027,12.0,2028,12.0,2029,12.0,2030,12.0,2031,12.0,2032,12.0,2033,12.0,2034,12.0,2035,12.0,2036,12.0,2037,12.0,2038,12.0,2039,12.0]]],-1,-1,0,-1,-1,3,[65,6,3440,[]]]],"IDS":[[10021,1023],[10022,1033]],"SIS":[1033,1009,1040,100253],"AE":[[64,[8.0]],[503,[20.0]],[504,[48.0]],[504,[48.0]],[339,[50.0]],[504,[48.0]],[613,[50.0]],[66,[65.0]],[503,[65.0]],[503,[45.0]],[504,[30.0]],[504,[45.0]],[614,[8.0]],[66,[48.0]],[504,[60.0]],[66,[45.0]],[504,[48.0]],[614,[30.0]],[66,[24.0]]]}},"GA":{"L":[[287,30],[216,2224],[215,744],[298,60],[651,76],[650,90],[649,132],[240,50]],"M":[[287,30],[216,2720],[215,3808],[298,60],[651,48],[650,102],[649,125],[648,179],[240,50]],"R":[[287,30],[216,2968],[298,60],[651,76],[650,90],[649,132],[240,50]],"RW":[[215,2358],[216,433]]},"AST":[390,400],"ATT":0,"SM":0}],"O":[{"OID":8344,"DUM":false,"N":"xrr","E":{"BGT":0,"BGC1":14408394,"BGC2":1644825,"SPT":2,"S1":82,"SC1":144394,"S2":82,"SC2":163,"IS":1},"L":7,"LL":50,"H":3195,"AVZ":180,"CF":3911,"HF":16245,"PRE":13,"SUF":23,"TPX":-1,"MP":459,"R":0,"AID":48,"AR":0,"AN":"drum vv","aee":{"CF":237604,"BGT":7,"BGC1":16825,"BGC2":3012,"BGC3":864,"SPT":23,"S1":2,"SC1":30512,"S2":41,"SC2":164825,"S3":0,"SC3":164825},"RPT":0,"AP":[[0,1007,1062,76,1],[0,1170610,1067,757,4],[0,1211752,1056,755,4],[0,100,1059,72,4],[1,163,710,79,12],[2,16771,809,68,12],[3,2052697,772,504,12]],"VP":[],"SA":0,"VF":0,"PF":1,"RRD":0,"TI":-1},{"OD":1187,"DM":false,"N":"xyz","E":{"BGT":1,"BG1":81264,"BG2":5126,"SPT":1,"S1":82,"S1":16425,"S2":82,"S2":164825,"IS":1},"L":7,"LL":90,"H":6738,"AVP":87345,"CF":4591,"HF":5623,"PRE":13,"SUF":27,"TX":1,"P":8828,"R":0,"AD":88,"AR":7,"AN":"DDR ","aee":{"CF":1878,"BT":2,"BC1":14494,"BG2":1894,"BC3":-1,"SPT":0,"S1":-1,"S1":-1,"S2":-1,"SC2":-1,"S3":-1,"SC3":-1},"RPT":0,"AP":[[0,1417194,675,607,1],[0,2086,795,1180,4],[0,3363161,663,603,4],[0,7164676,769,1179,4],[1,489,490,318,12],[2,208,33,71,12],[3,4347,42,108,12],[4,112,818,61,12]],"VP":[],"A":0,"VF":0,"PF":1,"RD":0,"TI":-1,"RP":-1}]}' var MyArray = JSON.parse(MyData) MyArray.M[0].GA.L.forEach(element1 => console.log(element1[1])); MyArray.M[0].GA.M.forEach(element2 => console.log(element2[1])); MyArray.M[0].GA.R.forEach(element3 => console.log(element3[1])); MyArray.M[0].GA.RW.forEach(element4 => console.log(element4[1])); //So I give myself all the values ​​first. //If I then use the for loop on one of the arrays, I get the output: let sum = 0; MyArray.M[0].GA.L.forEach(element1 => { const MyArray = [element1[1]]; for (let i = 0; i < MyArray.length; i++) { sum += MyArray[i]; } console.log(sum); }) //output: 30,2254,2998,3058,3134,3224,3356,3406 The number 3406 is the End Result, how can I print only the End result. If there is another method to add all 4 arrays together at once, it would help me even more. If possible an example for an array and an example for all 4 together please
you have to do three things: consolidate all the arrays, get the sum of all values, then print it first, consolidate const allArrs = [ ...MyArray.M[0].GA.L, ...MyArray.M[0].GA.M, ...MyArray.M[0].GA.R, ...MyArray.M[0].GA.RW ] then, the array method that would help the most here is Array.Reduce const result = allArrs.reduce((accumulator, currentArray) => { accumulator += currentArray[1] return accumulator }, 0) then log the result!
As others have pointed out, this type of problem is very well suited for using a functional programming approach, where you simplify the problem by morphing it into a series of atomic operations that have no side-effects. JavaScript does a great job of implementing the core functional programming methods of map(), filter(), and reduce(). For this problem, the only thing you need is reduce(). The code below could be made smaller and more efficient, but it was written to show each individual step. const data = JSON.parse(rawData); // subData is now an object whose members are the arrays you care about const subData = data.M[0].GA; // turn that into an array of arrays const myArrays = Object.values(subData); // now use reduce() to flatten the array of arrays into a single array with just // the numbers you want to sum const myNums = myArrays.reduce((acc, val) => acc.concat(val[1]), []); // finally, reduce() again to sum the values const sum = myNums.reduce((acc, val) => acc + val, 0); console.log(sum);
You're on the right track. Simply move console.log(sum) outside of the final }) so it's not inside any loops.
I hope I understand what you are trying to do. You can simplify your code in this way. function sum(arr) { return arr.reduce((acc, item) => acc += item[1], 0); } const result = sum(MyArray.M[0].GA.L) + sum(MyArray.M[0].GA.M) + sum(MyArray.M[0].GA.R) + sum(MyArray.M[0].GA.RW);
This is a good example of a use for Array.prototype.reduce. The reduce method loops through all the elements of an array, and uses an accumulator to carry a result through the operation. const someArray = [ /* any number of values */ ] const sum = someArray.reduce( ( acc, val ) => ( acc + val ) You can take this a step further and write the callback as a function expression, and dump all of your arrays into a BIGGER array, and use reduce on THAT ONE too! const summation = ( acc, val ) => ( acc + val ) const sumOfSums = ( acc, val ) => ( acc + val.reduce( summation, 0 ) ) const arrayOfArrays= [ MyArray.M[0].GA.L, MyArray.M[0].GA.M, MyArray.M[0].GA.R MyArray.M[0].GA.RW ] const total = arrayOfArrays.reduce( sumOfSums, 0 ) console.log( `output: ${total}` )
How do I apply a composed function to each object in a list using Ramda?
I'm building a simple app using RamdaJS that aims to take a list of objects that represent U.S. states, and for each state, it should calculate the number of electoral votes and add that value as a new property to each object, called electoralVotes. The basic gist of the calculation itself (as inaccurate as it may be) is to divide the population by 600000, round that number down, and if the rounded-down number is 0, round it up to 1. For simplicity, the array of states only includes a state name and population for each state: const states = [ { state: 'Alabama', population: 4833722 }, { state: 'Alaska', population: 735132 }, { state: 'Arizona', population: 6626624 }, // ... etc. ]; I created a function called getElectoralVotesForState that is created with nested levels of composition (A composed function that is built using another composed function). This function takes a state object, examines its population property, then calculates and returns the corresponding number of electoral votes. const R = require('ramda'); // This might not be factually accurate, but it's a ballpark anyway const POP_PER_ELECTORAL_VOTE = 600000; const populationLens = R.lensProp("population"); // Take a number (population) and calculate the number of electoral votes // If the rounded-down calculation is 0, round it up to 1 const getElectoralVotes = R.pipe( R.divide(R.__, POP_PER_ELECTORAL_VOTE), Math.floor, R.when(R.equals(0), R.always(1)) ); // Take a state object and return the electoral votes const getElectoralVotesForState = R.pipe( R.view(populationLens), getElectoralVotes ); If I want to pass in a single state to the getElectoralVotesForState function, it works fine: const alabama = { state: 'Alabama', population: 4833722 }; const alabamaElectoralVotes = getElectoralVotesForState(alabama); // Resolves to 8 While this seems to work for a single object, I can't seem to get this to apply to an array of objects. My guess is that the solution might look something like this: const statesWithElectoralVotes = R.map( R.assoc("electoralVotes", getElectoralVotesForState) )(states); This does add an electoralVotes property to each state, but it's a function and not a resolved value. I'm sure it's just a silly thing I'm getting wrong here, but I can't figure it out. What am I missing?
To apply a function to to an array of items use R.map. Since you want the value you don't need to R.assoc: const POP_PER_ELECTORAL_VOTE = 600000; const populationLens = R.lensProp("population"); const getElectoralVotes = R.pipe( R.divide(R.__, POP_PER_ELECTORAL_VOTE), Math.floor, R.when(R.equals(0), R.always(1)) ); const getElectoralVotesForState = R.pipe( R.view(populationLens), getElectoralVotes ); const mapStates = R.map(getElectoralVotesForState); const states = [{"state":"Alabama","population":4833722},{"state":"Alaska","population":735132},{"state":"Arizona","population":6626624}]; const result = mapStates(states); console.log(result); <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> In addition, the lens is a bit redundant here, take the value of population using R.prop. I would also replace R.when with R.max. const POP_PER_ELECTORAL_VOTE = 600000; const getElectoralVotesForState = R.pipe( R.prop('population'), R.divide(R.__, POP_PER_ELECTORAL_VOTE), Math.floor, R.max(1) ); const mapStates = R.map(getElectoralVotesForState); const states = [{"state":"Alabama","population":4833722},{"state":"Alaska","population":735132},{"state":"Arizona","population":6626624}]; const result = mapStates(states); console.log(result); <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> To add the property to each object, you'll need to get the value from the object, and then add it as a property to the object. This means that we need to use 2 functions - f (R.assoc) & g (getElectoralVotesForState), and apply both of them to the object - x, but one of them (R.assoc) also need the result of the other function. you'll need to apply getElectoralVotesForState on the object to get the number, (g(x)) and then take the result, and it to the object ( To add the electoralVotes you can use R.chain in conjunction with R.assoc. When R.chain is applied to function - R.chain(f, g)(x) is equivalent to f(g(x), x). In your case f - assoc g - getElectoralVotesForState x - the object The combined function - R.chain(R.assoc('electoralVotes'), getElectoralVotesForState) becomes assoc('electoralVotes')(getElectoralVotesForState(object), object). Example: const POP_PER_ELECTORAL_VOTE = 600000; const getElectoralVotesForState = R.pipe( R.prop('population'), R.divide(R.__, POP_PER_ELECTORAL_VOTE), Math.floor, R.max(1) ); const mapStates = R.map( R.chain(R.assoc("electoralVotes"), getElectoralVotesForState) ); const states = [{"state":"Alabama","population":4833722},{"state":"Alaska","population":735132},{"state":"Arizona","population":6626624}]; const result = mapStates(states); console.log(result); <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
I think Ori Drori answers your question well. I have no suggested improvements. But I want to show that it's not too hard to code the current apportionment method used for the U.S. Congress, the Huntington-Hill Method: // Huntington-Hill apportionment method const apportion = (total) => (pops) => huntingtonHill (total - pops.length, pops .map (pop => ({...pop, seats: 1}))) // method of equal proportions const huntingtonHill = (toFill, seats, state = nextSeat (seats)) => toFill <= 0 ? seats : huntingtonHill (toFill - 1, seats .map (s => s.state == state ? {...s, seats: s.seats + 1} : s)) // find state to assign the next seat const nextSeat = (seats) => seats .map (({state, population, seats}) => [state, population * Math.sqrt(1 / (seats * (seats + 1)))]) .sort (([_, a], [_1, b]) => b - a) [0] [0] // ideally, use a better max implementation that sort/head, but low priority // convert census data to expected object format const restructure = results => results .slice (1) // remove header .map (([population, state]) => ({state, population})) // make objects .filter (({state}) => ! ['District of Columbia', 'Puerto Rico'] .includes (state)) // remove non-states .sort (({state: s1}, {state: s2}) => s1 < s2 ? -1 : s1 > s2 ? 1 : 0) // alphabetize fetch ('https://api.census.gov/data/2021/pep/population?get=POP_2021,NAME&for=state:*') .then (res => res.json()) .then (restructure) .then (apportion (435)) .then (console .log) .catch (console .warn) .as-console-wrapper {max-height: 100% !important; top: 0} Here we call the U.S. Census API to fetch the populations of each state, remove Washington DC and Puerto Rico, reformat these results to your {state, population} input format, and then call apportion (435) with the array of values. (If you have the data already in that format, you can just call apportion (435)), and it will assign one seat to each state and then use the Huntington-Hill method to assign the remaining seats. It does this by continually calling nextSeat, which divides each state's population by the geometric mean of its current number of seats and the next higher number, then choosing the state with the largest value. This does not use Ramda for anything. Perhaps we would clean this up slightly with some Ramda functions (for example, replacing pop => ({...pop, seats: 1}) with assoc('seat', 1)), but it would not likely be a large gain. I saw this question because I pay attention to the Ramda tag. But the point here is that the actual current method of apportionment is not that difficult to implement, if you happen to be interested. You can see how this technique is used to compare different sized houses in an old gist of mine.
Sequentially apply multiple functions to object using different lenses
I would like to perform some updates to an array in an object, and then calculate another parameter based on this update. This is what I tried: import * as R from 'ramda' const obj = { arr: [ 2, 3 ], result: { sumOfDoubled: 0 } }; const double = a => { return a*2; } const arrLens = R.lensProp('arr'); const res0sumOfDblLens = R.lensPath(['result','sumOfDoubled']); const calc = R.pipe( R.over(arrLens,R.map(double)), R.view(arrLens), R.sum, R.set(res0sumOfDblLens) ); const updatedObjA = calc(obj); const updatedObjB = R.set(res0sumOfDblLens,R.sum(R.view(arrLens,R.over(arrLens,R.map(double),obj))),obj); // what I want: {"arr":[4,6],"result":{"sumOfDoubled":10}} console.log(JSON.stringify(obj)); //{"arr":[2,3],"result":{"sumOfDoubled":0}}, as expected console.log(JSON.stringify(updatedObjA)); //undefined console.log(JSON.stringify(updatedObjB)); //{"arr":[2,3],"result":{"sumOfDoubled":10}}, correct result but the array did not update I realise that neither approaches will work; approach A boils down to R.set(res0sumOfDblLens,10), which makes no sense as it doesn't have a target object for the operation. Approach B, on the other hand, manipulates the base object twice rather than passing the result of the first manipulation as an input for the second. How can I achieve this using only one function composition; i.e. apply the double() function to one part of the object, and then passing that updated object as input for calculating sumOfDoubled?
As well as OriDrori's converge solution, you could also use either of two other Ramda functions. I always prefer lift to converge when it works; it feels more like standard FP, where converge is very much a Ramda artifact. It doesn't always do the job because of some of the variadic features of converge. But it does here, and you could write: const calc = pipe ( over (arrLens, map (multiply (2))), lift (set (res0sumOfDblLens) ) ( pipe (view (arrLens), sum), identity ) ) But that identity in either of these solutions makes me wonder if there's something better. And there is. Ramda's chain when applied to functions is what's sometimes known as the starling combinator, :: (a -> b -> c) -> (a -> b) -> a -> c. Or said a different way, chain (f, g) //~> (x) => f (g (x)) (x). And that's just what we want to apply here. So with chain, this is simplified further: const arrLens = lensProp('arr') const res0sumOfDblLens = lensPath(['result', 'sumOfDoubled']) const calc = pipe ( over (arrLens, map (multiply (2))), chain ( set (res0sumOfDblLens), pipe (view (arrLens), sum) ) ) const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }} console .log (calc (obj)) <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script> <script>const {lensProp, lensPath, pipe, over, map, multiply, chain, set, view, sum} = R </script>
To get the updated value, and the object, so you can set the new sum, you can use R.converge(): const arrLens = R.lensProp('arr'); const res0sumOfDblLens = R.lensPath(['result', 'sumOfDoubled']); const calc = R.pipe( R.over(arrLens, R.map(R.multiply(2))), R.converge(R.set(res0sumOfDblLens), [ R.pipe(R.view(arrLens), R.sum), R.identity ]) ); const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }}; const result = calc(obj); console.log(result); <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Maybe a variant without a lense would be a better fit for your case? const doubleArr = pipe( path(['arr']), map(x => x*2) ) const newData = applySpec({ arr: doubleArr, result: { sumOfDoubled: pipe( doubleArr, sum ) } })
remove duplicates from array in moment js
i have let array = [moment('2019-01-17'),moment('2019-01-19'),moment('2019-01-19'),moment('2019-01-21')]; i need to remove duplicates so i written filter but it is not working correctly array= array.filter((v,i) => !moment(array.indexOf(v)).isSame(moment(i))) working live plunker code inside index.html
You were on the right track, but details were a bit off. Please try this: const comparisonValues = array.map(v => v.valueOf()); array = array.filter((v,i) => comparisonValues.indexOf(v.valueOf()) == i); Explanation: array.filter((value, index, self) => self.indexOf(value) == index) is an useful pattern for finding unique values in an array The intuition behind the pattern is to "pick only first instances of a value in an array" It only works for values that can be directly compared - indexOf uses strict equality check internally (===) momentValue.valueOf() will return an useful value for this comparison, namely number of milliseconds since the Unix Epoch Our solution uses a helper array that consists of the millisecond values from valueOf and in filter, makes comparisons using valueOf() of the current value in iteration Another way, if you want to use isSame, could be like this: array = array.filter((v, i) => { return array.findIndex(candidate => v.isSame(candidate)) == i });
You can achieve the same result and faster with just a single Array.reduce and once you got the items grouped just get them via Object.values. This would be faster than for each items searching the entire array every time. For small arrays it would not matter but for larger it would be quite noticeable. Here is the concise version: let data = [moment('2019-01-17'), moment('2019-01-19'), moment('2019-01-19'), moment('2019-01-19'), moment('2019-01-19'), moment('2019-01-21')]; const result = data.reduce((a, c) => (a[c.format()] = c, a), {}) console.log(Object.values(result)) <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script> And here the detailed one: let data = [moment('2019-01-17'), moment('2019-01-19'), moment('2019-01-19'), moment('2019-01-19'), moment('2019-01-19'), moment('2019-01-21')]; const result = data.reduce((accumulator, current) => { accumulator[current.format()] = current return accumulator }, {}) console.log(Object.values(result)) <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
Functional programming style pattern matching in JavaScript
I'm writing compiler from kind of functional language to JS. Compiler would run in browser. I need to implement pattern matching mechanics in JS, because original language have one. I've found Sparkler and Z. Sparkler can't be executed in browser as far as I know and Z doesn't have all possibilities I need. So my language have semantics like this: count x [] <- 0 count x [ x : xs ] <- 1 + count x xs count x [ y : xs ] <- count x xs This is what happens in this snippet: First line is definition of a function, which takes two parameters: some variable x and empty list, and returns zero. Second line is definition of a function, which also takes two parameters: some variable x and list, which starts with x, and returns 1 + count(x, xs) Fot this example I want to generate code like this: const count = (x, list) => { match(x, list) => ( (x, []) => {...} (x, [ x : xs ]) => {...} (x, [ y : xs ]) => {...} ) } How properly unfold this kind of pattern matching into ifs and ors?
General case There is a proposal for Pattern Matching in ECMAScript, but as of 2018 it's in a very early stage. Currently, the Implementations section only lists: Babel Plugin Sweet.js macro (NOTE: this isn't based on the proposal, this proposal is partially based on it!) List case Use destructuring assignment, like: const count = list => { const [x, ...xs] = list; if (x === undefined) { return 0; } else if (xs === undefined) { return 1; } else { return 1 + count(xs); } }
Using ex-patterns, you could write your example as follows. You need to use the placeholder names that come with the package (_, A, B, C, ... Z) but you can rename matched variables in the callback function with destructuring (an object containing all named matches is passed in as the first argument to the callback function). import { when, then, Y, _, tail, end } from 'ex-patterns'; const count = list => ( when(list) ([], then(() => 0)) // match empty array ([_], then(() => 1)) // match array with (any) 1 element ([_, tail(Y)], then(({ Y: xs }) => 1 + count(xs))) // match array and capture tail (end); ); This also covers the case where list = [undefined, 'foo', 'bar'], which I don't think would be covered by the accepted answer. To make the code more efficient, you can call count with an Immutable.js List instead of an array (no changes required). In that case, the tail portion of the array doesn't need to be sliced and copied into a new array on every loop. As with the packages you mentioned, this doesn't run in the browser natively, but I guess that's not a major obstacle with modern bundling tools. Here are the docs: https://moritzploss.github.io/ex-patterns/ Disclaimer: I'm the author of ex-patterns :)
I had a need for pattern matching and made something that works for me. const count = patroon( [_], ([, ...xs]) => 1 + count(xs), [], 0 ) count([0,1,2,3]) 4 See readme for more usage examples. https://github.com/bas080/patroon https://www.npmjs.com/package/patroon