Checking for time conflicts, want to show just 1 warning - javascript

I have a form where the user can add X number of rows, with each row having a start time and end time input.
The rows can be added as the user likes, and the times do not have to be entered sequentially, but must not conflict when submitting the form.
So far, I am able to check for conflicts, using a couple of for loops and checking each start and end time against the rest.
The problem I am facing, is that obviously, if row 1 and 2 conflict, my code is logging two conflicts (logically correct!)
I only want to show the first conflict, as once this is resolved, naturally, the second conflict is.
My code for far:
$('form').submit(function(event){
event.preventDefault();
var errors = [];
var data = serializedToObject($(this).serializeArray());
for(var i = data.row.length; i--;) {
for(var s = data.start.length; s--;) {
if(s != i) {
if(data.start[i] < data.end[s] && data.start[s] < data.end[i]) {
errors.push('Conflict between ' + data.row[i] + ' and ' + data.row[s]);
}
}
}
}
if(errors.length === 0) {
this.submit();
} else {
console.log(errors);
}
});
(serializedToObject simply converts the form data to an object)
So how do I have my code only push 1 of the conflicts to the array?
I tried adding the row ID to an object and pushing that to the array, but it wouldn't log additional conflicts later down the line e.g. Row 1 conflicts with 2 and 4, the conflicts between 1 and 4 would not be mentioned, as row 1 is already in the array.

I have an answer for you, but it is not that efficient (again O(n^2), as you code in the question ).
If i understood correctly , data.start.length and data.row.length has to be equal, right? If so, if you count the s from i-1 to 0, and the errors are (1,2) and (2,1) , (1,2) won't get cought becouse the second loop starts from i-1, in the case where i=1, s starts directly from 0. just take a look at the code below (only included the for loops) :
var length = data.row.length;
for( var i = length; i>0; i-- ) {
for( var s = i-1; s>0; s-- ) {
if(data.start[i] < data.end[s] && data.start[s] < data.end[i]) {
errors.push('Conflict between ' + data.row[i] + ' and ' + data.row[s]);
}
}
}
I hope somebody will come with a comment with an idea to optimise this, maybe O(n) if it is possible :D. But this will work for you , and in case where you don't have length variable as big as 100.000, it will work just swell!

Related

jQuery run code after chunked $.each() is finished

With the below code:
$('#button').on('click', function () {
var longArray = searchArray; // assume this has 100 or more postalcodes init
var shortArrays = [], i, len;
for (i = 0, len = longArray.length; i < len; i += 100) {
shortArrays.push(longArray.slice(i, i + 100));
}
// Now we iterate over shortArrays which is an array of arrays where each array has 100 or fewer
// of the original postalcodes in it
for (i = 0, len = shortArrays.length; i < len; i++) {
// shortArrays[i] is an array of postalcodes of 100 or less
$.each(shortArrays[i], function(index, value){
setTimeout( function() {
// Each parent gets its own searchToggle class
$('.postcodes input[data-postcode*="' + value + '"]').parent().parent().addClass('searchToggle');
// Each parent also gets a data filter attribute for ordering the results
$('.postcodes input[data-postcode*="' + value + '"]').parent().parent().attr('data-filter' , index);
// We display the items in the search array
$('.postcodes input[data-postcode*="' + value + '"]').parent().parent().css('display', 'flex');
$('.postcodes .searchToggle .postcode input[data-postcode*="' + value + '"]').parent().css('display', 'flex');
}, 0 );
})
} // /for
alert('Finished message');
});
I try to show an alert message(for debugging) once the $.each() is finished. Since this each goes through an array that could be 1000s of postal codes long I broke it up in chunks of 100. This to prevent the dreaded browser is unresponsive. This is all working fine but the alert fires immediately on click.
I have tried several things already:
I tried by using a count: ABOVE THE EACH var count = 0; INSIDE THE EACH count++ if ( count == longArray.length ) { ALERT } But this also fired the alert immediately???
I tried it by using an interval but that became a mess almost instantly.
I tried a couple of other SO answers but all of them resulted in the alert to fire immediately.
When looking through the jQuery docs and previous codes that I have written it should just run the code after the each is finished but in this case it does not.
Any idea on why this is and how I can fix it.
PS: This alert could be other codes! Like sorting the results or something else.
PS2: I can change all the js/jQuery you see but I cannot change any of the HTML selectors.
PS3: Thank you for thinking about this issue and especially for commenting/answering!
I have solved it by adding another setTimeout.
So I replaced the alert (see Question) from
alert(Finished message);
To
setTimeout( function() {
// As an example I used alert in my question and here in this answer.
// This can be offcourse anything else. I use it for instance to sort the results.
alert(Finished message);
}, 0 );
This works for me, but it might not be the best way to deal with it. So I am still looking forward to what more experienced people think about the question or the answer.

