Related
I have a problem optimizing the runtime for my code. Ideally, what I'm trying to achieve is that all the operations below is performed in a single loop, so that I don't have to run through the dataset many times as I'm doing now (very large dataset!)
The code is transforming aggData to an array on the following format: [0: 0, 1: 0, 2: 0, 3: 43, 4: 121, 5: 0, ....], where each number represents a year in the interval, if the interval is (1800-2020) 0 will represent the count for 1800, 1 will be 1801 and so on ..
aggData is an array of objects on the following format: {key_as_string: "1900-01-01T00:00:00.000Z", key: -2208988800000, doc_count: 17}. The start-year is the first year with a doc_count higher than 0.
Below I provide a description of what each step does as the code is now:
Here I am changing the format of each object in the list to be : {year: number, count: number}
const formatAggData = aggData
.map((item: AggData) => {
return { year: moment(item.key_as_string).year(), count: item.doc_count };
});
This function creates an array of objects with the from as start year and to as end year, if the year already exists in existingArray it uses the count from there, if not it sets count to 0.
function fillYears(from: number, to: number, existingArray: YearCount[]) {
const existingObject: { [year: string]: number } = { year: null };
existingArray.map((x: YearCount) => (existingObject[x.year] = x.count));
const yearsArray = [];
for (let i = from; i <= to; i++) {
yearsArray.push({
year: i,
count: existingObject[i] || 0,
});
}
return yearsArray;
}
Converts year values to count-values, where the first year in the list will be 0 with the corresponding value, second will be 1 with corresponding value and so on..
const resultList = fillYears(min, max, formatAggData).map(
(item: YearCount) => item.count,
);
I was looking at you code. can't you do it like this? it looks like you don't need to know the year at this moment
function fillYears(from: number, to:number, existingYears: YearCount[]) {
for (let i = from; i <= to; i++) {
yearsArray.push({
year: i,
count: existingYears[i].doc_count || 0,
});
}
}
How to find all objects where time difference between each consecutive object is less than 1 minute.
const input = [
{ id: 12,
time: '2018-03-01T12:34:00.000Z'},
{ id: 15,
time: '2018-03-02T09:25:20.000Z'},
{ id: 19,
time: '2018-03-04T07:14:20.000Z'},
{ id: 23,
time: '2018-04-01T10:24:00.000Z'},
{ id: 24,
time: '2018-04-01T10:24:40.000Z'},
{ id: 25,
time: '2018-04-01T10:25:10.000Z'}
]
expected output ===> [
{ id: 23,
time: '2018-04-01T10:24:00.000Z' },
{ id: 24,
time: '2018-04-01T10:24:40.000Z' },
{ id: 25,
time: '2018-04-01T10:25:10.000Z' }
]```
Assuming that there's only exactly one run of consecutive items in one minute, you can use the following algorithm:
Ensure your list is sorted, which in this case it seems is already in ascending time order, so I'll skip this step. You should not skip this step is the list isn't guaranteed to be sorted.
We want to find the first two consecutive items. Loop through each element of the array, starting from the second. For each item:
Check the difference in time between the previous item and the current one. If it is less than a minute, the previous and current items are the first consecutive entries. Stop the loop (break).
If we have iterated through the whole array without finding any consecutive entries, return an empty list.
We now want to find the rest of the consecutive items. Loop through each item starting from the item directly after the last known consecutive item. For each item:
If the time difference between the previous and current items is more than a minute, stop the loop (break).
Otherwise, the current item is also consecutive. Count it in, and continue the loop.
Return all known consecutive items.
Here is one such implementation:
// `entries` is of type `{ id: number; time: string; }`
function getConsecutiveEntries(entries) {
// Find the first two consecutive entries
let idx;
for (idx = 1; idx < entries.length; idx++) {
const prevTime = new Date(entries[idx - 1].time).getTime();
const curTime = new Date(entries[idx].time).getTime();
if (curTime - prevTime <= 60000) break;
}
if (idx === entries.length) return [];
const result = [entries[idx - 1], entries[idx]];
// Find the rest of consecutive entries
idx++;
while (idx < entries.length) {
const prevTime = new Date(entries[idx - 1].time).getTime();
const curTime = new Date(entries[idx].time).getTime();
if (curTime - prevTime > 60000) break;
result.push(entries[idx]);
idx++;
}
return result;
}
This implementation can be further optimized by memoizing the the results of Date to number conversions (the new Date(...).getTime()) calls, which happens twice for most entries.
Here is my days list
days_list: any[] = [
{
id: 0,
value: 'Monday'
},
{
id: 1,
value: 'Tuesday'
}, {
id: 2,
value: 'Wednesday'
}, {
id: 3,
value: 'Thursday'
}, {
id: 4,
value: 'Friday'
}, {
id: 5,
value: 'Saturday'
},
{
id: 6,
value: 'Sunday'
},
]
Here is my Business Hours
business_hours = { day_to: 2, time_to: "23:00", day_from: 5, time_from: "08:00" }
I'm using UTC date format I want to know if the day from days_list exist according to day_from and day_to
For example here day_from is 5 which is Saturday and day_to is 2 Wednesday so the required array is:
["Saturday", "Sunday", "Monday". "Tuesday". "Wednesday"] and the same for time if the current time exists in time_from and time_to,
My code is:
const activationDate = new Date();
const d_date = activationDate.getUTCDay() - 1;
console.log(d_date);
const B_from = this.getMin(this.business_hours.time_from);
const B_To = this.getMin(this.business_hours.time_to);
const min = activationDate.getUTCMinutes();
console.log(min)
const naan = activationDate.getUTCHours();
console.log(naan)
const utcTime = this.getUtcMin(naan, min);
for(let j = 0; j < this.business_hours.day_to; j++) {
for (let i = this.business_hours.day_from; i < this.days_list.length; i++) {
console.log(this.days_list[i]);
if (this.days_list[i].id === d_date) {
this.is_open = true;
console.log(this.days_list[i].value);
}
}
}
it's not giving the required results.
My understanding is you'd like to treat your array as circular, and then slice it according to a "from" and "to" index, where both the "from" and "to" indexes are treated as inclusive.
Let's assume you have an array of strings like this:
console.log(dayArray);
// ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
(which you can turn your structure into easily enough like:
const dayArray = days_list.reduce<string[]>((a, d) => (a[d.id] = d.value, a), []);
)
Then you can write a circular array slice with inclusive endpoints function in any number of ways. Here's one:
function circularArraySlice<T>(arr: T[], fromIndex: number, toIndex: number) {
return arr.map((_, i, a) => a[(i + fromIndex) % a.length]).
slice(0, ((arr.length + toIndex - fromIndex) % arr.length) + 1);
}
Essentially we're walking off the end of the array and back onto the beginning using modular arithmetic as (almost) implemented by the JS remainder operator (%). Let's see if it works:
console.log(circularArraySlice(dayArray, 5, 2));
// ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday"]
console.log(circularArraySlice(dayArray, 2, 5));
// ["Wednesday", "Thursday", "Friday", "Saturday"]
This is, I think, what you want. There may well be edge cases so be careful.
Playground link to code
To help answer questions like this, I'd recommend listing a couple of test cases and both provide expected values and the values you're actually seeing.
But I can see a couple of things which may cause problems in the code:
The calculation of d_date is going to return -1 for Sunday, not 6 (like days_list is expecting)
The outer loop (the one setting j) isn't really adding a lot here, because j isn't used inside the loop. Therefore, each iteration of the loop is going to have the same effect.
The inner loop (the one setting i) is only looking for days which appear after day_from in your days_list array. However, as per you example, days from the start of days_list may also match if the day_from value is greater than day_to.
Based on Randy Casburn's (now-deleted) answer, it's possible to solve this using javascript's filter method.
However, you need to be extra careful to handle separate cases for when to_date is before from_date and vice-versa.
For example:
const days_list = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
function getDays(business_hours) {
const days = days_list.filter((_, id) => {
if (business_hours.day_from <= business_hours.day_to) {
return (id >= business_hours.day_from && id <= business_hours.day_to);
} else {
return (id <= business_hours.day_to || id >= business_hours.day_from);
}
})
console.log(business_hours, days);
return days;
}
getDays({ day_from: 2, time_from: "23:00", day_to: 5, time_to: "08:00"});
getDays({ day_to: 2, time_to: "23:00", day_from: 5, time_from: "08:00"});
getDays({ day_from: 3, time_from: "23:00", day_to: 3, time_to: "08:00"});
I need to create an array with the days of the week, and I need to create a function that takes a day and a number n and return the day after the n days (for example getDayAfter('Monday', 4) will return 'Friday').
I've already created my array named day. It has in the array the days of the week.
I'm creating a function called RepeatArray and it's supposed to create a new array.
let day = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
function repeatArray(day, count) {
var ln = day.length;
var b = new Array();
var a = new Array();
for (i = 0; i < count; i++) {
day.push(day[i % ln]);
}
return b;
}
var a = new Array();
a.push("test1");
a.push("test2");
a.push("test3");
var b = repeatArray(a, 13);
console.log(b.length);
I expect the output of a, 13 to be monday, but the output is 0.
Let's walk through what your code currently does, which is often a good way to find any problems.
let day = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
This creates your days of the week array in an outer scope.
function repeatArray(day, count) {
This declares the repeatArray function -- which shadows the "day" variable. Since it has the same name as the "day" array in the global scope, anything inside this function that references "day" will reference this argument and you will have no access to the outer array.
var ln = day.length;
var b = new Array();
var a = new Array();
ln is now the length of the first argument passed to the function. b and a are empty arrays.
for (i = 0; i < count; i++) {
day.push(day[i % ln]);
}
This pushes to the first argument the value in the first argument, at the position i mod ln, for all i from 0 to count. So if the first argument is the array ['a', 'b', 'c'], and count is 6, it would be pushing 'a', 'b', 'c', 'a', 'b', 'c' to the first argument, resulting in the array ['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']. I don't think this is what you want. But let's continue.
return b;
The function returns b, which is still just a new, empty array with nothing in it, since we haven't touched it.
var a = new Array();
a.push("test1");
a.push("test2");
a.push("test3");
This creates an array, a, which ends up being ['test1', 'test2', 'test3'].
var b = repeatArray(a, 13);
console.log(b.length);
In our function call, 'day' will be the array a (so ['test1', 'test2', 'test3']), and count will be 13. Since the function returns an empty array every time, the length will always be 0. The array a, however, will be modified to repeat 'test1', 'test2', 'test3' 13 extra times, making its length 16 (13 plus the initial 3 values).
For your task, you don't need that kind of repetition or to create a new array. You can just use modular math :)
let day = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
function daysAfter(initial, n) {
const initialIndex = day.indexOf(initial);
return day[(initialIndex + n) % day.length];
}
So now daysAfter('wednesday', 13) will equal tuesday as expected :)
I'm not quite sure what you need repeatArray for, but if you are just trying to, as you say, 'create a function that takes a day and a number n and return the day after the n days' this is how I would do it:
const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
function getDayAfter(day, count) {
const i = days.indexOf(day);
return days[(i + count) % days.length];
}
console.log(getDayAfter('tuesday', 2)); // Should output 'thursday'
I do not really understand the question but this should work.
let weekday = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
function getDayAfter(day, count) {
var id = weekday.indexOf(day);
var ln = weekday.length;
var targetday = id + count;
if((id + count) < ln){
return weekday[id + count];
}else{
return weekday[(id + count) % ln];
}
}
var b = getDayAfter("monday", 13);
document.writeln(b);
Here is the solution I came up with:
const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
length = days.length;
function getDayFrom(day, count){
//i % 7 will effectively repeat 0 thru 6 for any value of i
//creating an array of repeated days. The count is the index of the day
//expected
return days[(days.indexOf(day) + count) % length]
//The above is the efficient version of
//Array.from({length: count + 1}, (_, i) => days[i % 7])[count]
}
console.log(getDayFrom('monday', 4))
The output is 'friday'.
I have an array of day nums that I'd like to convert to a prettier string format. The corresponding day names have to be displayed as a range.
Here is an example:
// 0 is Sunday, 6 is Saturday
days = [0,1,2,4,5];
// Should be ["Sunday-Tuesday", "Thursday-Friday"]
days = [0,1,3,5,6]
// Should be ["Friday-Monday", "Wednesday"]
// note that this wraps around!!
I have tried and somewhat succeeded, but it does not wrap around. Also it is ugly, imo.
let result = [];
let build = "";
let previous = -10;
let days = [0,1,3,5,6];
for(let i=0;i < days.length; i++) {
let d = parseInt(days[i]);
if (d !== previous+1 && i > 0) {
build = build.slice(1).split(",");
if (build.length == 1)
build = build[0];
else if (build.length > 0) {
build = build[0] + "-" + build[build.length-1];
}
result.push(build);
build = "";
}
build = build + "," + getDayName(d);
if (i === days.length-1) {
build = build.slice(1).split(",");
if (build.length == 1)
build = build[0];
else if (build.length > 0) {
build = build[0] + "-" + build[build.length-1];
}
result.push(build);
}
previous = d;
}
This will only print out Array [ "Sunday-Monday", "Wednesday", "Friday-Saturday" ].
How can I make the days wrap around? And is there a much cleaner way of doing this?
You can concatenate the first consecutive dates onto the end of the array so that when we run the algorithm to group them, the wrap is automatically handled.
let names = 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(',');
let i = 0;
while (days[i]+1 == days[i+1]) i++;
days = days.concat(days.splice(0, i+1));
let res = [];
let s = days[0];
for (i = 0; i < days.length-1; i++){
if ((days[i]+1)%7 != days[i+1]){
let f = days[i];
res.push(s == f ? names[s] : names[s]+'-'+names[f]);
s = days[i+1];
}
}
f = days[days.length-1];
res.push(s == f ? names[s] : names[s]+'-'+names[f]);
res.unshift(res.pop());
I break the problem down into two steps:
Find consecutive ranges in a sequence of numbers
Convert known consecutive ranges into pretty day ranges
The code I've attached here does this. groupRanges looks at any range, works through it grouping items into consecutive ranges. This means it'll take [0, 1, 2, 4, 5] and convert it into [[0, 1, 2], [4, 5]].
Note that if you gave it [5, 0, 1, 2, 4] it would ignore the fact that 4 and 5 would be consecutive. If you wish to treat that as consecutive you should sort any such array before passing it in.
prettyRange just deals with the three cases of:
Range is empty (return empty string)
Range has one item (return human-readable version of single item)
Range has more than one item (assume range is a consecutive range and make human-readable version by doing -
The groupRanges function uses Array.reduce as (what I think is) a clean solution.
const days1 = [0,1,2,4,5];
const days2 = [0,1,3,5,6];
const days3 = [0,2];
const days4 = [5,1,4];
const days5 = [4];
const daysVariants = [days1, days2, days3, days4, days5];
/**
* #param {number[]} numbers - An array of numbers from 0-6
* #return {number[][]} An array of an array of numbers. Each array of numbers will be consecutive in ascending order
*/
function groupRanges(numbers) {
// Loop over numbers, building an array as we go starting with [].
return numbers.reduce((ranges, next) => {
// It's our first number so just return a single-item range
if (ranges.length <= 0) return [[next]];
const lastRange = last(ranges);
const lastNumber = last(lastRange);
// Next number is consecutive to the last number in the last-seen range, so we should add it to that range
if (next - lastNumber === 1) return [...ranges.slice(0, -1), [...lastRange, next]]
// Next number is _not_ consecutive so we add a new single-item range
return [...ranges, [next]];
}, []);
}
function last(array) {
return array[array.length - 1];
}
daysVariants
.map(groupRanges)
.forEach(i => console.log(i));
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
function prettyRange(range) {
if (range.length === 0) return '';
if (range.length === 1) return DAYS[range[0]];
return DAYS[range[0]] + '-' + DAYS[last(range)];
}
daysVariants
.map(groupRanges)
.forEach(ranges => ranges.map(prettyRange).forEach(i => console.log(i)));
If divide the problem into next three steps, it will become simple.
chunking into consecutive elements, such that [[0, 1], [3], [5, 6]]
deal with wrap around, such that [[5, 6, 0, 1], [3]]
convert to string, such that ['Friday-Monday', 'Wednesday']
const convert = dayNumbers => {
// 1. chunking into consecutive elements, such that [[0, 1], [3], [5, 6]]
let chunks = []
dayNumbers.forEach(num => {
if (chunks.length === 0) chunks.unshift([num])
else if (chunks[0][0] === num - 1) chunks[0].unshift(num)
else chunks.unshift([num])
})
chunks = chunks.map(c => c.reverse()).reverse()
// 2. deal with wrap around, such that [[5, 6, 0, 1], [3]]
let chunksLength = chunks.length
if (chunksLength >= 2) {
let lastChunk = chunks[chunksLength - 1]
if (chunks[0][0] === 0 && lastChunk[lastChunk.length - 1] === 6) {
chunks[0] = lastChunk.concat(chunks[0])
chunks = chunks.splice(0, chunksLength - 1)
}
}
// 3. convert to string, such that ['Friday-Monday', 'Wednesday']
const dayNames = [
'Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'
]
const result = chunks.map(c => {
if (c.length === 1) return dayNames[c[0]]
else return dayNames[c[0]] + '-' + dayNames[c[c.length - 1]]
})
return result
}
console.log(convert([0, 1, 2, 4, 5])) // ['Sunday-Tuesday', 'Thursday-Friday']
console.log(convert([0, 1, 3, 5, 6])) // ['Friday-Monday', 'Wednesday']