Okay, I am trying to create a sorting application but, its kinda busting my mind. Maybe because of thinking too much.
What I wanted
I want to sort values from an array, maybe using bubble sort
I want to push each iteration to <tr> of a table
and be able to know which values has been replaced
What must happen
Each iteration, I will get a list of values
each list will highlight the values affected
What I currently have
var sort = {
init : function() {
sort.vars.$oldList = [6,4,7,1,8];
sort.play.bubble();
}
}
sort.vars = {
$oldList : new Array(),
$newList : new Array()
}
sort.play = {
bubble : function() {
var list = sort.vars.$oldList;
var n = list.length;
var isSorted = false;
while(!isSorted) {
var tmp, i;
isSorted = true;
for (i = 0; i < n; i++) {
if (list[i] > list[i+1]) {
tmp = list[i];
list[i] = list[i+1];
list[i+1] = tmp;
isSorted = false;
}
sort.ui.pushtToTable(list);
}
}
}
}
sort.ui = {
pushtToTable : function(list) {
$.each(list, function(n, val){
$('tr').append('<td>' + val);
})
}
}
$(document).ready(function(){
sort.init();
})
If possible I wanted to display values one by one, maybe setting a timeout, right?
Yes, setTimeout is a good idea if you want to "see" the algorithm's progress. However, setTimeout only accepts functions as arguments, so each iteration of the sorting algorithm has to be performed in a separate function. See the following code for an example (the code doesn't produce output in each iteration, instead it "animates" the swapping action - but I'm sure you can easily adjust this to fit your needs).
DEMO (JSFiddle)
var SORT = function(type, list, selector){
var container, containerTr, animSteps = [];
// Show all elements in the container
var printArray = function(list){
var str = ["<table>"], i = 0, l = list.length;
for (i; i < l; ++i) {
str.push("<tr><td>", list[i], "</td></tr>");
}
str.push("</table>");
container.html(str.join(""));
};
// This is the interesting part... ;)
var swap = function(list, i1, i2) {
var tmp = list[i1];
list[i1] = list[i2];
list[i2] = tmp;
// Add 3 functions for each swapping action:
// 1. highlight elements, 2. swap, 3. remove highlight
animSteps.push(function(){
containerTr.eq(i1).add(containerTr.eq(i2)).addClass("highlight");
}, function(){
var tmp = containerTr.eq(i1).text();
containerTr.eq(i1).text(containerTr.eq(i2).text());
containerTr.eq(i2).text(tmp);
}, function(){
containerTr.eq(i1).add(containerTr.eq(i2)).removeClass("highlight");
});
};
var animation = function(){
// Execute all iteration functions one after another
if (animSteps.length) {
setTimeout(function(){
animSteps.splice(0,1)[0]();
animation();
}, 250);
}
};
// Collection of sorting algorithms
var algorithms = {
bubblesort: function(list) {
for (var n = list.length; n > 1; --n) {
for (var i = 0; i < n-1; ++i) {
if (list[i] > list[i+1]) {
swap(list, i, i+1);
}
}
}
}
// Add more algorithms using "swap" here...
};
if (algorithms[type] != undefined) {
container = $(selector);
printArray(list);
containerTr = container.find("tr");
algorithms[type](list);
this.sorted = list;
animation();
}
};
// Usage:
var s = new SORT("bubblesort", [5,8,2,4,1,9,7,3,0,6], "#container");
console.log(s.sorted); //the sorted array
Related
I have the following code:
page.listSuccess = function (data, status, xhr) {
console.log(data);
if (data && data.items) {
var itemsQty = Math.ceil(data.items.length / 3);
for (var index = 0; index < itemsQty; index++) {
var itemData = {};
var itemClone = page.insertItemDOM(itemData);
if (index == 0) {
itemClone.addClass("active");
console.log(itemClone);
}
}
}
for (var index = 0; index < data.items.length; index++) {
var testimonialData = {}
testimonialData.title = data.items[index].title;
testimonialData.body = data.items[index].body;
testimonialData.starRating = data.items[index].starRating;
testimonialData.id = data.items[index].id;
page.insertTestimonialDOM(testimonialData);
}
}
At the moment, this code inserts all(5) my testimonials on one page. I need three per page, but I'm unsure as to how to go about it.
itemClone = a page on the carousel
testimonialData = a testimonial
insertItemDOM function =
page.insertItemDOM = function (itemData) {
var newItem = $($("#itemTemplate").html()).clone();
var targetLoc = $('.carousel-inner');
targetLoc.attr("data-target", "true");
targetLoc.append(newItem);
return newItem;
}
insertTestimonialDOM function =
page.insertTestimonialDOM = function (testimonialData) {
var newTemplate = $($("#testimonialTemplate").html()).clone();
newTemplate.find('.title').html(testimonialData.title);
newTemplate.find('.body').html(testimonialData.body);
newTemplate.find('.starRating').html(testimonialData.starRating);
var targetLoc = $('.carousel-inner');
targetLoc.attr("data-target", "true");
targetLoc.append(newTemplate);
}
Let me explain the concept for you. What you want is after every 3 loop execution the loop shall work it again and enter details in a new page.
Let's make two function.
var index;
var theinitial = function(z)
{
index = z
var counter = 1;
insertionfunction();
}
var insertionfunction = function()
{
for (var zzz = index; zzz < data.items.length; zzz++) {
if (counter <= 3)
{
itemClone = page.insertItemDOM(itemData);
}
else
{
theinitial(zzz);
}
}
Here the first request you make to 'theinitial function' with 1 as value of z then the function will call the insertion function and now the loop will run for 3 times then after three times the value of zzz is used to call the initial function again. Now your index is 4, hence the loop will start from 4th element and run for 3 more times as counter is reinitialized to 1.
I don't really know how your page insertion thing works therefore I am explaining the concept to you.
I have an array of shots. I have been able to take that array and loop through it to get all shots that occurred on hole #1 and then rearrange them in order based on "shot_number". I now need to do this for every hole and to create an array for each hole (ex: holeArray1, holeArray2). I have attempted a number of solutions to increment x but if I do I end up missing some shots that occurred on certain holes.
How can I refactor this function to create this array for every hole without just copying and pasting the code and changing the variable x myself? Thank you for your help. I know I should be able to figure this one out but am struggling.
$scope.createHoleShotsArrays = function () {
var i = 0;
var x = 1;
var holeArray = [];
var len = $scope.shots.length;
for (; i < len; i++) {
if ($scope.shots[i].attributes.hole == x) {
holeArray.push($scope.shots[i]);
holeArray.sort(function (a, b) {
if (a.attributes.shot_number > b.attributes.shot_number) {
return 1;
}
if (a.attributes.shot_number < b.attributes.shot_number) {
return -1;
}
// a must be equal to b
return 0;
});
}
}
console.log(holeArray);
};
Push the items you want into arrays, and sort them once. I don't have cases to test the code. You may modified it a little if something goes wrong.
$scope.createHoleShotsArrays = function() {
var holeArrays = [];
$scope.shots.forEach(function(shot) {
if (holeArrays.length < shot.attributes.hole) {
holeArrays[shot.attributes.hole - 1] = [];
}
holeArrays[shot.attributes.hole - 1].push(shot);
});
holeArrays.forEach(function(arr) {
arr.sort(function(a, b) {
return a.attributes.shot_number - b.attributes.shot_number;
});
});
console.log(holeArrays);
};
I'm fairly new to JS and am still slightly confused, by the ordering and nesting of JS functions. I have a script that I want to occur in a specific way. The problem a criteria can be left blank by a user. Im trying to say if the variable length is greater than zero run the callback, but then move into the code that occurs under the next two if statements. I know there must be a more efficient method for this, but for the life of me I can't think of one besides placing all the other code under each different if/else statement.
var lst = []
var lst2 = []
var lst3 = []
alert(cityu);
alert(dateu);
alert(numberu);
d3.csv("kyle.csv", function (d) {
return {
city: d.from,
number: d.phone_number,
date: d.from_date
};
}, function (error, rows) {
if (dateu.length > 0) {
for (var i = 0; i < rows.length; i++) {
if (rows[i].date === dateu) {
lst.push(rows[i]);
console.log(rows[i]);
}
}
} else {
if (cityu.length > 0) {
for (var i = 0; i < lst.city.length; i++) {
if (lst.city[i] === cityu) {
lst2.push(lst[i]);
console.log(lst2);
}
}
} else {
if (numberu.length > 0) {
for (var i = 0; i < rows.length; i++) {
if (lst.number[i] === numberu) {
lst3.push(lst2[i]);
console.log(lst3);
}
}
}
}
}
})
};
Here you can see that if the dateu variable has length greater than zero the rows in a csv matching that user entered criteria will be pushed to the array "lst". Obviously it currently doesn't move into the next callback under, it will only do this if "dateu" equalled zero.
One other issue with my script is that at each if statement I hope to reduce my original input based on the user entered parameters. For example a user might enter "seattle" the variable "city" will now equal seattle and only rows containing Seattle as their city will be kept in the array that rows[i] is pushed to.
Every piece of user input shown here:
alert(cityu);
alert(dateu);
alert(numberu);
will have the same affect on the dataset, each time reducing the number of rows included.
The problem specifically is that each if statement relies on the array from the previous callback.
Your code redone a little - I've removed the else blocks, because you want to do each loop regardless of the previous loop
if(condition1) {
do something
}
else {
do something else
}
from that, if the first condition is met, the else block wont get executed
if(condition1) {
do something
}
if(condition2) {
do something else
}
In this case, do something else only relies on condition2 being true, consition1 is irrelevant
var lst = []
var lst2 = []
var lst3 = []
alert(cityu);
alert(dateu);
alert(numberu);
d3.csv("kyle.csv", function(d) {
return {
city: d.from,
number: d.phone_number,
date: d.from_date
};
}, function(error, rows) {
var i;
if (dateu.length > 0) {
for (i = 0; i < rows.length; i++) {
if (rows[i].date === dateu) {
lst.push(rows[i]);
console.log(rows[i]);
}
}
}
if (cityu.length > 0) {
for (i = 0; i < lst.city.length; i++) {
if (lst.city[i] === cityu) {
lst2.push(lst[i]);
console.log(lst2);
}
}
}
if (numberu.length > 0) {
for (i = 0; i < rows.length; i++) {
if (lst.number[i] === numberu) {
lst3.push(lst2[i]);
console.log(lst3);
}
}
}
});
};
One minor inconsequential change, moved the declaration of var i to the top of the function block, because technically you were declaring it three times, and jslint/jshint would complain - though nothing would break (yet)
I'm still not completely sure on your logic, but looking at Tiny Giant's gist, it seems like there are only three combinations:
1) Only dateu exists.
2) dateu and cityu exist.
3) dateu, cityu, and numberu exist.
So you don't care about the condition where dateu and numberu exist, but cityu is empty, right?
Okay, I reread your code. It seems like you have rows, and you have three possible filters. Filter rows based on AND, for example cityu AND dateu.
If so, here's a giant refactor. Just as a note, there may be some JavaScript errors because I had no way of testing this. But the code is fairly simple and straightforward:
var lst = [] // I'm not using these.
var lst2 = []
var lst3 = []
alert(cityu);
alert(dateu);
alert(numberu);
var getActiveFilters = function() {
// Edit possibleFilters as necessary.
// the key should reflect the header of the column
// and the value is a variable referring to the user-inputted string
var possibleFilters = {
'date': dateu,
'city': cityu,
'number': numberu
};
var activeFilters = {};
for (key in possibleFilters) {
if (possibleFilters[key].length > 0) {
activeFilters[key] = possibleFilters[key];
}
}
return activeFilters;
}
// just made this into a function to get it out of the callback
var functionAfterCsv = function(rows) {
var activeFilters = getActiveFilters();
var filteredList = [];
var addRow = false;
for(i = 0; i < rows.length; i++) {
// see if the current row matches all the filters present
for (key in activeFilters) {
if (rows[i][key] === activeFilters[key]) {
addRow = true;
} else {
addRow = false;
// if the row doesn't meet one of the conditions,
// there's no need to check the rest
break;
}
}
if (addRow) {
filteredList.push(row[i]);
}
}
return filteredList;
};
d3.csv("kyle.csv", function (d) {
return {
city: d.from,
number: d.phone_number,
date: d.from_date
};
}, function (error, rows) {
functionAfterCsv(rows);
})
});
If you're filtering using OR, then you'll need to change your comparison to something like this:
for (key in activeFilters) {
if (rows[i][key] === activeFilters[key]) {
addRow = true;
break; // if one is true, that's good enough for an OR
} else {
addRow = addRow || false;
}
}
if (addRow) {
filteredList.push(row[i]);
// reset addRow for the next row
addRow = false;
}
Hope this was closer to your intended logic!
I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.
EDIT**
In a game I am creating I use the next question button to move onto other questions in the grid if the user is having trouble with the current one. At the moment I have had real problems with it as it keeps on crashing my program, and not giving any console errors. The last problem I had with it was that it said "too much recursion". Since then I thought I had sorted the problem, but I have just done a few tests and it crashes every time.
This is the click event for the button...
//Next question click event
$('.next-question').bind("click", function() {
$('td').removeClass('highlight-problem');
shuffleEqually(listOfWords);
shuffleEqually(nextWordIndexes);
var rndWord = nextWordIndexes[Math.floor(Math.random())];
var rndWord = nextWordIndexes[2];
//Adds and removes nesesary classes
$('td[data-word="' + listOfWords[rndWord].name + '"]').addClass('highlight-problem');
$('td[data-word=' + word + ']').removeClass('wrong-letter').removeClass('wrong-word').removeClass('right-letter');
var spellSpace = $('td[data-word="' + listOfWords[rndWord].name + '"]').hasClass('right-word');
if (spellSpace) {
$('.next-question').trigger('click');
} else {
$("#hintSound").attr('src', listOfWords[rndWord].audio);
hintSound.play();
$("#hintPic").attr('src', listOfWords[rndWord].pic);
$('#hintPicTitle').attr('title', listOfWords[rndWord].hint);
}
});
I think it may have something to do with the if statement, but have tried changing it to this..
if (spellSpace == false) {
$("#hintSound").attr('src', listOfWords[rndWord].audio);
hintSound.play();
$("#hintPic").attr('src', listOfWords[rndWord].pic);
$('#hintPicTitle').attr('title', listOfWords[rndWord].hint);
}
and it makes it even worse
ShuffleEqually:
//Shuffles words to randomize
shuffleEqually(nextWordIndexes);
var shuffledWords = [];
shuffledWords = chosenWords.sort(function () {
return 0.5 - Math.random();
});
function shuffleEqually(a1, a2) {
var arrays = [];
if (typeof a1 === 'object' && a1.length > 0) {
arrays.push(a1);
}
if (typeof a2 === 'object' && a2.length > 0) {
arrays.push(a2);
}
var minLength = arrays[0].length;
jQuery.each(arrays, function (i, a) {
minLength = a.length < minLength ? a.length : minLength;
});
var randoms = [];
for (i = 0; i < minLength; i++) {
randoms.push(Math.random());
}
jQuery.each(arrays, function (i, a) {
var i = minLength;
while (i--) {
var p = parseInt(randoms[i] * minLength);
var t = a[i];
a[i] = a[p];
a[p] = t;
}
});
};
Hint sound:
var hintSound = $("#hintSound")[0];
Your issue is an infinite loop, plain and simple.
$('.next-question').bind("click", function() {
// binds click...
...
if (spellSpace) {
$('.next-question').trigger('click');
// triggers click ON THE SAME ELEMENT COLLECTION (same selector)
You want to refine this. I assume you want the trigger to work on the next question, so I suggest changing the second statement to:
$(".next-question").eq(($(".next-question").index($(this)) + 1) % $(".next-question").length).trigger("click");
You have a second infinite loop in shuffleEqually:
jQuery.each(arrays, function (i, a) {
var i = minLength;
while (i--) {
var p = parseInt(randoms[i] * minLength);
var t = a[i];
a[i] = a[p];
a[p] = t;
}
Change the while condition to have a limiting value, or it will loop endlessly (as a decrement operation always succeeds).