Javascript custom sort function depending on user input - javascript

I'm trying to make a custom compare callback function for the Javascript array.sort method. It should return a value depending on user input.
When that callback function is called, it should wait until the user clicks a button. Depending on the clicked button the function would know which of the two elements is bigger and return the corresponding result (1 or -1).
The only way I know to wait for user input is with an event listener function, but I don't know how to adapt this to the custom function I have to pass to the array.sort() method.
Here is the code I'm trying with; I know this code won't work:
var array=["a","b","c"];
array.sort(function(a,b){
var result;
$("#button1").click(function(){
result = 1;
});
$("#button2").click(function(){
result = -1;
});
return result;
}
I'm starting to think it's impossible to use the array.sort function for this purpose.
Is there a way to do this?

You could do this the ugly way, by making use of the window.confirm method, which has the behaviour to wait with the further execution of your code until the user closes the pop-up by either choosing OK or Cancel:
array=["a","b","c"];
array.sort(function(a,b){
var a_before_b = confirm("'" + a + "' will be sorted before '" + b
+ "'. Cancel to order differently.");
return a_before_b ? -1 : 1;
});
document.body.innerHTML = '<p>Array sorted: ' + array + '</p>';
<p>You will get 2 to 3 pop up dialogs to sort [a, b, c]</p>
To have the user answer via normal interaction, not via modal dialogs, and still use the standard sort method, is a harder task.
One idea would be to keep a list of answers given, and repeat the sort each time an new answer is available, feeding those answers via the sort callback function.
Once a comparison comes up for which no answer was yet given, you would then present that comparison to the user, and complete the rest of the sort with any order. That sorted array will be ignored, up until the moment you have all answers for all comparisons.
The code is quite long, mostly because of the interaction with the page:
var array = ['a', 'b', 'c'];
var answers = [];
var questionTemplate = "Order '#' before or after '#'?";
var answerTemplate = "The array is sorted. Result: #.";
function logMsg(msg) {
$($('#log')[0].insertRow(-1).insertCell(-1)).text(msg);
}
function answer(order) {
// keep track of answers
answers.push(order);
// show answers also in a log table
logMsg($('#question').text() + ' - ' + (order<0? 'Before' : 'After'));
askNext();
}
function askNext() {
var arrayCopy = array.slice(0); // take real copy
var questionNo = -1;
arrayCopy.sort(function(a,b){
questionNo++
if (questionNo < answers.length) {
// we already asked this, so use that answer
return answers[questionNo];
}
if (questionNo == answers.length) {
// put question to user:
$('#question').text(questionTemplate.replace('#', a).replace('#', b));
}
// don't care for now, just finish it. We need first to
// get an answer, and will then restart the sort.
return -1;
});
if (array.length == answers.length) {
// Array is now sorted according to answers:
// Hide question section
$('#questionDiv').hide();
// Show the result
logMsg(answerTemplate.replace('#', arrayCopy));
}
}
function reset() {
$('#array').text(array);
$('#questionDiv').show()
$('#log').html(''); // empty log table
answers = [];
askNext();
}
// Set up click handlers
$('#reset').click(reset);
$('#before').click(answer.bind(null, -1));
$('#after').click(answer.bind(null, 1));
reset();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>The array <span id="array"></span> will be sorted by asking you questions:</p>
<table id="log"></table>
<div id="questionDiv">
<p id="question" style="font-weight: bold"></p>
<button id="before">Before</button><button id="after">After</button>
</div>
<button id="reset">Restart</button>

Related

reset a variable for a reusable function

I have a bit of code that counts how many objects are in the returned json and gives me the total number.
loadCountSetOne = 0;
$.each(dataSetOne.user.customers, function(key, val) {
loads = Object.keys(val.loads).length;
loadCountSetOne = loadCountSetOne + loads;
console.log(loadCountSetOne);
});
This works fine, but since I'll need to count these a bunch of times I thought I'd move it into it's own function and call it when I need it with something like counter(val.loads);
count = 0;
function counter(itemToCount) {
result = Object.keys(itemToCount).length;
count = count + result;
console.log(itemToCount, result, count);
return count;
}
When I call the function the 1st time I get the right result. When I call it again it adds the 2nd result to the 1st and so on.
My understanding is that that is what it's supposed to do, but not what I need it to do. I tried resetting the value for count is various places but it didn't work.
Is there a way to make this function give me a result based on the number of objects in itemToCount each time it's called?
Thanks.
You can't do this in the counter() function itself, since it has no way of distinguishing the first call (which should reset the variable) with subsequent calls. You could pass the array index, and it could reset the total when the index is 0, but this is not a good general solution because you might want to use the function in other ways.
You just need to reset the variable before the $.each() loop:
count = 0;
$.each(dataSetOne.user.customers, function(key, val) {
counter(val.loads);
});
Or you could use reduce, which is designed for accumulating values.
function counter(total, itemToCount) {
var result = Object.keys(itemToCount).length;
total += result;
console.log(itemToCount, result, total);
return total;
}
var count = dataSetOne.user.customers.reduce(function(total, current) {
return counter(total, current.loads);
}, 0);

I am trying to stop my function from displaying the same object twice when clicking a button

I have for quite some time now been trying to figure out how I can stop my code to print the same quote twice.
Also, when every single object in the array has been printed out, I'd like for it to reset somehow. So that you can browse through the quotes once you've gone through all of them.
This is the essential parts of my code:
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
The printQuote function simply contains information that's accessing information from my array:
var randomObjectNumber = getRandomQuote();
var html = "<p class='quote'>"
+ quotes[randomObjectNumber].quote +
"</p>";
document.getElementById('quote-box').innerHTML = html;
One random object is displayed each time you click the eventListener:
function getRandomQuote () {
var randomObjectNumber = Math.floor(Math.random() * quotes.length );
return randomObjectNumber;
}
I have some ideas on how to do this and I have tried them but without success. I tried giving each object a boolean property but I can't really seem to assign each property a boolean value without messing the printQuote function up.
I also tried assigning the object displayed to a different array but the same problem occurred there.
I feel like there is some concepts around the eventListener that I don't fully understand, because every time I try to manipulate a displayed object I just end up changing every single object.
This is what a typical object in the array looks like by the way:
{quote : "Darkness is merely the absence of light"}
(I also have other properties assigned to the object but i feel like presenting them would be redundant)
If someone could explain, or give me a hint, on how to solve this problem I've been struggling with for some time.
Some hints would be greatly appreciated!
Have a nice day.
Sebastian.
EDIT: All code: https://jsfiddle.net/fusqb7hz/
Basically what you need:
Create a separate array that will store all quotes that you've already used.
Remove quote from initial array.
Check if you still have quotes in initial array, if not, get them back from backup array.
The problem is that you call addEventListener twice:
//Let's developers create multiple eventListeners without being redundant.
function onClicking (printFunction) {
document.getElementById('loadQuote').addEventListener("click", printFunction, false);
}
onClicking(printColor);
onClicking(printQuote);
by calling onClicking twice you make the click happen twice, so addEventListener is added twice, meaning one click counts as two.
Change the above code for this:
//Let's developers create multiple eventListeners without being redundant.
document.getElementById('loadQuote').addEventListener("click", function(){
printColor();
printQuote();
});
Here is the jsfiddle:
https://jsfiddle.net/fusqb7hz/3/
I think the easiest approach is to shuffle your quote array and then go through them one by one. This gives you the next "random" as yet unseen quote. The only part I'm not keen on is this shuffler (a derivation of Fisher Yates) modifies the original quote array. You might not care about that though.
// --------------------------------
// A bunch of quotes
// --------------------------------
var quotes = [];
quotes.push({quote : "Darkness is merely the absence of light"});
quotes.push({quote : "quote 2"});
quotes.push({quote : "quote 3"});
quotes.push({quote : "quote 4"});
quotes.push({quote : "quote 5"});
// --------------------------------
// --------------------------------
// Your favorite array shuffle utility
// --------------------------------
var shuffle = function(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
};
// --------------------------------
// --------------------------------
// construct a function to get a random unseen quote until
// all quotes have been seen. Then reset...
// --------------------------------
var getQuote = (function(quotes, shuffle){
var current = 0;
var get = function(){
if ( !quotes || !quotes.length ) { return ""; }
if ( current >= quotes.length ){ current = 0; }
if ( current === 0 ){
console.log("randomizing quotes...");
shuffle(quotes);
}
return quotes[current++].quote;
};
return get;
})(quotes, shuffle);
// --------------------------------
var printQuote = function(){
document.getElementById('quote').innerText = getQuote();
};
document.getElementById('loadQuote').addEventListener("click", printQuote, false);
<div id="quote"></div>
<button id="loadQuote">get quote</button>

How do I add elements to a dynamic array and exclude exsisting elements

function addNumifnotThere(numer){
var numCent = [];
numCent.forEach(function(){
if(numer in numCent)
console.log("you logged that");
else
numCent.push(numer);
});
return numCent;
}
This is my current code, what its attempting to do is read an array and if there is already an element exits the loop and says "you already logged that", obviously if it cannot find a similar element then it pushes it to the array.
I want this to work dynamically so we cannot know the size of the array beforehand, so the first element passed as an argument should be put into the array, (addNum(1) should have the array print out [1], calling addNum(1) again should print "you already logged that")
However there are two problems with this
1) Trying to push to a new array without any entries means everything is undefined and therefore trying to traverse the array just causes the program to print [].
2) Adding some random elements to the array just to make it work, in this case numCent=[1,2,3] has other issues, mainly that adding a number above 3 causes the code to print incorrect information. In this case addNum(5) should print [1,2,3,5] but instead prints [1,2,3,5,5,5]
I know this has to be a simple mistake but I've been dragging myself too long to not ask for help.
EDIT: Thanks to the many outstanding answers here I have now leanred about the indexOf method, thank you guys so much.
For every non-match you are pushing the number. Use something like this
var numCent = [];
function addNumifnotThere(numer)
{
var index = numCent.indexOf(number);
if(index >=0)
{
console.log("you logged that");
}
else
{
numCent.push(number);
}
return numCent;
}
Use Array.prototype.indexOf
var numCent = [];
function addNum(numer){
if (numCent.indexOf(numer) > -1)
{
console.log("Number already in array");
}
else
{
numCent.push(numer);
}
}
//DEMO CODE, not part of solution
document.querySelector("button").addEventListener("click", function(){
if (document.querySelector("input").value.length > 0)
{
addNum(document.querySelector("input").value);
document.querySelector("div").innerHTML = numCent.join(", ");
}
}, false);
Output
<div id="output"></div>
<input />
<button>Add number</button>
indexOf tests if an element is inside the array and returns its index. If not found it will return -1. You can test for this. You can try it for your self in this snippet. It will only allow you to add a number (or any string, in this example) once.
I also was confused by the newCent array declaration inside the function. I think, based upon the content of your question, you meant this.
If you want the array held in the instance, you can do it like this.
function AddIf(arr){
if( arr || !this.arr ) {
this.arr = arr || [];
}
return function(number) {
if( this.arr.indexOf(number) >= 0 ) {
console.log("Already Present!");
} else {
this.arr.push(number);
}
return this.arr;
}.bind(this);
}
// Usage would be like this:
// var addIf = new AddIf([1, 2, 3]);
// addIf(10); // returns [1, 2, 3, 10]
// addIf(10); // logs "Already Present!", doesn't add 10 to array
This basically returns a function, with this bound to the function being called. If you pass in an initial array, it will use that array to compare to when adding it to the array.
You can catch the return function and call it as you would want to. If you don't call new when invoking however, it will share the same array instance (and have a funky way of being called, AddIf()(10)).
I used fn.bind() to ensure the function gets called in the correct context every time, if you were wondering why I called it like that.
Do do this cleanly, I'd consider prototyping the global Array object and adding a method to push values but only if they're unique to the array. Something like this:
Array.prototype.pushUnique = function (item) {
if (this.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
this.push(item);
}
}
If you're not comfortable prototypeing global types like Array, you can build the same thing in a procedural pattern:
function arrayPushUnique (arr, item) {
if (arr.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
arr.push(item);
}
}
Then to use it, simply create a new empty array and start pushing things to it.
var numCent = [];
// The Array.prototype method
numCent.pushUnique(number);
// The procedural method
arrayPushUnique(numCent, number);

