remove duplicates from array in moment js - javascript

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>

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 to compare between two array that is inside one same array? [duplicate]

I have two arrays [a,b,c,d] and [b,d,f,h].
I want to get an array back with the common elements [b,d].
I can achieve that with a combination of filter and indexOf:
[a,b,c,d].filter(el => [b,d,f,h].indexOf(el) !== -1)
but I was wondering if and how I can do the same with reduce.
I admit that, despite looking at many examples, reduce still is to me one of the most obscure JS methods, so I'd really appreciate some advice.
ES6, a propoosal with Array#includes
The includes() method determines whether an array includes a certain element, returning true or false as appropriate.
On every loop of aa, reduce adds the element to the result array if the value is found in the test array bb. If not found, the former result is returned.
var aa = ['a','b','c','d'],
bb = ['b','d','f','h'],
cc = aa.reduce((r, a) => bb.includes(a) && r.concat(a) || r, []);
console.log(cc);
Just a smarter approach with using a single array which contains all arrays.
var aa = ['a','b','c','d'],
bb = ['b','d','f','h'],
result = [aa, bb].reduce((a, b) => a.filter(c => b.includes(c)));
console.log(result);
Reduce is designed to return a single value from a list of items. So filter makes much more sense here.
A good use for reduce would be to return the total number of common elements. Check it out here: https://jsfiddle.net/c69vgzL4/
var a = ['a','b','c','d']
var b = ['b','d','f','h']
var number_of_common = b.reduce(function(prev, next) {
return prev + (a.indexOf(next) + 1 ? 1 : 0)
}, 0)
$('body').html(number_of_common)
Not only two arrays but for an intersection of n arrays... Let's invent Array.prototype.intersect()
Array.prototype.intersect = function(...a) {
return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
}
var arrs = [[0,2,4,6,8],[4,5,6,7],[4,6]],
arr = [0,1,2,3,4,5,6,7,8,9];
console.log(JSON.stringify(arr.intersect(...arrs)));
// or just do
console.log(JSON.stringify(["a","b","c","d"].intersect(["b","d","f","h"])));

Ramda.js transducers: average the resulting array of numbers

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.

is it possible to sort an array and just get the return of the placements NOT change the order

I have an array of numbers
counter[7,3,9,5,1]
if i do counter.sort() it will change the array to
counter[1,3,5,7,9]
is it possible to sort an array and just get the return of the placements NOT CHANGE THE ORDER
something like this
sortedArrayByPlacment[3,1,4,2,0];
You can copy the array, sort it, then turn it into an object (for quick lookup) and map the original array onto the object to identify the new indicies.
You'll also need to pass a comparator function; .sort without any arguments will sort lexicographically, resulting in, eg, 11 coming before 2, which almost certainly isn't desirable.
const arr = [7,3,9,5,1];
const indexByNum = Object.fromEntries(
[...arr]
.sort((a, b) => a - b)
.map((num, i) => [num, i])
);
const indicies = arr.map(num => indexByNum[num]);
console.log(indicies);
I'm using Object.fromEntries to make the mapping less computationally complex, but you could do without it if you wanted:
const arr = [7,3,9,5,1];
const sorted = [...arr].sort((a, b) => a - b);
const indicies = arr.map(num => sorted.indexOf(num));
console.log(indicies);
PS: These snippets work only if the values are unique in the array

Is there a way to check whether the argument passed into a function is an array in javascript?

const validateCred = arr => {
let checkableArr = arr.pop();
for (let i = arr.length - 1; i >= 0; i--) {
arr[i]
checkableArr.push(arr[i])
}
}
When i run the code, I get an error saying that .push() is not a function that I can use on checkableArr. this is because checkableArr isn't an array due to it being a variation of arr (the argument that will be passed when the function is called), which the function isn't sure is an array, is there any way to check that the argument passed into the function is an array?
EDIT:
The thing I was looking for is called isArray(), a method that returns a boolean indicating if the item passed into it is an array or no. Thanks to #David for showing me this tool, along with a bunch of helpful information that helped a lot with writing my program
You're getting that error, because you haven't made sure that the last item of the passed array (arr) is an array itself, but your function's logic requires it to be an array.
There are various ways to solve this, some of them have already been outlined by others (#hungerstar).
Check the last element of arr
One attempt is to ensure that the last element/item inside arr is an array and bail out if it isn't.
const validateCred = arr => {
let lastItem = arr.pop();
if (!Array.isArray(lastItem)) {
throw new Error('validateCred :: Last item of passed array must be an array itself');
}
// ... rest of your code ...
}
Although that does not solve the root cause, it ensures you get a decent and descriptive message about what went wrong. It's possible to improve that by defining a fallback array in case the last item isn't an array itself. Something like this:
const validateCred = arr => {
let lastItem = arr.pop();
let checkableArr = Array.isArray(lastItem) ? lastItem : [];
// ... rest of your code ...
}
One thing to note: If the last item may be an array with a value inside, you have to copy that value into the new array!
const validateCred = arr => {
let lastItem = arr.pop();
let checkableArr = Array.isArray(lastItem) ? lastItem : [lastItem]; // <--
// ... rest of your code ...
}
HINT: The following answer is based on guessing. The name validateCred lets me assume you use it to validate credentials. However, that's just guessing because all the provided code does is taking the last item and then pushing the rest of the contents of arr reversely into it (= reversing and flattening)
Reversing and flattening
If all you want to do with validateCred is reversing and flattening (and you only target supporting environments), you can easily do that with a one-liner:
// impure version
const validateCred = arr => arr.reverse().flat();
// pure version
const validateCred = arr => arr.flat().reverse();
To support older environments as well, you can use .reduce and .concat instead of .flat:
// impure version
const validateCred = arr => arr.reverse().reduce((acc, x) => acc.concat(x), []);
// pure version
const validateCred = arr => arr.reduce((acc, x) => acc.concat(x), []).reverse();
Yes, we pass an array as an argument using call/apply method. In your code when you are using arr.pop() it gets converted to number/string depending upon what type of array you specified, I specified below integer value so checkableArr is now integer so because of this you are getting an error.
Corrected code is here. Just replace in your code like:
let checkableArr = arr; //arr.pop();

Categories