Finding objects that are within a time range of each other - javascript

I am using an external API to fetch a list of events happening between two dates. I have then used array.reduce to group the events happening on the same day into one array.
const time = events && events.reduce((acc, item) => {
if (!acc[item.fixture.date.split('T')[1]]) {
acc[item.fixture.date.split('T')[1]] = [];
}
acc[item.fixture.date.split('T')[1]].push(item);
return acc;
}, {})
They are labelled by the time in which the event occurs. If I console.log time then you can see in the image below how the data is returned for one day.
Example of returned data
I am trying to work out how to loop through these objects and find the ones that are within 30 minutes of each other. For example: It would find 16:05:00+01:00 and 16:30:00+01:00 and place these into a new array called Interval together.
What would be the easiest way to achieve this?

const datesInRange = (date1, date2, range) => //range = difference in minutes
(date1 - date2) / 1000 / 60 <= range
? true : false

Related

Array inside useEffect to use in if Statement

I'm trying to use an if statement in my code where I want it to 'open' a Calendar Box if the date of today has occurred as well as for the past days of my calendar to open.
Here is my code where I'm using an useEffect to post it on loading the React Component:
// Call on post method via axios
useEffect(async () => {
console.log(daysData);
const daysDay = daysData.map((day) => day.day);
console.log(daysDay);
if (date + 1 >= daysDay) {
// Url where to post
await axios.post(`http://localhost:5001/open/chocolate`, {
day: date,
});
alert('New day is available to eat!');
}
setOpenCalendarBox('');
}, []);
I'm trying to get an array I've initiated a few lines above of the useEffect function (daysData) and I want the value of the 'day' item inside of the objects inside of the array and then compare the date of today if it is equal to or less than daysDay (day item inside of daysData)
Here is the code for my array:
// Here I initalize the array with useState
const [daysData, setDaysData] = useState([]);
// Here is the port I'm fetching my array from.
useEffect(() => {
fetch('http://localhost:5001/chocolates')
.then((resp) => resp.json())
.then((data) => setDaysData(data));
}, []);
And here is the date code:
// Initiate new Date
const current = new Date();
// Retrieve current day of the month
const date = current.getDate();
I can't seem to get the effect I want. I basically only want to see if the day has passed or if it is today then I want it to post to '/open/chocolate'.
That's probably because the value of daysData is set asynchronously, yet the useEffect block that depends on it does not list it as a dependency. Therefore you are invoking logic, which requires daysData to be populated asynchronously, when the component is loaded at runtime. So daysData will be empty.
A solution is to simply add daysData in the dependency array, so that you will only execute whatever logic that is in there once the array is successfully populated.
On the other hand, you are comparing a number against an array: which will give an unexpected result. If you want any of the day to meet date + 1, use daysDay.some(d => date + 1 >= d). If you want all of the days to meet date + 1, use daysDate.every(d => date + 1 >= d).
useEffect(async () => {
const daysDay = daysData.map((day) => day.day);
// This needs to be fixed, see comment for options
if (daysDay.some(d => date + 1 > d)) {
// Url where to post
await axios.post(`http://localhost:5001/open/chocolate`, {
day: date,
});
}
setOpenCalendarBox('');
}, [daysData]);

How to filter last day in an array?