Give structure to 'random' function js?

I have an array and a function that picks randomly elements from this array and displays them in a div.
My array:
var testarray = [A, B, C, D, E, F];
Part of the js function:
var new_word = testarray[Math.floor((Math.random()*testarray.length)+1)];
$("#stimuli").text(new_word);
My question is, is there a way I can have them picked randomly in a certain ratio/order?
For example, that if I have my function executed 12 times, that each of the six letters is displayed exactly twice, and that there can never be the same letter displayed twice in a row?
You might want to try a quasi-random sequence. These sequences have the properties you're after. http://en.wikipedia.org/wiki/Low-discrepancy_sequence
Edit:
To your question in the comment: Of course there are hundreds ways to solve a problem. Think about using artificial intelligence, a mathematical algorithm or the answers given by others here. It depends on what you really want to achieve. I just gave a robust solution that is easy to understand and implement..
Here's another (different approach), same result but with the prevention that values displays twice in a row.
Jsfiddle: http://jsfiddle.net/kychan/jJE7F/
Code:
function StructuredRandom(arr, nDisplay)
{
// storage array.
this.mVar = [];
this.previous;
// add it in the storage.
for (var i in arr)
for (var j=0; j<nDisplay; j++)
this.mVar.push(arr[i]);
// shuffle it, making it 'random'.
for(var a, b, c = this.mVar.length; c; a = Math.floor(Math.random() * c), b = this.mVar[--c], this.mVar[c] = this.mVar[a], this.mVar[a] = b);
// call this when you want the next item.
this.next = function()
{
// default value if empty.
if (this.mVar.length==0) return 0;
// if this is the last element...
if (this.mVar.length==1)
{
// we must give it..
return this.mVar.pop();
// or give a default value,
// because we can't 'control' re-occuring values.
return -1;
}
// fetch next element.
var element = this.mVar.pop();
// check if this was already given before.
if (element==this.previous)
{
// put it on top if so.
this.mVar.unshift(element);
// call the function again for next number.
return this.next();
}
// set 'previous' for next call.
this.previous = element;
// give an element if not.
return element;
};
}
NOTE: In this example we can't fully control that the same values are displayed twice.. This is because we can control the first numbers, but when there is only one number left to display, we must either give it or display a default value for it, thus there is a chance that the same value is shown.
Good luck!
Like this?
var arr = [1,2,3,4,5,6,7], // array with random values.
maxDispl = 2, // max display.
arr2 = init(arr) // storage.
;
// create object of given array.
function init(arr)
{
var pop = [];
for (var i in arr)
{
pop.push({value:arr[i], displayed:0});
}
return pop;
}
// show random number using global var arr2.
function showRandom()
{
// return if all numbers has been given.
if (arr2.length<1) return;
var randIndex= Math.floor(Math.random()*arr2.length);
if (arr2[randIndex].displayed<maxDispl)
{
document.getElementById('show').innerHTML+=arr2[randIndex].value + ', ';
arr2[randIndex].displayed++;
}
else
{
// remove from temp array.
arr2.splice(randIndex, 1);
// search for a new random.
showRandom();
}
}
// iterate the function *maxDispl plus random.
var length = (arr.length*maxDispl) + 2;
for (var i=0; i<length; i++)
{
showRandom();
}
jsfiddle: http://jsfiddle.net/kychan/JfV77/3/