Populate a prompt with elements of an array and number them off

(Stack Overflow doesn't have a tag for 'prompt' so I have used alert as I am guessing it is similar enough to attract the right answerers.)
Hello,
I am currently making a JavaScript-based game for an assignment at university. I am usually pretty good with problem solving but have been stumped by this issue.
To explain, I have an array which names the possible armour slots the player can pick. In any order these can be picked, and each time the choice gets pushed to a second array which handles what has already been picked (and in what order) and that item gets spliced from the original array. There is a while loop which runs through until all 3 have been picked.
var armourSlotToPick = ["Head", "Chest", "Legs"],
armourSlotPicked = [],
armourLoop = 1,
indexArmour = 0;
function numInArray() {
indexArmour++;
return (indexArmour + ". " + armourSlotToPick[indexArmour - 1] + "\n");
}
function armour() {
while (armourLoop < 4) {
var armourPick = prompt("Pick an armour slot to generate an item for:\n" + armourSlotToPick.forEach(numInArray));
if (armourPick == 1) {
armourSlotPicked.push(armourSlotToPick[0]);
armourSlotToPick.splice(0,1);
} else if (armourPick == 2) {
armourSlotPicked.push(armourSlotToPick[1]);
armourSlotToPick.splice(1,1);
} else if (armourPick == 3) {
armourSlotPicked.push(armourSlotToPick[2]);
armourSlotToPick.splice(2,1);
} else {
alert("Invalid choice, you suck");
break;
}
armourLoop++;
}
}
I know it probably wouldn't be possible to do the whole return in numInArray() to the prompt, but it shows some working.
Now the problem: I got it working so that each item in the array was numbered (var armourSlotToPick = ["1. Head", "2. Chest", "3. Legs"],) but as you could see, if the player chose 2, then the next time it would show "1. Head (new line) 3. Legs" and when the player chooses 3, a problem would occur, as they were really meant to choose 2. How is it possible to number the items in the array, in a prompt?
I'm possibly over thinking this but I have suffered for a few hours now.
I thank you in advance for any insight you may have,
Daniel.
EDIT: Solved.
Below is the end result, a slight variation from the edited answer from Jonathan Brooks.
var armourSlotToPick = [null, "Head", "Chest", "Legs"]
var armourSlotPicked = [null];
var armourLoop = 1;
function armour() {
while (armourLoop < 4) {
var message = "Pick an armour slot to generate an item for:\n";
for (var i = 0; i < armourSlotToPick.length; i++) {
if (armourSlotToPick[i] !== null) {
message += "" + i + ". " + armourSlotToPick[i] + "\n";
}
}
var armourPick = prompt(message);
if (armourPick > armourSlotToPick.length-1 || armourPick < 1) {
alert("Invalid choice, you suck");
} else {
var insert = armourSlotToPick.splice(armourPick, 1);
armourSlotPicked.push(insert);
}
armourLoop++;
}
armourSlotPicked.splice(0,1);
}
armour();
alert(armourSlotPicked.join("\n"));
I thank all that have contributed to this discussion and the end result, and I hope this is a good example for future problems people may have similar to this.
Check out my fiddle, I think I have a working solution.
What you really want to be using are Object Literals with your own indexing (starting from 1) - if it were me, I would create my own way to iterate over this custom indexing by adding a method to the Object's prototype, but I digress.
You're overcomplicating your code by using a while loop, and that large bulk of if statements is unnecessary: instead, all you need is some basic validation on the input and then you can just trust whatever input passes this validation. That is demonstrated here:
if ( armourPick > armourSlotToPick.length || armourPick < 1 ) {
alert("Invalid choice, you suck");
}
else {
armourSlotPicked.push( armourSlotToPick[armourPick-1] )
alert (armourSlotPicked[armourSlotPicked.length-1].value);
}
Read my code carefully, and you should get a better understanding of how to deal with certain issues.
EDIT:
As per your request, I think I have a solution that suits your needs. Basically all you have to do to have the arrays "start" at an index of 1 is to fill the zeroth element with a null value, like so:
var armourSlotToPick = [null, "Head", "Chest", "Legs"]
var armourSlotPicked = [null];
You just have to remember to take this null object into account in your code, for example:
if (armourSlotToPick[i] !== null) {
message += "" + i + "\n";
}
The indices will update automatically. See this updated fiddle for more details.
use structures / objects as content in the array, instead of just values.
the basic concept:
armourSlotPicked.push({ "key": 1, "value":armourSlotToPick[1]})
alert("value: " + armourSlotPicked[0].value)
alert("key: " + armourSlotPicked[0].key)
edit: responding to comments can take some space.
IMHO a prompt is the completely wrong tool for this, since most browsers would ask the user permission to prevent multiple popups, and since a promt can only return 1 piece of information, you can only ask for 1 thing per popup. Instead you ought to use a div element, with checkboxes for each information..
That being said it can easily be used in a promt.
The prompt is just a built in function, that takes a string as an argument (which is shown as text in the popup) and returns a string with the users input.
what does the magic for you is in fact this:
array.foreach(): The forEach() method executes a provided function once per array element.
in your case that means it calls a function that returns a string for each element in the array, and concatenates the strings.
in the old days you would have written this:
var messageText= "Pick an armour slot to generate an item for:\n"
for(var i = 1; i < armourSlotToPick.length; i++){
messageText += i + ". " + armourSlotToPick[i- 1] + "\n";
}
var armourPick = prompt(messageText);
but in this modern age, you define a printing function, and use it to generate the loop:
function numInArray() {
indexArmour++;
return (indexArmour + ". " + armourSlotToPick[indexArmour - 1] + "\n");
}
//more code before we get to where the function is used....
indexArmour = 0;
var messageText = "Pick an armour slot to generate an item for:\n" + armourSlotToPick.forEach(numInArray);
var armourPick = prompt(messageText);
or in a single line as in your code:
indexArmour = 0; //you forgot this - otherwise the list will only be complete once?
var armourPick = prompt("Pick an armour slot to generate an item for:\n" + armourSlotToPick.forEach(numInArray));
It produces the same output, because it does the same thing, its just written very differently!
If the array holds "object literals" instead of simply values, as I suggest, the old fashioned code would look something like this:
function contains(a, value) {
try{
for (var i = 0; i < a.length; i++) {
if (a[i].value == value) {
return true;
}
}
}
catch(err) {
// do nothing
};
return false;
}
and later..
for(var j = 0; j < 4; j++){
for(var i = 0; i < Math.min(armourSlotToPick.length); i++){
if( contains(armourSlotPicked, armourSlotToPick[i- 1]) )
continue;
var messageText = "Generate an item for armour in slot: " + i + "\n"
messageText += armourSlotToPick[i- 1] + "\n";
}
var armourPick = prompt(messageText);
if (armourPick > 0 && armourPick < armourSlotToPick.length) {
armourSlotPicked.push({"key":j, "value":armourSlotToPick[armourPick]);
}
...
}
//now we have an array that holds information about when what was picked..
or something along those lines.. this is bt.w completely untested, it's just for illustration
You want to use the array index to number your items. Since your numbers are one-based and the index is zero-based, you will need to convert between the two when outputting and interpreting the response.
This approach will also allow you to eliminate all but two of the cases in your if-else statement.

Result of FOR-loop if there is no related item

in a Podio calculation_field A I sum up the numbers of each related item (from another app) which contain "searchstring_1" in a text_field, in calculation_field B all related items which contain "searchstring_2"
No problem with the following code - IF there exists a related item. But if there exists no related item the "1" (= "nothing found"?) is displayed as "result". I tried several things, but can't find a solution for a calculation like: IF there is no related item then result = 0 (or "" or null), else let run the for-loop. Has anybody a tip what I can do?
Thanks,
Rainer
var str = all of text_field;
var num = all of number_fields;
var total = 0;
for(var i = 0; i < num.length ; i++) {
if (str[i].indexOf("searchstring_1") === 0) {
total += num[i];
}
}
The calculation field always returns the last used value if you don't explicitly specify the return value. Maybe in your case the last value of i, str[i].indexOf("searchstring_1") would return -1, I think...
To make sure that the value of totalis returned, simply add
total;
at the end of your calculation field value.
Enjoy,
Michael / Hamburg

Nested If-else statements being skipped

What I'm building is a game where the computer generates a random number (1-100) and the user must guess the correct number. The goal is for the computer to compare the current guess to the previous guess and spit out a statement: "hot", "cold", "hotter", "colder", etc.
My Code (focus on the JS): CodePen fiddle
//global variables--computer generated guess, and guess log
var answer = Math.floor((Math.random() * 100)+1);
var guessArray = [];
var index = 0;
//user clicks submit button and guess is registered by computer
$("#submit").click( function(){
var guess = $("#guess").val();
guessArray.push(guess);
//prints out the answer and user guesses
$("#answer").text("Answer:" + " "+ answer);
$("#guessArrayPrint").text("You guessed: " + " " + guessArray + " ");
if (answer === guess) {
$("#statement").text("woo hoo right answer");
} else {
var currentDifference = Math.abs(answer-guess);
var currentDiffArray = [];
currentDiffArray.push(currentDifference);
if (index = 0) {
//if-else statement comparing current guess range to answer
if ( currentDifference >=1 && currentDifference <= 10){
$("#statement").text("Ouch! You're hot!");
} else {
$("#statement").text("Brr! You're cold!");
}
} else {
//if-else statement comparing current guess to previous guess
var previousDiff = answer- prevguess;
var prevguess = guessArray [i-1];
if( previousDiff < currentDifference){
$("#statement").text("Ahh! Getting Warmer!");
} else {
$("#statement").text("Brrr...getting colder");
}
}
index++
}
});
My nested if-else statements are not working. When a user inputs a guess, no matter how close to the answer, it always returns the statement "brr.. getting colder", which is in the "else" section.
Ideally when the user inputs their first guess if (index = 0) should run then when the second guess is input, it should move to the "else" statement with the previous guess variables. I tried moving around the variables, changed orders of if/else, and thought maybe it's the placement of index++. Nothing is working. Not sure if something is wrong with my variables , arrays, or the syntax of my if/else statements.
tl;dr: when the program is run only the "else" portion of the nested if-else statement is run. Not sure how to fix… I've gone through my code a number of times. The syntax, the arrays, and variables. Uncertain what's wrong.
You JS has if (index = 0). This should be if (index === 0).
Additionally, you need to cast the value of your input field to a number. You can do this using:
var guess = +$("#guess").val(); // + cast as a number
More syntax errors:
prevguess = guessArray[i - 1] --> prevguess = guessArray[index - 1];
Here is a partial working Fiddle. I ran through some scenarios, and the fiddle really only works if you give the application the right answer. The code has many syntax errors, bad refs and calculations. I would suggest opening the console or a debugger, identifying those issue, and fixing them.
Here is a Fully Functional Demo.

Am I loopy or is it localstorage...?

My goal: To clean up localstorage, by removing previously used items with a for loop.
Usually, I have a number of items, like so:
order-1356666288243 => {"cartID":2713329701836,"productName"...
order-1356666288243 => {"cartID":2713329701836,"productName"...
When I check how many items there are altogether, I get the correct zero-based amount:
console.log( localStorage.length );
Even when I do a for loop to write out the key and values, and console.log() a few other things, all is well:
for(var i=0, len=localStorage.length; i<=len; i++) {
var key = localStorage.key(i);
var value = localStorage[key];
console.log(key + " => " + value);
if(key != '' && key != null){
console.log( key.indexOf('order-') );
console.log(i + '. Key: ', key);
if(key.indexOf('order-') != -1){
console.log('The key to be removed is: ', key.toString());
localStorage.removeItem(key.toString());
}
}
console.log(i + ': ' + len);
}
Everything pretty much does what one would expect. However, this line executes only once when I run the script:
localStorage.removeItem(key);
or
localStorage.removeItem(key.toString());
In fact, I have to run the entire script as many times as there are items. So if I have, say 3 items, I need to run the loop 3 times to get rid of each item.
I'm perplexed: Where did I go wrong? All the console.log() stuff shows up 3 times (if I have 3 items and run the for loop once) . Out of despair, I even changed i < len to i >= len, but that doesn't solve the problem either.
Anyone?
The problem is that you are modifying a collection while you are traversing it. Always be careful when you do this.
In this particular case, the length of localStorage changes when you remove an item, so you end up skipping items.
You should either traverse localStorage backwards (last item to first item), or better yet you should do this in two passes: one pass to collect the keys to remove, and another pass to remove them:
Object.keys(localStorage)
.filter(function(key){return key.indexOf('order-')===0})
.forEach(localStorage.removeItem, localStorage);
Or if you need this to work in IE8 or FF3.x:
var delkeys = [], key, i, len;
for (i=0,len=localStorage.length; i<len, key=localStorage.key(i); i++) {
if (key.indexOf('order-')===0) {
delkeys.push(key);
}
}
for (i=0,len=delkeys.length; i<len, key=delkeys[i], i++) {
localStorage.removeItem(key);
}
I think the problem is that when you remove an item, it changes the length of the local storage. Try iterating from the length-1 down to 0 instead of from 0 up to the length. (By the way, the loop condition should have been i < len, not i <= len. With my suggestion, of course, it should be i >= 0.)
The loop should count down to prevent problems with deleted keys altering storage length. Try this for loop instead:
for( var i = localStorage.length; i--;){
//make sure to remove references to len in your code
}

Categories

Resources