Turn a forEach function into a reduce function - javascript

I have been asked to refactor the following code:
const temp = {};
this.sessionData = [];
sessionsData.forEach(session => {
const date = moment(session.startDatetime).format('DDMMYYYY');
if (temp[date]) {
temp[date].push(session);
} else {
temp[date] = [session];
}
});
Apparently it can be more efficient using reduce?
I have tried to simply place reduce in the function but this isnt good enough ;)
const temp = {};
this.sessionData = [];
sessionsData.reduce(session => {
const date = moment(session.startDatetime).format('DDMMYYYY');
if (temp[date]) {
temp[date].push(session);
} else {
temp[date] = [session];
}
});
I understand reduce can add the elements together and have other fun stuff happen during the process BUT specifically I have been asked to sort of get rid of my variables and use them within reduce I suppose!
Any help would be greatly appreciated! Thanks!

If you want to refactor this using reduce, you have to set an inital value {} for the accumulator, and then you have to modify this accumulator, this is what reduce will return after the iterations on your data. You then have to assign this returned accumulator to your temp variable.
So the only input you give to reduce is sessionData, on which reduce is applied. Then at each iteration, it gives you the session and you modify accum.
Also, make sure to return this accum at the end of each iteration, since it has to be passed to the next iteration.
Here is the MDN doc on reduce.
const temp = sessionData.reduce((accum, session) => {
const date = moment(session.startDatetime).format('DDMMYYYY');
if (accum[date]) {
accum[date].push(session);
} else {
accum[date] = [session];
}
return accum;
}, {});

Here you go
let dates = [{startDatetime: '2016-01-02'}, {startDatetime: '2016-02-02'}, {startDatetime: '2016-03-02'}];
const temp = dates.reduce(function(acc, cur) {
const date = moment(cur.startDatetime).format('DDMMYYYY');
if(acc[date]) acc[date].push(cur)
else acc[date] = [cur]
return acc;
}, {})
console.log(temp)