I have an array of objects like this:
[
{
created: "2019-08-14T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-15T13:24:36Z",
email: "test2#gmail.com"
},
{
created: "2019-08-16T13:24:36Z",
email: "test1#gmail.com"
},
{
created: "2019-08-22T13:24:36Z",
email: "test4#gmail.com"
},
{
created: "2019-08-22T15:29:66Z",
email: "test1#gmail.com"
}
]
The array is sorted by created. I want to filter those records which are on the last day, irrespective of the time on that day. I added the timestamp using moment.js. Something on these lines:
router.get('/GetLastDayRecords', (req, res) => {
res.json(allRecords.filter(record => record.created.max()));
});
Split the task: first get the maximum date which you'll find at the end of the sorted array (just getting the "YYYY-MM-DD" part of it is enough) and then launch the filter:
let max = allRecords.length ? allRecords[allRecords.length-1].created.slice(0,10) : "";
res.json(allRecords.filter(({created}) => created >= max));
First you need to figure out which day is the last day. If you can assume the records are already sorted, then this is pretty simple:
// Assuming your records are stored in the variable "records"
var lastDay = records[records.length - 1].created;
Now here's where your specific answer may differ based on how you want to handle time zones. Suppose one event happened at 11 PM EST (3 AM GMT) and another event happened at 1 AM EST (5 AM GMT). Are these the same day? In Europe they are, but in America they aren't!
What you need to do is create some cipher from the date+time listed to a "day". This way you can compare two "days" to see if they're the same:
lastDay = new Date(lastDay);
// Setting hours, minutes, and seconds to 0 will give you just the "day" without the time, but by default will use the system timezone
lastDay.setHours(0);
lastDay.setMinutes(0);
lastDay.setSeconds(0);
Once you know which day was the last, it's a simple filter:
// Using a for loop
var results = []
for (var i = 0; i < records.length; i++)
{
if (records[i].created > lastDay) {
results.push(records[i]);
}
}
// Using .filter
var results = records.filter(x => x.created > lastDay);
Alternatively, since we know it's already sorted, we can do it a bit more efficiently by binary searching for the first record on the last day, then grabbing all records after that:
var test = records.length / 2;
var step = records.length / 4;
var found = false;
while (!found) {
if (records[test].created < lastDay) {
test += step;
step /= 2;
}
else if (records[test].created > lastDay) {
if (step == 1) {
// We found the exact cut-off
found = true;
}
else {
test -= step;
step /= 2;
}
}
}
var results = records.slice(test);
Because you're only interested in the "last" day, the logic is a bit simpler. If you wanted the "third" day, you would need to check if created was after the start of the third day and before the end of the third day. We can just check if it's after the start of the last day.
I would create a function to turn your created properties into data be easily compared.
I would also avoid trying to do the entire filter operation in one or two lines as it will difficult to read by other developers.
const dateToInt = date => parseInt( date.split('T').shift().replace(/-/g, '') );
The above will:
Split your created property into an array of date and time.
Select the first element, which happens to be the date.
Remove the dashes in the date.
Coerce the value into a number.
With this you can find the maximum value and filter based on that value.
const nums = foo.map( ({ created }) => dateToInt(created) )
First get a list of numbers from the dataset.
const max = Math.max( ...nums )
Get the biggest number in the list.
const lastDays = foo.filter( ({ created }) => dateToInt(created) === max )
With all that setup, getting the max date is very easy and readable.
Of course, since the list is already sorted. You could have just done this as well.
const last = foo[foo.length -1].created;
const lastDays = foo.filter( ({ created }) => created === last )
I wrote a solution using reduce and filter:
const lastDay = arr.reduce((acc, el) => {
const date = el.created.substr(0,10);
const oldDate = new Date(acc);
const nextDate = new Date(date);
if(oldDate.getTime() > nextDate.getTime()) {
return oldDate;
} else {
return nextDate;
}
}, '1900-01-01');
const lastDayArr = arr.filter(el => {
const date = el.created.substr(0,10);
const oldDate = new Date(lastDay);
const nextDate = new Date(date);
return (oldDate.getTime() === nextDate.getTime());
});
First, you find the most recent date, reducing the original array by comparing which date is the most recent, for this you drop the part of the created string that specifies the hours/minutes/seconds.
You can use a very distant in time date as initial value, or you can set it to null and add another validation in your callback function.
As a second step, you use filter, using the same technique of dropping the hours/minutes/seconds of the created string.
The end result is an array of the elements with the most recent date in your original array.
If you can assume the array is sorted, you can skip the reduce method and just do:
const lastDay = arr[arr.length - 1].created.substr(0,10);
This should work:
allRecords.filter( record => {
let last_date = allRecords[ allRecords.length - 1].created
return last_date.slice(0, 10) === record.created.slice(0, 10)
})
Basically, you are getting the last element from your array and slicing its created value down to its date. Then you are slicing your current record's created value down to its date and comparing if they are the same.
Assuming that the array is already ASC ordered:
const onLastDay = values.filter( v => {
const last = moment(values[ values.length - 1 ].created)
const differenceInDays = last.diff(moment(v.created), 'days')
return differenceInDays < 1
})
console.log(onLastDay)
NOTE: If you try with the reported array you get an error due the fact that the last date is not valid! There are 66 seconds!

