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']
Related
I am attempting to add the results of doubling every other number in an array to a new array. However the code only seems to be pushing the first index that is doubled. I assume the problem is with the .push line but I am not sure. Thanks for any help!
Here is the code I have:
const addTwoDigits = n => {
return n % 10 + Math.floor(n / 10);
}
const validateCred = array => {
let sumNums = []
let i = array.length - 2
while (i > 0) {
let doubleNum = (array[i] * 2)
if (doubleNum > 9) {
let addedNum = addTwoDigits(doubleNum)
sumNums.push(addedNum)
} else {
sumNums.push(doubleNum)
}
i--;
return sumNums;
}
}
const testArray = [4, 2, 1, 6, 5, 7, 5]
console.log(validateCred(testArray));
I was hoping the sumNums array would contain every other digit doubled (starting with '' in the test array and moving left) but it only returns [ 5 ].
You returned the value in the loop. When the compiler comes in loop then first time it returns the value and break the loop.
const addTwoDigits = n => {
return n % 10 + Math.floor(n / 10);
}
const validateCred = array => {
let sumNums = []
let i = array.length - 2
while (i > 0) {
let doubleNum = (array[i] * 2)
if (doubleNum > 9) {
let addedNum = addTwoDigits(doubleNum)
sumNums.push(addedNum)
} else {
sumNums.push(doubleNum)
}
i--;
}
return sumNums;
}
const testArray = [4, 2, 1, 6, 5, 7, 5]
console.log(validateCred(testArray));
Your can try this code.
I have a sample array as
const arr=[0,2,4,6]
I want to check if they have a common difference in them, which is 2 and yes in above array.
There could be more efficient way to do this. I tried using a simple for loop as:
const arr = [0,2,4,6];
for(let i=0;i<arr.length-1;i++){
if(arr[i]+2==arr[i+1]){
console.log(true);
}
else{
console.log(false);
}
}
This could give me result as true,true,true and push all values to a new array then check if all are true and finally I would get true
It would be helpful if there is a more efficient way to do this; any help appreciated.
Assuming the array is sorted, you can do it pretty simply with .every().
const arr = [0,2,4,6];
const itvl = arr[1] - arr[0];
const result = arr.slice(0, -1).every((n, i) =>
itvl === arr[i + 1] - n
)
console.log(result);
This starts off by calculating the first interval, then it iterates the array (except the last index) using .every() to test that the next index minus the current one is equal to that pre-calculated index.
You can do this. It's better to stop as soon as you find a false rather than testing all the values.
const arr=[0,2,4,6]
var r = true
for (var i=1; i<arr.length-1 && r; i++) {
r = ((arr[i+1] - arr[i]) == (arr[i] - arr[i-1]))
}
console.log(r)
I assume what you mean by difference is the step value. For that, first find the difference between the first two elements ; then check whether the same difference holds for all consecutive values.
const arr = [0, 2, 4, 6];
let flag = true;
diff = arr[1] - arr[0];
for(let i = 1; i < arr.length-1; i++){
if(arr[i+1] - arr[i] != diff ){
flag = false;
break;
}
}
console.log(flag)
The code assumes the array size is atleast 2. Add an if guard if this is not always guaranteed.
Checks if numbers sum up to desired sum and if rest of numbers have same difference as desired in comments. (This should be a separate question.)
add up until s >= sum. return false if s isn't equal to set sum
if numbers left <= 2 return true
get difference of first 2 numbers
check difference of numbers, if not same difference return false
differences all same, return true
let arr = [0, 1, 3, 4, 6, 8, 10, 12];
const sum = 4
check = arr => {
let s = 0,
idx = arr.findIndex(x => (s+=x) >= sum ) + 1
if(s !== sum) return false
if(arr.length - idx < 3) return true
const diff = arr[idx+1] - arr[idx]
while(++idx<arr.length-1)
if(arr[idx+1] - arr[idx] !== diff) return false
return true
}
arr = [0, 1, 3, 4, 6, 8, 10, 12]
console.log(
check(arr)
)
arr = [0, 1, 3]
console.log(
check(arr)
)
arr = [0, 1, 3, 4, 6, 8, 10, 12, 11]
console.log(
check(arr)
)
I'm working to automate a google sheet to google calendar, but I'm stuck.
I have an array of strings that correspond to hours
ex: time = [8, 9, 10, 2, 3, 4]
and want to an output a string
ex: range = "8-11, 2-5"
I need to write this in google app script, any suggestions?
I'm new to google app script and having a hard time writing the function. My initial process was to convert the array of strings to military time integers, and create two for loops, but I'm sure there is a more efficient way to do this.
Thanks for the help!
This is my current code:
var time = [8, 9, 10, 2, 3, 4]
// if (currentTime == 13) {currentTime -= 12;}
function timeRange(cellInput, hourList) {
var start = parseInt(hourList[0]);
for (var i = 1; i < hourList.length; ++i) {
if (hourList[i] == start + i) {
var end = parseInt(hourList[i]);
} else {
cellInput.setValue(start + " - " + (end + 1));
}
}
}
function soloTime(cellInput, hour) {
//convert hour string to hour
var hour = parseInt(hour)
var start = hour
var end = hour + 1
cellInput.setValue(start + "-" + end);
}
You could check the predecessor and collect the ranges.
var time = [8, 9, 10, 2, 3, 4, 11, 12, 1],
range = time
.reduce((r, t, i, a) => {
if (a[i - 1] % 12 + 1 === t) {
r[r.length - 1][1] = t;
} else {
r.push([t]);
}
return r;
}, [])
.map(a => a.join('-'))
.join(', ');
console.log(range);
Is this what you are looking for?
const timeToRange = (arr) => {
const mins = arr
.filter((x, i, arr) => x !== arr[i - 1] + 1);
const maxs = arr
.filter((x, i, arr) => x !== arr[i + 1] - 1)
.map(x => x + 1);
return mins
.map((x, i) => [x, maxs[i]].join('-'))
.join(', ');
};
console.log(
timeToRange([8, 9, 10, 2, 3, 4])
);
You could keep the start of each range, then iterate until the current value does not fit to the previous, then take the previous and the start to create a range, collect them in an array, and that's it.
const result = [];
let start = time[0];
let previous = time[0];
// Start at the second position, and go over the arrays length by one, so tgat the last range gets added too
for(const value of time.slice(1).concat(Infinity)) {
// if the range isn't continuous (12 to 1 is continuous)
if((value - previous) % 12 !== 1) {
// Add the previous range, start the new range here
result.push(start + "-" + previous);
start = value;
}
previous = value;
}
console.log(result.join(", "));
I am doing some coding practice and found some questions online.
I keep getting 1 integer lower than expected when looking to return the number of consecutive numbers inside an array.
function LongestConsecutive(arr) {
arr.sort((a,b) => {return a-b});
let highest = 0;
let counter = 0;
let prevNum;
arr.forEach((num,index,arr) => {
if (prevNum === undefined) {
prevNum = num
} else {
if (num + 1 == arr[index + 1]) {
counter += 1;
highest = Math.max(highest,counter)
} else {
counter = 0;
}
}
})
return highest;
}
for example, the input [5, 6, 1, 2, 8, 9, 7], should return 5 -- because when sorted, there are 5 consecutive numbers. I keep getting one lower than I should so for this example, I get 4. The only way to get the correct answer is when I return 'highest + 1', which obviously is avoiding the problem.
The first iteration will hit
if (prevNum === undefined) {
prevNum = num;
}
But isn’t that already the first consecutive number? So counter = 1; and highest = 1; should be here.
Next, you reset counter = 0; in an else case. Why? There’s at least one number that is consecutive, so reset it to 1 instead.
Then, you’re not really using prevNum for anything. if (prevNum === undefined) can be replaced by if (index === 1).
You then check if the current number (num) precedes the next number (arr[index + 1]), but you skip this check for the first index. How about checking if the current number succeeds the previous?
This code uses the above changes plus some code quality changes:
function longestConsecutive(arr) { // Non-constructor functions start with a lower-case letter
arr.sort((a, b) => a - b); // Use expression form
let highest = 0;
let counter = 0;
arr.forEach((num, index, arr) => {
if (index === 0) {
highest = 1;
counter = 1;
} else if (num - 1 === arr[index - 1]) { // Merge `else if`, use strict equal
counter += 1;
highest = Math.max(highest, counter);
} else {
counter = 1;
}
});
return highest;
}
Well, by the definition of consecutive, you'll always have 1 consecutive number. So you need to start the counter from 1.
I tried this code (its different than the one posted in the question) which gives the expected result. In addition, if there are two sets of consecutive numbers of same (and largest) length, both are printed,
var arr = [5, 6, 1, 2, 8, 9, 7, 99, 98];
arr.sort((a, b) => a - b);
var prevNum = arr[0];
var consecutiveNumbersArr = [prevNum];
// Map of consecutiveNumbersArr array as key and
// the array length as values
var arrMap = new Map();
for (let i = 1; i < arr.length; i++) {
let num = arr[i];
if (num === prevNum+1) {
prevNum = num;
consecutiveNumbersArr.push(num);
continue;
}
arrMap.set(consecutiveNumbersArr, consecutiveNumbersArr.length);
consecutiveNumbersArr = [];
consecutiveNumbersArr.push(num);
prevNum = num;
}
arrMap.set(consecutiveNumbersArr, consecutiveNumbersArr.length);
// the largest length of all the consecutive numbers array
var largest = 0;
for (let value of arrMap.values()) {
if (value > largest) {
largest = value;
}
}
// print the result - the largest consecutive array
for (let [key, value] of arrMap) {
if (value === largest) {
console.log("RESULT: " + key);
}
}
Can also be achieved with array:reduce
function longestRun(array) {
const { streak } = array
.sort((a, b) => a - b) // sort into ascending order
.reduce(({ count, streak }, current, index, arr) => {
if (current === arr[index - 1] + 1) {
count++; // increment if 1 more than previous
} else {
count = 1; // else reset to 1
}
return {count, streak: Math.max(streak, count)};
}, { count: 0, streak: 0 }); // initial value is 0,0 in case of empty array
return streak;
}
console.log(longestRun([])); // 0
console.log(longestRun([0])); // 1
console.log(longestRun([0, 1])); // 2
console.log(longestRun([0, 1, 0])); // 2
console.log(longestRun([0, 0, 0])); // 1
console.log(longestRun([2, 0, 1, 0, 3, 0])); // 4
If you are able to split arrays into subarrays via a condition, you can do it by splitting the array at non consecutive points.
const arr = [5, 6, 1, 2, 8, 9, 7, 11, 12, 13, 14]
// utility for splitting array at condition points
const splitBy = (arr, cond) => arr.reduce((a, cur, i, src) => {
if(cond(cur, i, src)){
a.push([])
}
a[a.length - 1].push(cur)
return a
}, [])
const consecutives = splitBy(
arr.sort((a, b) => a - b),
(cur, i, src) => cur !== src[i-1] + 1
).sort((a, b) => b.length - a.length)
// largest consecutive list will be the first array
console.log(consecutives[0].length)
I have an array full of numbers. Here's an example:
myArray = [0,1,2,4,5];
I need to find the lowest unused number starting from 1, so in this case it will be 3.
I've been reading up of using indexOf but I'm unsure how to use it for my specific purpose.
Assuming the array isn't sorted, you always start at 0, and taking into account your desire to find a highest number if there isn't one missing:
var k = [6, 0, 1, 2, 4, 5];
k.sort(function(a, b) { return a-b; }); // To sort by numeric
var lowest = -1;
for (i = 0; i < k.length; ++i) {
if (k[i] != i) {
lowest = i;
break;
}
}
if (lowest == -1) {
lowest = k[k.length - 1] + 1;
}
console.log("Lowest = " + lowest);
Logs answer 3. If 3 was also in there, would log 7 since no other number is missing.
If you aren't always starting at zero, use an offset:
var k = [6, 2, 3, 4, 5];
k.sort(function(a, b) { return a-b; }); // To sort by numeric
var offset = k[0];
var lowest = -1;
for (i = 0; i < k.length; ++i) {
if (k[i] != offset) {
lowest = offset;
break;
}
++offset;
}
if (lowest == -1) {
lowest = k[k.length - 1] + 1;
}
console.log("Lowest = " + lowest);
Logs answer 7 since none are missing after 2 which starts the sequence.
This takes a sequence starting from a number (like 1 in your example) and returns the lowest unused number in the sequence.
function lowestUnusedNumber(sequence, startingFrom) {
const arr = sequence.slice(0);
arr.sort((a, b) => a - b);
return arr.reduce((lowest, num, i) => {
const seqIndex = i + startingFrom;
return num !== seqIndex && seqIndex < lowest ? seqIndex : lowest
}, arr.length + startingFrom);
}
Example:
> lowestUnusedNumber([], 1)
1
> lowestUnusedNumber([1,2,4], 1)
3
> lowestUnusedNumber([3], 1)
1
In exchange for readability, it's slightly less optimized than the other example because it loops over all items in the array instead of breaking as soon as it finds the missing item.