You could use in es6
var sessionData = [
{foo:'bar', startDatetime: Date.now()},
{xxx:'yyy', startDatetime: Date.now()}
];
console.log(
sessionData.reduce((obj,next) => {
const key = moment(sessionData.startDatetime).format('DDMMYYYY');
if(!obj[key]) obj[key] = [];
obj[key].push(next);
return obj;
},{})
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>

Related

Firestore - Clever approach to converting all Firestore.Timestamp objects into Javascript date

I have a collection of objects that I want to retrieve. The objects have some date key:value pairs in them and I want to return all of those as a proper Javascript date. I don't want to declare them all one-by-one, as there are some dates that only exist on some objects, some I might not know about and in general, it's frustrating to declare everything one-by-one.
Here is my code that does not work, how could I get it working?
async function getChargesFromDatabase() {
const chargesCol = fsExpenses.collection('charges');
const chargesDocs = (await chargesCol.limit(50).orderBy('startTs', 'desc').get()).docs.map((doc) => {
const returnDoc: any = {};
for (const [key, value] of Object.entries(Object.entries(doc.data()))) {
returnDoc[key] = value?.toDate() ?? value;
}
return returnDoc;
});
return chargesDocs;
}
You will have to check all the keys as you are doing now by checking if a field is instance of Firestore Timestamp. Try using the following function:
const convertTimestamps = (obj: any) => {
if (obj instanceof firebase.firestore.Timestamp) {
return obj.toDate();
} else if (obj instanceof Object) {
// Check for arrays if needed
Object.keys(obj).forEach((key) => {
obj[key] = convertTimestamps(obj[key]);
});
}
return obj;
};
async function getChargesFromDatabase() {
const chargesCol = fsExpenses.collection('charges');
const chargesSnap = await chargesCol.limit(50).orderBy('startTs', 'desc').get()
const chargesDocs = chargesSnap.docs.map((doc) => convertTimestamps(doc.data()))
}

Angular code mysteriously assigning values

This code is assigning the value of 'temps' to this.messages for some reason that I do not understand, any help would be appreciated. I know the issue lies within the below chunk of code as removing the .sort section changes the output order of this.messages.
this.currentMessageSubject.subscribe(()=> {
console.log('Refreshing messages')
if (this.conversationId) {
firebase.firestore().collectionGroup('conv').where("parentMessageId", "==", this.conversationId).get().then(async (querySnapshot) => {
let temps: FirebaseMessages;
let tempsMsgs = new Map();
temps = this.messages;
temps.messages = []
querySnapshot.forEach((newConvo) => {
const thing = { id: newConvo.id, ...newConvo.data() } as FirebaseMessage;
if (!tempsMsgs.has(thing.id)) {
tempsMsgs.set(thing.id, thing)
temps.messages.push(thing);
}
})
temps.messages.sort(function(a,b){
return a.timestamp.seconds - b.timestamp.seconds;
});
temps.messages = temps.messages.filter((message) => {
if (message.id) {
return true;
}
return false;
})
})
}
})
If you assign a non-primitive value to a variable (i.e. object), it will be passed by reference.
So, in your case this line:
temps = this.messages;
assigns the same object that is in your this.messages to your temps variable. So, editing temps will actually edit the this.messages as well, since both are pointing to the same object.
You will have to clone your object, and assign the clone to your temps variable. Regarding cloning - there are multiple good answers about cloning object on SO, so I will not cover that here since we don't know the structure of the messages.
When doing temps = this.messages;, the reference is passed to temps.
You need to clone this object in another way (notice the change line 7) :
this.currentMessageSubject.subscribe(()=> {
console.log('Refreshing messages')
if (this.conversationId) {
firebase.firestore().collectionGroup('conv').where("parentMessageId", "==", this.conversationId).get().then(async (querySnapshot) => {
let temps: FirebaseMessages;
let tempsMsgs = new Map();
temps = {...this.messages};
temps.messages = []
querySnapshot.forEach((newConvo) => {
const thing = { id: newConvo.id, ...newConvo.data() } as FirebaseMessage;
if (!tempsMsgs.has(thing.id)) {
tempsMsgs.set(thing.id, thing)
temps.messages.push(thing);
}
})
temps.messages.sort(function(a,b){
return a.timestamp.seconds - b.timestamp.seconds;
});
temps.messages = temps.messages.filter((message) => {
if (message.id) {
return true;
}
return false;
})
})
}
})
You can refer to this topic to learn more about this : What is the most efficient way to deep clone an object in JavaScript?

How to interupt Array.push with Proxy?

I want to add a side effect every when an array being pushed. For example, I want to add console.log:
var arr = [];
arr.push(1); // => I want it to work normally, and in addition, it logs 1 to the console
How to achieve that? I'm looking for a solution using Proxy and I have tried handler.get() and handler.apply() but still, can't figure it out.
To directly answer your initial question...you need to return a closure from the get trap. To actually trap this, you would need to use proxy.push() instead of array.push(), though. For example:
const arr = [];
const arrProxy = new Proxy(arr, {
get(target, prop) {
if (prop === 'push') {
return (...args) => {
console.log(...args);
return target[prop](...args);
};
}
return target[prop];
}
});
arrProxy.push('test');
arrProxy.push('test1', 'test2');
Here's the final answer that I'm comfortable with, it doesn't use Proxy by the way.
{
var arr = [];
// add push
arr.push = function (...items) {
console.log(...items);
Array.prototype.push.apply(this, items);
};
arr.push('test');
arr.push('test1');
// clean up the push
delete arr.push;
}
something like that ?
Object.defineProperty(Array.prototype, 'myPush',
{
value : function (...val)
{
console.log(...val)
return this.push(...val)
}
})
let aa = []
aa.myPush( 5,123)
console.log('aa = ', aa )

Store new value in an array of objects

I want to change the value of an object of an array but seems like values are not saving.I read about immutable objects but there must be somehow a way to do that.I would be glad for any help, thank you.
app.get('/api/reservations',function(req,res) {
Rezervari.getReservations(function(err,reserv){
if(err){
throw err;
}
let datas = reserv
for( var i=0;i < reserv.length ;i++){
let changetime = dateformat(datas[i].data).format("MM-DD-YYYY")
datas[i].data = changetime;
datas[i].data = dateformat(datas[i].data).format("MM-DD-YYYY")
console.log(datas[i].data) // 2018-09-17T21:00:00.000Z
console.log(changetime) // 09-18-2018
}
res.json(datas);
});
});
Edit: The object is reserv or datas(same array).I want to change field dataof reserv from ISO format to MM-DD-YYYY format.Value is changed in var changeTime but in the array value of data is not changed.
This could happen if the objects in the array were proxies. Here's an example of how you could implement something similar:
const handler = {
get: function(obj, prop) {
if (prop === 'data') {
return this._data.toISOString();
}
},
set: function(obj, prop, value) {
if (prop === 'data') {
this._data = new Date(value);
}
}
};
let obj = new Proxy({}, handler);
obj.data = '2018-09-17';
console.log(obj.data); // returns '2018-09-17T00:00:00.000Z'
If this is your case, and you want to avoid the proxy behavior, you could map the original array to a new one:
res.json(datas.map(item => {
return {
data: dateformat(item.data).format("MM-DD-YYYY")
/* extract any other properties you're interested in from the original objects */
};
}));
You could try cloning the Javascript objects, populating a new array and returning it. Here is an example:
let datas = [];
for (let i = 0; i < reserv.length ; i++){
const clone = Object.assign({}, reserv[i]);
clone.data = dateformat(reserv[i].data).format("MM-DD-YYYY");
datas.push(clone);
}
res.json(datas);

Get last value inserted into a Set

The MDN documentation for Set says that JavaScript Set objects retain insertion order of elements:
Set objects are collections of values, you can iterate its elements in insertion order.
Is there a way to get the last item inserted into a Set object?
var s = new Set();
s.add("Alpha");
s.add("Zeta");
s.add("Beta");
console.log(getLastItem(s)); // prints "Beta"
Edit
It is possible to implement a Linked Set datastructure container class that has the same interface as Set and has the desired capability. See my answer below.
I was not able to find any method to get last value inserted in set from ECMA 2015 Specification, may be they never intended such a method, but you can do something like:
const a = new Set([1, 2, 3]);
a.add(10);
const lastValue = Array.from(a).pop();
Edit:
on second thought, a space efficient solution might be:
function getLastValue(set){
let value;
for(value of set);
return value;
}
const a = new Set([1, 2, 3]);
a.add(10);
console.log('last value: ', getLastValue(a));
Some ideas:
Consider using an array instead of a set. Extracting the last element of an array is easy, e.g.
array[array.length-1];
array.slice(-1)[0];
array.pop(); // <-- This alters the array
If you really need a set, you can convert it to an array when you want to extract the last item, but that will cost time and space.
Iterate the set manually. This will cost time but not as much space as copying into an array. For example (there are probably more elegant ways to do this)
var set = new Set([1, 2, 3]);
var iter = set.values(), prev, curr;
do {
prev = curr;
curr = iter.next();
} while(!curr.done)
var last = prev.value; // 3
Consider inserting the items in reverse order. Then you only need to get the first item in the set, and that's easier:
set.values().next().value;
Subclass Set to add this new functionality:
class MySet extends Set {
add(value) {
super.add(value);
this.last = value;
}
}
var set = new MySet();
set.add(1); set.add(2); set.add(3);
set.last; // 3
Note this will only detect values added with add. To be more complete, it should also detect the latest value when the set is constructed, and update the value when the last item is removed.
Yes, there is a way to do that, you can simply convert the set to an array and pop of the last item
function getLastItem(_set) {
return [..._set].pop();
}
to get keys/values etc, you can do
return [..._set.entries()].pop(); // the entire entry
return [..._set.keys()].pop(); // the key only
return [..._set.values()].pop(); // the value only
If you don't want to create an array, you'd probably have to iterate and get the last value, like this
var last; s.forEach(k => { last = k }); // last === "Beta"
FIDDLE
Just another approach.
Set.prototype.last = function(){
return new Set().add( [...this].pop() );
}
Set.prototype.lastKey = function(){
return [...this.keys()].pop();
}
Set.prototype.lastValue = function(){
return [...this.values()].pop();
}
var lastSet = s.last(); // "Beta"
var lastKey = s.lastKey(); // "Beta"
var lastValue = s.lastValue(); // "Beta"
I have created a replacement for Set, which re-implements the linked functionality of the set, using an underlying Map.
class LinkedSetLink {
constructor(value) {
this.value = value;
this.prev = this;
this.next = this;
}
insertBefore(item) {
const prev = item.prev = this.prev;
const next = item.next = this;
next.prev = item;
prev.next = item;
}
remove() {
const prev = this.prev;
const next = this.next;
next.prev = prev;
prev.next = next;
}
}
class LinkedSet {
constructor(iterable) {
this._map = new Map();
this._pivot = new LinkedSetLink(/* undefined */);
if (iterable) {
this._addAll(iterable);
}
}
_addAll(iterable) {
for (const item of iterable) {
this.add(item);
}
}
has(item) {
return this._map.has(item);
}
add(item) {
if (!this._map.has(item)) {
const link = new LinkedSetLink(item);
this._pivot.insertBefore(link);
this._map.set(item, link);
}
}
delete(item) {
const link = this._map.get(item);
if (link) {
this._map.delete(item);
link.remove();
}
}
clear() {
this._map.clear();
this._pivot.next = this._pivot.prev = this._pivot;
}
get size() {
return this._map.size;
}
values() {
return this._map.keys();
}
keys() {
return this.values();
}
[Symbol.iterator]() {
return this.values();
}
*entries() {
for (const key of this.values()) {
yield [key, key];
}
}
first() {
return this._pivot.next.value;
}
last() {
return this._pivot.prev.value;
}
}
function test1() {
console.log(Array.from(new LinkedSet(["a", "b", "c"]).entries()));
}
function test2() {
console.log(new LinkedSet(["a", "b", "c"]).last());
}
<button onclick="test1()">test entries</button>
<button onclick="test2()">test last</button>
There is no method for accessing the last item, but you can do it as follows
let setOfNumbers = new Set([10]);
setOfNumbers.add(20).add(2);
let lastValue = [...setOfNumbers].pop()
A simple solution, but O(N):
const getLastValueFromSet = (set) => {
for (var value of set);
return value;
}

Categories

Resources