Javascript: Generic get next item in array

I am trying to make a JavaScript function that will search an array of strings for a value and return the next string. For example, if an array is built such that an item is followed by its stock code, I want to search for the item and have the stock code written.
var item = (from user input); //some code to get the initial item from user
function findcode(code){
var arr = ["ball", "1f7g", "spoon", "2c8d", "pen", "9c3c"]; //making the array
for (var i=0; i<arr.lenth; i++){ //for loop to look through array
arr.indexOf(item); //search array for whatever the user input was
var code = arr(i+1); //make the variable 'code' whatever comes next
break;
}
}
document.write(code); //write the code, I.e., whatever comes after the item
(I'm sure it's obvious I'm new to JavaScript, and while this is similar to a number of other questions I found, those seemed to have more involved arrays or more complex searches. I can't seem to simplify them for my needs.)
You've almost got it right, but the syntax is arr[x], not arr(x):
index = array.indexOf(value);
if(index >= 0 && index < array.length - 1)
nextItem = array[index + 1]
BTW, using an object instead of an array might be a better option:
data = {"ball":"1f7g", "spoon":"2c8d", "pen":"9c3c"}
and then simply
code = data[name]
Cycled items from array this might be useful
const currentIndex = items.indexOf(currentItem);
const nextIndex = (currentIndex + 1) % items.length;
items[nextIndex];
The first item will be taken from the beginning of the array after the last item
Try this String.prototype function:
String.prototype.cycle = function(arr) {
const i = arr.indexOf(this.toString())
if (i === -1) return undefined
return arr[(i + 1) % arr.length];
};
Here is how you use it:
"a".cycle(["a", "b", "c"]); // "b"
"b".cycle(["a", "b", "c"]); // "c"
"c".cycle(["a", "b", "c"]); // "a"
"item1".cycle(["item1", "item2", "item3"]) // "item2"
If you want to do it the other way round, you can use this Array.prototype function:
Array.prototype.cycle = function(str) {
const i = this.indexOf(str);
if (i === -1) return undefined;
return this[(i + 1) % this.length];
};
Here is how you use it:
["a", "b", "c"].cycle("a"); // "b"
["a", "b", "c"].cycle("b"); // "c"
["a", "b", "c"].cycle("c"); // "a"
["item1", "item2", "item3"].cycle("item1") // "item2"
I think that an object could be probably a better data structure for this kind of task
items = {
ball : "1f7g",
spoon: "2c8d",
pen : "9c3c"
}
console.log(items['ball']); // 1f7g
You may pass array to function as argument and return found value from function:
var item = "spoon"; // from user input
var arr = ["ball", "1f7g", "spoon", "2c8d", "pen", "9c3c"]; //making the array
function findcode(item, arr){
var idx = arr.indexOf(item); //search array for whatever the user input was
if(idx >=0 && idx <= arr.length - 2) { // check index is in array bounds
return arr[i+1]; // return whatever comes next to item
}
return '';
}
document.write(findcode(item, arr)); //write the code, i.e., whatever comes after the item
Answer for 2022
This question came up when I was searching for a modern way to do this. I had been using the technique described in SerzN1's excellent answer without the wraparound (because I don't want that to happen). It winds up being quite a bit of code to make it safe, so I wanted something more modern.
As it turns out, there is a feature that has been available in every major browser since 2016. If someone hasn't updated their browser in six years, that's their loss, right?
ES2015 Array.find()
This function is used for finding a specific element in an array. That's exactly what we want to do here. The only problem is it doesn't maintain state for you, so you can only find a matching element, not the one after (or before, for that matter). To get around that, we use a closure.
Here's the short version:
let trigger = false;
const found = arr.find(element => trigger || (trigger = element === value) && !trigger);
You start with trigger set to false because we need to keep track of when the element is found (if at all). Then we use the Array.find() on the list we should be searching. The single argument to that function is a search function, which we define in-line as a closure so it has access to trigger.
The search function is the tricky part: element => trigger || (trigger = element === query.value) && !trigger. It might be easier to read if I break it apart into a more conventional function just so we can evaluate it. I'll describe what's happening in the comments:
function (element) {
// If trigger is true, that means the previously-evaluated element was the match.
// Therefore, we must be currently evaluating one AFTER it.
// We should match on this one.
if (trigger === true) return true;
// Then we update the value of trigger to the result of comparing the element with the search value
trigger = (element === value)
// Now we `and` it together with its negation in order to make sure
// it always returns false even when the element matches the search value
return trigger && !trigger
}
And there you have it! It only takes two lines of code to get the element after one that matches your query.
Want to see it in action? Here you go:
function showNext() {
const query = document.getElementById('query');
if (query === null) return;
const result = document.getElementById('result');
if (result === null) return;
var arr = ["ball", "1f7g", "spoon", "2c8d", "pen", "9c3c"];
// This is the important part
// Create a boolean flag that will inform the find function when it passes
// the matching element in its search
let trigger = false;
const found = arr.find(element => trigger || (trigger = element === query.value) && !trigger);
// Now `found` is equal to the array element AFTER the one you searched for
// If it is undefined, that means the one you searched for was either the last
// element in the array or it was missing.
result.innerText = `Found ${found ?? 'nothing'}`;
}
<input id="query" />
<input type="submit" value="Display next" onclick="showNext()" />
<div id="result"></div>

Categories