sorting two associative arrays/stacks

I am implementing an algorithm I designed and am exploring different approaches
This isn't a homework problem but I am going to explain it like one: lets say a merchant has bought inventory of apples on different days, and also sold some on different days. I want the weighted average timestamp of their current purchases.
I am storing this data object as timestamp string in epoch time, and quantity of apples. My dataset actually has the purchases and the sells in separate data sets, like so:
//buys
var incomingArray = {
"1518744389": 10,
"1318744389": 30
};
//sells
var outgoingArray = {
"1518744480": 3,
"1418744389": 5,
"1408744389": 8
};
and I would like the outcome to show only the remainding incomingArray timestamp purchase pairs.
var incomingArrayRemaining = {
"1518744389": 7,
"1318744389": 17
};
Where you see there was one outgoing transaction for 3 apples at a later timestamp, therefore subtracting from 10. And there were 13 outgoing transactions before the buy of 10, but after the purchase of 30, so they only subtract from the 30.
Note, if more than 10 were transferred after 10, it would subtract from both 10 and 30. The number of apples can never be less than 0.
First, to accomplish my goals it seems that I need to know how many are actually still owned from the lot they were purchased in.
Instead of doing stack subtracting in the LIFO method, it seems like this has to be more like Tax Lot Accounting. Where the lots themselves have to be treated independently.
Therefore I would have to take the timestamp of the first index of the sell in the outgoing array and find the nearest older timestamp of the buy in the incoming array
Here is what I tried:
for (var ink in incomingArray) {
var inInt = parseInt(ink);
for (var outk in outgoingArray) {
if (inInt >= 0) {
var outInt = parseInt(outk);
if (outInt >= inInt) {
inInt = inInt - outInt;
if (intInt < 0) {
outInt = inInt * -1; //remainder
inInt = 0;
} //end if
} //end if
} //end if
} //end innter for
} //end outer for
It is incomplete and the nested for loop solution will already have poor computational time.
That function merely tries to sort the transactions so that only the remaining balance remains, by subtracting an outgoing from the nearest incoming balance, and carrying that remainder to the next incoming balance
I feel like a recursive solution would be better, or maybe something more elegant that I hadn't thought of (nested Object forEach accessor in javascript)
After I get them sorted then I need to actually do the weighted average method, which I have some ideas for already.
First sorting, then weighted average of the remaining quantities.
Anyway, I know the javascript community on StackOverflow is particularly harsh about asking for help but I'm at an impasse because not only do I want a solution, but a computationally efficient solution, so I will probably throw a bounty on it.
You could convert the objects into an array of timestamp-value pairs. Outgoing ones could be negative. Then you can easily sort them after the timestamp and accumulate how you like it:
const purchases = Object.entries(incomingArray).concat(Object.entries(outgoingArray).map(([ts, val]) => ([ts, -val])));
purchases.sort(([ts1, ts2]) => ts1 - ts2);
Now you could iterate over the timespan and store the delta in a new array when the value increases (a new ingoing):
const result = [];
let delta = 0, lastIngoing = purchases[0][0];
for(const [time, value] of purchases){
if(value > 0){
// Store the old
result.push([lastIngoing, delta]);
// Set up new
delta = 0;
lastIngoing = time;
} else {
delta += value;
}
}

immutable.js filter and mutate (remove) found entries

