Sorting in Javascript - javascript

I'd like to sort by time,day.
Here is my attempt:
var days = new Array();
var days['SU'] = 0;
var days['MO'] = 1;
var days['TU'] = 2;
var days['WE'] = 3;
var days['TH'] = 4;
var days['FR'] = 5;
var days['SA'] = 6;
events.sort(function(a, b)
{
if(a['day'] != b['day'])
{
return (days[a['day']] < days[b['day']]) ? 1 : -1;
}
else if(a['time'] != b['time'])
{
return (a['time'] < a['time']) ? 1 : -1;
}
else
return 0;
);
It's not tested, but am I doing it correct?
(Time asc, days asc) Mon 8am, Tues 8am, Mon 9pm is the order I'm looking for.
Cheers.
events[0]['day'] = 'MO';
events[0]['time'] = 8;
events[1]['day'] = 'MO';
events[1]['time'] = 21;
events[2]['day'] = 'TU';
events[2]['time'] = 8;
My solution which seems to work thanks to #T.J. Crowder
events = new Array();
events[0] = new Array();
events[0]['day'] = 'MO';
events[0]['time'] = 8;
events[1] = new Array();
events[1]['day'] = 'MO';
events[1]['time'] = 21;
events[2] = new Array();
events[2]['day'] = 'TU';
events[2]['time'] = 8;
var days = {
'SU': 0,
'MO': 1,
'TU': 2,
'WE': 3,
'TH': 4,
'FR': 5,
'SA': 6
};
events.sort(function(a, b)
{
if (a.time != b.time)
{
return a.time - b.time;
}
else if (a.day != b.day)
{
return days[a.day] - days[b.day];
}
else
{
return 0;
}
});
Condensed:
events.sort(function(a, b)
{
return a.time != b.time
? a.time - b.time
: days[a.day] - days[b.day];
});

Your fundamental approach is sound. A few notes:
You're not using days as an array, so I wouldn't make it an array. Instead:
var days = {
'SU': 0,
'MO': 1,
'TU': 2,
'WE': 3,
'TH': 4,
'FR': 5,
'SA': 6
};
Also, you don't need those quotes since none of those strings is a keyword, so:
var days = {
SU: 0,
MO: 1,
TU: 2,
WE: 3,
TH: 4,
FR: 5,
SA: 6
};
...but you may choose to keep them as a style thing, or to defend against adding ones that are keywords later.
You don't have to use the bracketed notation to look up a property (a['day']) unless the string you're using for the property name is dynamic or the property name is a reserved word. day is neither, so you can use the simpler dotted notation (a.day).
There is no elseif in JavaScript; use else if.
You can simplify this:
return (days[a['day']] < days[b['day']]) ? 1 : -1;
to
return days[a.day] - days[b.day];
..and you may be able to do something similar with your time values, but I don't know what they are, so... now that you've posted them, I do, and you can.
Strongly recommend always using braces, not just when you "need" them. (None of your three branches actually needs them, but you're only using them on two.)
You've compared a['time'] to a['time] rather than b['time'] when checking for equality.
You haven't ended your function (missing })
Since you can just subtract your time values, you don't need your final equality check.
So:
events.sort(function(a, b)
{
if (a.day != b.day)
{
return days[a.day] - days[b.day];
}
else
{
return a.time - b.time;
}
});
...or you can condense it further:
events.sort(function(a, b)
{
return (a.day != b.day
? days[a.day] - days[b.day]
: a.time - b.time);
});
Live example

Related

Look up tables and integer ranges - javascript

So I am looking to create look up tables. However I am running into a problem with integer ranges instead of just 1, 2, 3, etc. Here is what I have:
var ancient = 1;
var legendary = 19;
var epic = 251;
var rare = 1000;
var uncommon = 25000;
var common = 74629;
var poolTotal = ancient + legendary + epic + rare + uncommon + common;
var pool = general.rand(1, poolTotal);
var lootPool = {
1: function () {
return console.log("Ancient");
},
2-19: function () {
}
};
Of course I know 2-19 isn't going to work, but I've tried other things like [2-19] etc etc.
Okay, so more information:
When I call: lootPool[pool](); It will select a integer between 1 and poolTotal Depending on if it is 1 it will log it in the console as ancient. If it hits in the range of 2 through 19 it would be legendary. So on and so forth following my numbers.
EDIT: I am well aware I can easily do this with a switch, but I would like to try it this way.
Rather than making a huge lookup table (which is quite possible, but very inelegant), I'd suggest making a (small) object, choosing a random number, and then finding the first entry in the object whose value is greater than the random number:
// baseLootWeight: weights are proportional to each other
const baseLootWeight = {
ancient: 1,
legendary: 19,
epic: 251,
rare: 1000,
uncommon: 25000,
common: 74629,
};
let totalWeightSoFar = 0;
// lootWeight: weights are proportional to the total weight
const lootWeight = Object.entries(baseLootWeight).map(([rarity, weight]) => {
totalWeightSoFar += weight;
return { rarity, weight: totalWeightSoFar };
});
console.log(lootWeight);
const randomType = () => {
const rand = Math.floor(Math.random() * totalWeightSoFar);
return lootWeight
.find(({ rarity, weight }) => weight >= rand)
.rarity;
};
for (let i = 0; i < 10; i++) console.log(randomType());
Its not a lookup, but this might help you.
let loots = {
"Ancient": 1,
"Epic": 251,
"Legendary": 19
};
//We need loots sorted by value of lootType
function prepareSteps(loots) {
let steps = Object.entries(loots).map((val) => {return {"lootType": val[0], "lootVal": val[1]}});
steps.sort((a, b) => a.lootVal > b.lootVal);
return steps;
}
function getMyLoot(steps, val) {
let myLootRange;
for (var i = 0; i < steps.length; i++) {
if((i === 0 && val < steps[0].lootVal) || val === steps[i].lootVal) {
myLootRange = steps[i];
break;
}
else if( i + 1 < steps.length && val > steps[i].lootVal && val < steps[i + 1].lootVal) {
myLootRange = steps[i + 1];
break;
}
}
myLootRange && myLootRange['lootType'] ? console.log(myLootRange['lootType']) : console.log('Off Upper Limit!');
}
let steps = prepareSteps(loots);
let pool = 0;
getMyLoot(steps, pool);

Javascript: Find out of sequence dates

Consider this nested array of dates and names:
var fDates = [
['2015-02-03', 'name1'],
['2015-02-04', 'nameg'],
['2015-02-04', 'name5'],
['2015-02-05', 'nameh'],
['1929-03-12', 'name4'],
['2023-07-01', 'name7'],
['2015-02-07', 'name0'],
['2015-02-08', 'nameh'],
['2015-02-15', 'namex'],
['2015-02-09', 'namew'],
['1980-12-23', 'name2'],
['2015-02-12', 'namen'],
['2015-02-13', 'named'],
]
How can I identify those dates that are out of sequence. I don't care if dates repeat, or skip, I just need the ones out of order. Ie, I should get back:
results = [
['1929-03-12', 'name4'],
['2023-07-01', 'name7'],
['2015-02-15', 'namex'],
['1980-12-23', 'name2'],
]
('Namex' is less obvious, but it's not in the general order of the list.)
This appears to be a variation on the Longest Increase Subsequence (LIS) problem, with the caveat that there may be repeated dates in the sequence but shouldn't ever step backward.
Use case: I have sorted and dated records and need to find the ones where the dates are "suspicious" -- perhaps input error -- to flag for checking.
NB1: I am using straight Javascript and NOT a framework. (I am in node, but am looking for a package-free solution so I can understand what's going on...)
Here's an adaptation of Rosetta Code LIS to take a custom getElement and compare functions. We can refine the comparison and element-get functions based on your specific needs.
function f(arr, getElement, compare){
function findIndex(input){
var len = input.length;
var maxSeqEndingHere = new Array(len).fill(1)
for(var i=0; i<len; i++)
for(var j=i-1;j>=0;j--)
if(compare(getElement(input, i), getElement(input, j)) && maxSeqEndingHere[j] >= maxSeqEndingHere[i])
maxSeqEndingHere[i] = maxSeqEndingHere[j]+1;
return maxSeqEndingHere;
}
function findSequence(input, result){
var maxValue = Math.max.apply(null, result);
var maxIndex = result.indexOf(Math.max.apply(Math, result));
var output = new Set();
output.add(maxIndex);
for(var i = maxIndex ; i >= 0; i--){
if(maxValue==0)break;
if(compare(getElement(input, maxIndex), getElement(input, i)) && result[i] == maxValue-1){
output.add(i);
maxValue--;
}
}
return output;
}
var result = findIndex(arr);
var final = findSequence(arr, result)
return arr.filter((e, i) => !final.has(i));
}
var fDates = [
['2015-02-03', 'name1'],
['2015-02-04', 'nameg'],
['2015-02-04', 'name5'],
['2015-02-05', 'nameh'],
['1929-03-12', 'name4'],
['2023-07-01', 'name7'],
['2015-02-07', 'name0'],
['2015-02-08', 'nameh'],
['2015-02-15', 'namex'],
['2015-02-09', 'namew'],
['1980-12-23', 'name2'],
['2015-02-12', 'namen'],
['2015-02-13', 'named'],
];
console.log(f(fDates, (arr, i) => arr[i][0], (a,b) => a >= b));
This solution tries to get all valid sequences and returns the longes sequences for filtering the parts out.
It works by iterating the given array and checks if the values could build a sequence. If a value is given, which part result has a valid predecessor, the array is appended with this value. If not a backtracking is made and a sequence is searched with a valid predecessor.
act. array
value 7 3 4 4 5 1 23 7 comment
----- ------------------------ ---------------------------
7 7 add array with single value
3 7 keep
3 add array with single value
4 7 keep
3 4 add value to array
4 7 keep
3 4 4 add value to array
5 7 keep
3 4 4 5 add value to array
1 7 keep
3 4 4 5 keep
1 add array with single value
23 7 23 add value to array
3 4 4 5 23 add value to array
1 23 add value to array
7 7 23 keep
7 7 fork above, filter for smaller or equal and add value
3 4 4 5 23 keep
3 4 4 5 7 fork above, filter for smaller or equal and add value
1 23 keep
1 7 fork above, filter for smaller or equal and add value
function longestSequences(array, getValue = v => v) {
return array
.reduce(function (sub, value) {
var single = true;
sub.forEach(function (s) {
var temp;
if (getValue(s[s.length - 1]) <= getValue(value)) {
s.push(value);
single = false;
return;
}
// backtracking
temp = s.reduceRight(function (r, v) {
if (getValue(v) <= getValue(r[0])) {
r.unshift(v);
single = false;
}
return r;
}, [value]);
if (temp.length !== 1 && !sub.some(s => s.length === temp.length && s.every((v, i) => getValue(v) === getValue(temp[i])))) {
sub.push(temp);
}
});
if (single) {
sub.push([value]);
}
return sub;
}, [])
.reduce(function (r, a) {
if (!r || r[0].length < a.length) {
return [a];
}
if (r[0].length === a.length) {
r.push(a);
}
return r;
}, undefined);
}
function notInSequence(array, getValue = v => v) {
var longest = longestSequences(array, getValue);
return array.filter((i => a => a !== longest[0][i] || !++i)(0));
}
var array = [7, 3, 4, 4, 5, 1, 23, 7, 8, 15, 9, 2, 12, 13],
fDates = [['2015-02-03', 'name1'], ['2015-02-04', 'nameg'], ['2015-02-04', 'name5'], ['2015-02-05', 'nameh'], ['1929-03-12', 'name4'], ['2023-07-01', 'name7'], ['2015-02-07', 'name0'], ['2015-02-08', 'nameh'], ['2015-02-15', 'namex'], ['2015-02-09', 'namew'], ['1980-12-23', 'name2'], ['2015-02-12', 'namen'], ['2015-02-13', 'named']],
usuallyFailingButNotHere = [['2015-01-01'], ['2014-01-01'], ['2015-01-02'], ['2014-01-02'], ['2015-01-03'], ['2014-01-03'], ['2014-01-04'], ['2015-01-04'], ['2014-01-05'], ['2014-01-06'], ['2014-01-07'], ['2014-01-08'], ['2014-01-09'], ['2014-01-10'], ['2014-01-11']],
test2 = [['1975-01-01'], ['2015-02-03'], ['2015-02-04'], ['2015-02-04'], ['2015-02-05'], ['1929-03-12'], ['2023-07-01'], ['2015-02-07'], ['2015-02-08']];
console.log(longestSequences(array));
console.log(notInSequence(array));
console.log(notInSequence(fDates, a => a[0]));
console.log(longestSequences(usuallyFailingButNotHere, a => a[0]));
console.log(notInSequence(usuallyFailingButNotHere, a => a[0]));
console.log(longestSequences(test2, a => a[0]));
console.log(notInSequence(test2, a => a[0]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
This solution uses the function reduce and keeps the previously accepted date to make the necessary comparisons.
var fDates = [['2015-02-03', 'name1'], ['2015-02-04', 'nameg'], ['2015-02-04', 'name5'], ['2015-02-05', 'nameh'], ['1929-03-12', 'name4'], ['2023-07-01', 'name7'], ['2015-02-07', 'name0'], ['2015-02-08', 'nameh'], ['2015-02-15', 'namex'], ['2015-02-09', 'namew'], ['1980-12-23', 'name2'], ['2015-02-12', 'namen'], ['2015-02-13', 'named']],
results = fDates.reduce((acc, c, i, arr) => {
/*
* This function finds a potential valid sequence.
* Basically, will check if any next valid sequence is
* ahead from the passed controlDate.
*/
function sequenceAhead(controlDate) {
for (var j = i + 1; j < arr.length; j++) {
let [dt] = arr[j];
//The controlDate is invalid because at least a forward date is in conflict with its sequence.
if (dt > acc.previous && dt < controlDate) return true;
}
//The controlDate is valid because forward dates don't conflict with its sequence.
return false;
}
let [date] = c; //Current date in this iteration.
if (i > 0) { // If this is not the first iteration
if (date === acc.previous) return acc; // Same as previous date are skipped.
// If the current date is lesser than previous then is out of sequence.
// Or if there is at least valid sequence ahead.
if (date < acc.previous || sequenceAhead(date)) acc.results.push(c);
else acc.previous = date; // Else, this current date is in sequence.
}
else acc.previous = date; // Else, set the first date.
return acc;
}, { 'results': [] }).results;
console.log(results);
.as-console-wrapper { max-height: 100% !important; top: 0; }
All of previous answers focus on JavaScript and maybe they won't work
correctly. So I decided to add new answer that focused on
Algorithm.
As #Trees4theForest mentioned in his question and comments, he is looking for a solution for Longest Increase Subsequence and out of order dates are dates that aren't in Longest Increase Subsequence (LIS) set.
I used this method like below. In algorithm's point of view, it's true.
function longestIncreasingSequence(arr, strict) {
var index = 0,
indexWalker,
longestIncreasingSequence,
i,
il,
j;
// start by putting a reference to the first entry of the array in the sequence
indexWalker = [index];
// Then walk through the array using the following methodolgy to find the index of the final term in the longestIncreasing and
// a sequence (which may need altering later) which probably, roughly increases towards it - http://en.wikipedia.org/wiki/Longest_increasing_subsequence#Efficient_algorithms
for (i = 1, il = arr.length; i < il; i++) {
if (arr[i] < arr[indexWalker[index]]) {
// if the value is smaller than the last value referenced in the walker put it in place of the first item larger than it in the walker
for (j = 0; j <= index; j++) {
// As well as being smaller than the stored value we must either
// - be checking against the first entry
// - not be in strict mode, so equality is ok
// - be larger than the previous entry
if (arr[i] < arr[indexWalker[j]] && (!strict || !j || arr[i] > arr[indexWalker[j - 1]])) {
indexWalker[j] = i;
break;
}
}
// If the value is greater than [or equal when not in strict mode) as the last in the walker append to the walker
} else if (arr[i] > arr[indexWalker[index]] || (arr[i] === arr[indexWalker[index]] && !strict)) {
indexWalker[++index] = i;
}
}
// Create an empty array to store the sequence and write the final term in the sequence to it
longestIncreasingSequence = new Array(index + 1);
longestIncreasingSequence[index] = arr[indexWalker[index]];
// Work backwards through the provisional indexes stored in indexWalker checking for consistency
for (i = index - 1; i >= 0; i--) {
// If the index stored is smaller than the last one it's valid to use its corresponding value in the sequence... so we do
if (indexWalker[i] < indexWalker[i + 1]) {
longestIncreasingSequence[i] = arr[indexWalker[i]];
// Otherwise we need to work backwards from the last entry in the sequence and find a value smaller than the last entry
// but bigger than the value at i (this must be possible because of the way we constructed the indexWalker array)
} else {
for (j = indexWalker[i + 1] - 1; j >= 0; j--) {
if ((strict && arr[j] > arr[indexWalker[i]] && arr[j] < arr[indexWalker[i + 1]]) ||
(!strict && arr[j] >= arr[indexWalker[i]] && arr[j] <= arr[indexWalker[i + 1]])) {
longestIncreasingSequence[i] = arr[j];
indexWalker[i] = j;
break;
}
}
}
}
return longestIncreasingSequence;
}
With method above, we can find dates that is out of order like below:
// Finding Longest Increase Subsequence (LIS) set
var _longestIncreasingSequence = longestIncreasingSequence(fDates.map(([date]) => date));
// Out of order dates
var result = fDates.filter(([date]) => !_longestIncreasingSequence.includes(date));
Online demo(jsFiddle)
here is a simple self- explanatory solution. hope it will help you.
const findOutOfSequenceDates = items => {
items = items.map(d => d);
const sequence = [], outOfsequence = [];
sequence.push(items.shift());
const last = ind => sequence[sequence.length - ind][0];
items.forEach(item => {
const current = new Date(item[0]);
if (current >= new Date(last(1))) {
sequence.push(item);
} else if (current >= new Date(last(2))) {
outOfsequence.push(sequence.pop());
sequence.push(item);
} else {
outOfsequence.push(item);
}
});
return outOfsequence;
};
var fDates = [
['2015-02-03', 'name1'],
['2015-02-04', 'nameg'],
['2015-02-04', 'name5'],
['2015-02-05', 'nameh'],
['1929-03-12', 'name4'],
['2023-07-01', 'name7'],
['2015-02-07', 'name0'],
['2015-02-08', 'nameh'],
['2015-02-15', 'namex'],
['2015-02-09', 'namew'],
['1980-12-23', 'name2'],
['2015-02-12', 'namen'],
['2015-02-13', 'named'],
];
console.log(findOutOfSequenceDates(fDates));
Use the Javascript Date type. Compare with those objects. Very simplistically,
date1 = new Date(fDates[i, 0])
date2 = new Date(fDates[i+1, 0])
if (date2 < date1) { // or whatever comparison you want ...
// flag / print / alert the date
}
To clarify, This merely finds items out of sequence. You can do that with strings, as Jaromanda X pointed out. However, you use the phrase "way out of line"; whatever this means for you, Date should give you the ability to determine and test for it. For instance, is '2023-07-01' unacceptable because it's 8 years away, or simply because it's out of order with the 2015 dates? You might want some comparison to a simpler time span, such as one month, where your comparison will looks something like
if (date2-date1 > one_month)
Summary of your question
If I have understood your question correctly, you are trying to identify array entries that do not follow a chronological order based on the time/date property value.
Solution
Convert the date string / time into a UNIX time stamp (number of seconds lapsed since 01/jan/1970 at 00:00:00)
Using a loop, we can store the value against a previous reading per itenary, if the value is negative, this would indicate an error in the date lapse, if the value is positive, it would indicate the order is valid
When negative, we can create an array to denote the position of the reference array and its values allowing you to go back to the original array and review the data.
Example Code
var arrData = [
{date: '2015-02-03', value:'name1'},
{date: '2015-02-04', value:'nameg'},
{date: '2015-02-04', value:'name5'},
{date: '2015-02-05', value:'nameh'},
{date: '1929-03-12', value:'name4'},
{date: '2023-07-01', value:'name7'},
{date: '2015-02-07', value:'name0'},
{date: '2015-02-08', value:'nameh'},
{date: '2015-02-15', value:'namex'},
{date: '2015-02-09', value:'namew'},
{date: '1980-12-23', value:'name2'},
{date: '2015-02-12', value:'namen'},
{date: '2015-02-13', value:'named'}
];
var arrSeqErrors = [];
function funTestDates(){
var intLastValue = 0, intUnixDate =0;
for (x = 0; x <= arrData.length-1; x++){
intUnixDate = Date.parse(arrData[x].date)/1000;
var intResult = intUnixDate - intLastValue;
if (intResult < 0){
console.log("initeneration: " + x + " is out of sequence");
arrSeqErrors.push (arrData[x]);
}
intLastValue = intResult;
}
console.log("Items out of sequence are:");
console.log(arrSeqErrors);
}
funTestDates();

Javascript - quick way to check if 10 variables are between 0 and 1?

I know there's probably an easy loop for this, but can't think of it.
I have 10 scores, and I need to validate them by making sure they are between 0 and 1 (plenty of decimals).
The input is pretty loose, so blank, null, alphanumeric values can be in there.
Right now I simply have
if (!(score1>=0 && score1<=1)){var result="error"} else
if (!(score2>=0 && score2<=1)){var result="error"} else
if (!(score3>=0 && score3<=1)){var result="error"} ...
Maybe not the most elegant formatting but -- there's got to be a way to loop through this, right?
Just use every MDN, and place your numbers in an array.
var score1 = 0.89;
var score2 = 0.75;
var score3 = 0.64;
var booleanResult = [score1,score2,score3].every(s => s >= 0 && s<= 1);
console.log(booleanResult);
This answer uses an arrow function:
Alternatively, this is an example of using every with a classic function callback
var score1 = 0.89;
var score2 = 0.75;
var score3 = 0.64;
var booleanResult = [score1,score2,score3].every(function(s){ return s >= 0 && s<= 1 });
console.log(booleanResult);
you could try something like this
var array = [var1, var2, varn ...];
for (let arr of array) {
if (typeof arr === 'number')
if (arr >= your condition)
... the rest of your code here
}
You can just create an array var numbersArray = [var1, var2, var3 ...] iterate through the array and check the if, you can create a "flag" variable with a boolean and if any of the numbers result in error then change the flag value and break the for...
That's it, pretty straightforward.
You can do it this way:
for (i = 1; i <= 10; i++)
{
if (!(window["score"+i.toString()]>=0 && window["score"+i.toString()]<=1)){var result="error"}
}
Here is a fiddle to prove the concept: https://jsfiddle.net/gL902rtu/1/
And as mentionned by #Rick Hitchcock, the score variable has to be global (see the fiddle for example)
Proof of concept:
score1 = 0.5;
score2 = 0.1;
score3 = 0.5;
score4 = 0.8;
score5 = 0.9;
score6 = 0.4;
score7 = 0.10;
score8 = 0.4;
score9 = 0.5;
score10 = 0.8;
result = "noerror";
for (i = 1; i <= 10; i++){
if (!(window["score"+i.toString()]>=0 && window["score"+i.toString()]<=1)){
result="error"
}
}
console.log(result);
Note that this would work with your code but the easiest way would be for sure to store your score in a array and loop trough it, it's pretty much what arrays are for.
You can have more information about array over here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
I would use a function for this, where the first argument is minimum, second is maximum, then the rest are numbers to check, then using .filter() to find invalid numbers:
function CheckRange(min, max) {
if (arguments.length > 2) {
var args = Array.prototype.slice.call(arguments);
return args.slice(2).filter(function(x) {
return x < min || x > max;
}).length == 0;
}
return true;
}
console.log(CheckRange(0, 1, 0.25, '', null, 0.7, 0.12, 0.15));
In the above code empty or null are treated as valid, easy enough to disallow them if needed.
const scores = [0.1, 0.2, 0.3, 0.4, 1, 2, 3, 4, 5, 6];
scores.forEach(function(score){
// http://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
let isFloat = ( Number(score) === score && score % 1 !== 0 );
if (isFloat && (score > 0 && score < 1))
console.log("It's correct!");
});
Putting your scores into an array would be the best starting point. You can do this easily like:
var allScores = [score1, score2, score3];
Once you have an array, if you are targeting a platfrom with ES5 support check here then you can use the filter function of an array:
var errorScores = allScores.filter(function(s) {
return !(parseInt(s) >= 0 && parseInt(s) <= 1)
});
if (errorScores.length > 0) {
// Do some error handling.
}
Here is an example of this on code pen
Alternatively, if you can't use filter then you can do a loop like this:
for (var i = 0; i < allScores.length; i++) {
var score = allScores[i];
if (!(parseInt(score) >= 0 && parseInt(score) <= 1)) {
// Do some error handling.
document.write('something went wrong!');
}
}
Here is an example of this on code pen
Note the use of parseInt above to handle, null, '' and other text values. Invalid numbers will be parsed to NaN which will fail to meet the condition.
If you are using lodash, you can use
_.pluck(window, function(key, value) {}) and then check if key contains variable name and value is less than 10.
You can use
var i;
for (i = 0; i <= 10; i=i) { // i=i means do nothing
i++;
if( eval("score" + i + ">=0 && score" + i + "<=1") ) {
// do something
}
}
However, you should use some sort of type-checking; eval is considered unsafe in certain contexts.

JavaScript check if time ranges overlap

I have e.g. an array with 2 objects (myObject1 and myObject2 like ).
Now when I add an third object I will check if time range overlaps.
Actually I don't know how I can do this in a performant way.
var myObjectArray = [];
var myObject1 = {};
myObject1.startTime = '08:00';
myObject1.endTime = '12:30';
...
var myObject2 = {};
myObject2.startTime = '11:20';
myObject2.endTime = '18:30';
...
myObjectArray.push(myObject1);
myObjectArray.push(myObject2);
Let assume we have some intervals
const INTERVALS = [
['14:00', '15:00'],
['08:00', '12:30'],
['12:35', '12:36'],
['13:35', '13:50'],
];
If we want to add new interval to this list we should check if new interval is not overlapping with some of them.
You can loop trough intervals and check if the new one is overlapping with others. Note that when comparing intervals you do not need Date object if you are sure it is the same day as you can convert time to number:
function convertTimeToNumber(time) {
const hours = Number(time.split(':')[0]);
const minutes = Number(time.split(':')[1]) / 60;
return hours + minutes;
}
There are two cases where intervals are NOT overlapping:
Before (a < c && a < d) && (b < c && b <d):
a b
|----------|
c d
|----------|
After where (a > c && a > d) && (b > c && b > d):
a b
|----------|
c d
|----------|
Because always c < d, it is enough to say that condition for NOT overlapping intervals is (a < c && b < c) || (a > d && b > d) and because always a < b, it is enough to say that this condition is equivalent to:
b < c || a > d
Negation of this condition should give us a condition for overlapping intervals. Base on De Morgan's laws it is:
b >= c && a <= d
Note that in both cases, intervals can not "touch" each other which means 5:00-8:00 and 8:00-9:00 will overlap. If you want to allow it the condition should be:
b > c && a < d
There are at least 5 situation of overlapping intervals to consider:
a b
|----------|
c d
|----------|
a b
|----------|
c d
|----------|
a b
|----------|
c d
|--------------------|
a b
|--------------------|
c d
|----------|
a b
|----------|
c d
|----------|
Full code with extra add and sort intervals functions is below:
const INTERVALS = [
['14:00', '15:00'],
['08:00', '12:30'],
['12:35', '12:36'],
['13:35', '13:50'],
];
function convertTimeToNumber(time) {
const hours = Number(time.split(':')[0]);
const minutes = Number(time.split(':')[1]) / 60;
return hours + minutes;
}
// assuming current intervals do not overlap
function sortIntervals(intervals) {
return intervals.sort((intA, intB) => {
const startA = convertTimeToNumber(intA[0]);
const endA = convertTimeToNumber(intA[1]);
const startB = convertTimeToNumber(intB[0]);
const endB = convertTimeToNumber(intB[1]);
if (startA > endB) {
return 1
}
if (startB > endA) {
return -1
}
return 0;
})
}
function isOverlapping(intervals, newInterval) {
const a = convertTimeToNumber(newInterval[0]);
const b = convertTimeToNumber(newInterval[1]);
for (const interval of intervals) {
const c = convertTimeToNumber(interval[0]);
const d = convertTimeToNumber(interval[1]);
if (a < d && b > c) {
console.log('This one overlap: ', newInterval);
console.log('with interval: ', interval);
console.log('----');
return true;
}
}
return false;
}
function isGoodInterval(interval) {
let good = false;
if (interval.length === 2) {
// If you want you can also do extra check if this is the same day
const start = convertTimeToNumber(interval[0]);
const end = convertTimeToNumber(interval[1]);
if (start < end) {
good = true;
}
}
return good;
}
function addInterval(interval) {
if (!isGoodInterval(interval)) {
console.log('This is not an interval');
return;
}
if (!isOverlapping(INTERVALS, interval)) {
INTERVALS.push(interval);
// you may also want to keep those intervals sorted
const sortedIntervals = sortIntervals(INTERVALS);
console.log('Sorted intervals', sortedIntervals);
}
}
// --------------------------------------
const goodIntervals = [
['05:31', '06:32'],
['16:00', '17:00'],
['12:31', '12:34']
];
let goodCount = 0;
for (const goodInterval of goodIntervals) {
if (!isOverlapping(INTERVALS, goodInterval)) {
goodCount += 1
}
}
console.log('Check good intervals: ', goodCount === goodIntervals.length);
// --------------------------------------
const ovelappingIntervals = [
['09:30', '12:40'],
['05:36', '08:50'],
['13:36', '13:37'],
['06:00', '20:00'],
['14:00', '15:00']
]
let badCount = 0;
for (const badInterval of ovelappingIntervals) {
if (isOverlapping(INTERVALS, badInterval)) {
badCount += 1
}
}
console.log('Check bad intervals: ', badCount === ovelappingIntervals.length);
// --------------------------------------
addInterval(goodIntervals[0])
You can try something like this:
var timeList = [];
function addTime() {
var startTime = document.getElementById("startTime").value;
var endTime = document.getElementById("endTime").value;
if (validate(startTime, endTime)){
timeList.push({
startTime: startTime,
endTime: endTime
});
print(timeList);
document.getElementById("error").innerHTML = "";
}
else
document.getElementById("error").innerHTML = "Please select valid time";
}
function validate(sTime, eTime) {
if (+getDate(sTime) < +getDate(eTime)) {
var len = timeList.length;
return len>0?(+getDate(timeList[len - 1].endTime) < +getDate(sTime) ):true;
} else {
return false;
}
}
function getDate(time) {
var today = new Date();
var _t = time.split(":");
today.setHours(_t[0], _t[1], 0, 0);
return today;
}
function print(data){
document.getElementById("content").innerHTML = "<pre>" + JSON.stringify(data, 0, 4) + "</pre>";
}
<input type="text" id="startTime" />
<input type="text" id="endTime" />
<button onclick="addTime()">Add Time</button>
<p id="error"></p>
<div id="content"></div>
Use moment-js with moment-range (broken reference)
Tested example:
const range1 = moment.range(a, c);
const range2 = moment.range(b, d);
range1.overlaps(range2); // true
See more examples in https://github.com/rotaready/moment-range#overlaps
Note, for the above code to work maybe you first do:
<script src="moment.js"></script>
<script src="moment-range.js"></script>
window['moment-range'].extendMoment(moment);
HTML code
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/2.2.0/moment-range.min.js"></script>
JavaScript code
var range = moment.range(new Date(year, month, day, hours, minutes), new Date(year, month, day, hours, minutes));
var range2 = moment.range(new Date(year, month, day, hours, minutes), new Date(year, month, day, hours, minutes));
range.overlaps(range2); // true or flase
Pretty neat solution and momentjs comes with tons of date and time utilities.
Use JavaScript Date() object to store time and then compare them if ending time of object1 is greater than starting time of object2 then they are overlapping.
You can compare them using > operator.
date1.getTime() > date2.getTime()
Demonstration given here
Usage of Date object
To determine whether the time range overlaps other time ranges you can utilize both moment.js and moment-range libraries.
First install moment-js and moment-range
Given you have an INTERVALS array that contains example objects:
const INTERVALS = [
{ START: 0, END: 10 },
{ START: 12, END: 30 },
...
]
You can use a function below:
const validateIntervalOverlaps = () => {
if (INTERVAL_START && INTERVAL__END) {
const timeInterval = moment.range(moment(INTERVAL_START), moment(INTERVAL_ENDS))
const overlappingInterval = INTERVALS.find(intervalItem => {
const interval = moment.range(moment(intervalItem.START), moment(intervalItem.END))
return timeInterval.overlaps(interval)
})
return overlappingInterval
}
}
Next, you can do what you need to do with overlappingInterval :) F.e. determine if it exists or use it in any other way. Good luck!
Here's something that might work.
// check if time overlaps with existing times
for (var j = 0; j < times.length; j++) {
let existing_start_time = moment(this.parseDateTime(this.times[j].start_time)).format();
let existing_end_time = moment(this.parseDateTime(this.times[j].end_time)).format();
// check if start time is between start and end time of other times
if (moment(start_time).isBetween(existing_start_time, existing_end_time)) {
times[i].error = 'Time overlaps with another time';
return false;
}
// check if end time is between start and end time of other times
if (moment(end_time).isBetween(existing_start_time, existing_end_time)) {
times[i].error = 'Time overlaps with another time';
return false;
}
}
https://momentjs.com/
You can check if there is an overlap by trying to merge a time range to the existing time ranges, if the total count of time ranges decrease after merge, then there is an overlap.
I found following articles which might help on handle merging ranges
Merge arrays with overlapping values
merge-ranges

Javascript sort on on part of string

I have an array of strings that consist of an optional two-letter string signifying spring or fall, followed by a four-digit year, i.e. as one of the following examples:
var example_data = ["HT2014", "VT2013", "2017"];
I'd like to sort this array so that it is primarily sorted on the year (i.e. the four digits, as numbers) and then (if the years are equal) it is sorted so that VT is first, HT is in the middle and entries that do not specify spring or fall are last.
If I've understood the JavaScript sort() function correctly, I should be able to implement a sortFunction that tells me which of two objects should be first, and then just make a call to data.sort(sortFunction).
I've also started working on such a sortFunction, and come up with the following:
function specialSort(a,b) {
var as = a.split("T");
var bs = b.split("T");
if (as[1] != bs[1]) {
return as[1] - bs[1];
} else {
// The year is equal.
// How do I sort on term?
}
}
As the comments signify, I have no clue on what to do to get the sorting on "HT", "VT" and "" correct (except maybe a ridiculous series of nested ifs...). (Also, I know the above code will fail for the third item in the example data, since "2017.split("T") will only have 1 element. I'll deal with that...)
Is this a good approach? If yes - how do I complete the function to do what I want? If no - what should I do instead?
It could be shorter, but this approach calculates a sorting key first, which is then used to sort the array.
Generating the sorting key is very explicit and easy to understand, which always helps me when creating a sort algorithm.
// sorting key = <year> + ('A' | 'B' | 'C')
function getitemkey(item)
{
var parts = item.match(/^(HT|VT)?(\d{4})$/);
switch (parts[1]) {
case 'VT': return parts[2] + 'A'; // VT goes first
case 'HT': return parts[2] + 'B'; // HT is second
}
return parts[2] + 'C'; // no prefix goes last
}
function cmp(a, b)
{
var ka = getitemkey(a),
kb = getitemkey(b);
// simple key comparison
if (ka > kb) {
return 1;
} else if (ka < kb) {
return -1;
}
return 0;
}
["HT2014", "VT2013", "2017", 'HT2013', '2013'].sort(cmp);
I'd use a regular expression with captures and compare on the parts
function compare(a, b) {
var re = /([HV]T)?(\d\d\d\d)/;
var ma = re.exec(a);
var mb = re.exec(b);
// compare the years
if (ma[2] < mb[2])
return -1;
if (ma[2] > mb[2])
return 1;
// years are equal, now compare the prefixes
if (ma[1] == mb[1])
return 0;
if (ma[1] == 'VT')
return -1;
if (mb[1] == 'VT')
return 1;
if (ma[1] == 'HT')
return -1;
return 1;
}
I'll deal with that...
You can do that by getting the last item from the array, instead of the second:
var lastCmp = as.pop() - bs.pop();
if (lastCmp) // != 0
return lastCmp;
else
// compare on as[0] / bs[0], though they might be undefined now
how do I complete the function to do what I want?
You will need a comparison index table. Similiar to #Jack's switch statement, it allows you to declare custom orderings:
var orderingTable = {
"V": 1,
"H": 2
// …
},
def = 3;
var aindex = orderingTable[ as[0] ] || def, // by as[0]
bindex = orderingTable[ bs[0] ] || def; // by bs[0]
return aindex - bindex;
If you don't want a table like this, you can use an array as well:
var ordering = ["V", "H" /*…*/];
var *index = ordering.indexOf(*key)+1 || ordering.length+1;
I took the liberty of using underscore:
var example_data = ["2002","HT2014", "VT2013", "2017", "VT2002", "HT2013"];
var split = _.groupBy(example_data, function(val){ return val.indexOf('T') === -1});
var justYears = split[true].sort();
var yearAndTerm = split[false].sort(function(a,b){
var regex = /([HV])T(\d\d\d\d)/;
var left = regex.exec(a);
var right = regex.exec(b);
return left[2].localeCompare(right[2]) || right[1].localeCompare(left[1]);
});
var sorted = yearAndTerm.concat(justYears);
console.log(sorted);
Here is the fiddle: http://jsfiddle.net/8KHGu/ :)

Categories

Resources