I have two loops, one for each day of the month, other with all events for this month. Let's say I have 100 000 events.
I'm looking for a way to remove events from the main events List once they were "consumed".
The code is something like:
const calendarRange = [{initialDate}, {initialDate}, {initialDate}, {initialDate}, ...] // say we have 30 dates, one for each day
const events = fromJS([{initialDate}, {initialDate}, {initialDate}, ...]) // let's say we have 100 000
calendarRange.map((day) => {
const dayEvents = events.filter((event) => day.get('initialDate').isSame(event.get('initialDate'), 'day')) // we get all events for each day
doSomeThingWithDays(dayEvents)
// how could I subtract `dayEvents` from `events` in a way
// the next celandarRange iteration we have less events to filter?
// the order of the first loop must be preserved (because it's from day 1 to day 3{01}])
}
With lodash I could just do something like:
calendarRange.map((day) => {
const dayEvents = events.filter((event) => day.get('initialDate').isSame(event.get('initialDate'), 'day')) // we get all events for each day
doSomeThingWithDays(dayEvents)
pullAllWith(events, dayEvents, (a, b) => a === b)
}
How to accomplish the same optimization with immutablejs? I'm not really expecting a solution for my way of iterating the list, but for a smart way of reducing the events List in a way it get smaller and smaller..
You can try a Map with events split into bins - based on your example, you bin based on dates - you can lookup a bin, process it as a batch and remove it O(1). Immutable maps are fairly inexpensive, and fare much better than iterating over lists. You can incur the cost of a one time binning, but amortize it over O(1) lookups.
Something like this perhaps:
eventbins = OrderedMap(events.groupBy(evt => evt.get('initialDate').dayOfYear() /* or whatever selector */))
function iter(list, bins) {
if(list.isEmpty())
return
day = list.first()
dayEvents = bins.get(day.dayOfYear())
doSomeThingWithDays(dayEvents)
iter(list.shift(), bins.delete(day))
}
iter(rangeOfDays, eventbins)
By remobing already processed elements you are not going to make anything faster. The cost of all filter operations will be halved on average, but constructing the new list in every iteration will cost you some cpu cycles so it is not going to be significantly faster (in a big O sense). Instead, you could build an index, for example an immutable map, based on the initialDate-s, making all the filter calls unnecessary.
const calendarRange = Immutable.Range(0, 10, 2).map(i => Immutable.fromJS({initialDate: i}));
const events = Immutable.Range(0, 20).map(i => Immutable.fromJS({initialDate: i%10, i:i}));
const index = events.groupBy(event => event.get('initialDate'));
calendarRange.forEach(day => {
const dayEvents = index.get(day.get('initialDate'));
doSomeThingWithDays(dayEvents);
});
function doSomeThingWithDays(data) {
console.log(data);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>

Combining two Json feeds in one

Good day stack people,
I'm doing research for my self on how to combine two Json feeds in one and display them in one timeline by date using JS or jQuery.
For example we will have two json files file1.json and file2.json (one from twitter and another from filckr).
I need "n" numbers of latest items and show append them to show by items time.
Any ideas or hints?
Thank you!
P.S. Example feeds: http://twitter.com/status/user_timeline/ignaty.json?count=5 and http://api.flickr.com/services/feeds/groups_pool.gne?%20id=675729#N22&lang=en-us&format=json
Lets pick only any one value from each.
Here's some method that should do that (you need to tweak it though).
Essentially you request the two APIs and then (once both requests are completed) you sort an array of normalized objects.
var all = [];
var waiting = 2; // number of services you request
// once you get response1 or response2
function parseFlickr(data) {
$.each(data, function(index, item) {
// normalize item here depending on service format (parse date)
var normalized = {};
normalized.date = new Date(Date.parse(item.date));
all.push(normalized);
});
if(--waiting == 0) { onDone(); }
}
function onDone() {
all.sort(function(a,b) {
// switch -1 and +1 to invert ordering
return (a.date < b.date ? -1 : (a.date > b.date ? +1 : 0));
});
// do the rendering/appending (you might limit the amount here)
}

Categories

Resources