3

First of all, I've had a look on all the 'sleep' questions lying around (such as What is the JavaScript version of sleep()?) but I didn't find an acceptable solution.

I would like to make a visual education tool for all sort of algorithms. In order to do so, I'm using javascript with jQuery to display the data and paint it up nicely. In order to start it up, I want to do a sorting sample, where an array is displayed, shuffled and then sorted in a visually pleasing way. So what I want to happen is that two cells get highlighted (easy), possibly swapped (easy), and then there's a small delay before the next pair is tested (hard).

I understand there isn't an explicit 'sleep' method in javascript. However, to restructure the code into using setTimeout would imply rewriting all my algorithms recursively, which is a huge hinder (although obviously not impossible).

As a sample problem, take a look at a bubble sort sample:

function bubble_sort(arr){
    for(var i=0;i<arr.length;i++){
        for(var j=1;j<arr.length;j++){
            highlight(j-1);
            highlight(j);
            if(arr[j-1] > arr[j]){
                visible_swap(arr, j, j-1);
            }
            sleep(1000);
        }
    }
    exhibit_array(arr);
}

This can obviously rewritten recursively to work with setTimeout, but to do so on all the algorithms I have in mind would take a great deal of time. Am I missing something? Is there an 'easy' way to leave the implementations as they are and place sleeps at will?

EDIT: I found two solutions: a pretty one, and a compatible one. The pretty one only works on firefox, I'm afraid, and makes use of the wonderful yield semantics (There is some sample explanation here: https://developer.mozilla.org/en/New_in_JavaScript_1.7). This actually solves my problem perfectly, thus:

function bubble_sort(arr){
    for(var i=0;i<arr.length;i++){
        for(var j=1;j<arr.length;j++){
            highlight(j-1);
            highlight(j);
            if(arr[j-1] > arr[j]){
                visible_swap(arr, j, j-1);
            }
            yield true;
        }
    }
    yield false;
}
var bubble = bubble_sort(arr)
function gen(){
    if(bubble.next()){
        setTimeout(gen, 500);
    }
    else{
        alert("Done!");
    }
}

This works wonderfully for me, but does rely on the yield capability which currently is only supported on firefox. Notice that for this to work at all, you need to use <script type="text/javascript;version=1.7">. This however is perfect. It could have also worked for infinite algorithms, showing them toiling in vain if need be.

The second solution I found works as well, based on the answer below:

function bubble_sort(arr){
    var q = new Array();
    for(var i=0;i<arr.length;i++){
        for(var j=1;j<arr.length;j++){
            q[q.length] = [ [highlight, j-1 ], [highlight, j] ];
            if(arr[j-1] > arr[j]){
                swap(arr, j, j-1);
                q[q.length] = [ [visible_swap, j, j-1] ];
            }
        }
    }
    return q;
}
function play(q, step){
    if(q.length == 0)
        return;
    clear_highlights();
    cmds = q.shift();

    for(ind in cmds){
        cmd = cmds[ind];
        f = cmd.shift();
        f.apply(null, cmd);
    }
    setTimeout(function(){ play(q, step); }, step);
}

This works as well. This is pretty bothersome syntactically, but definitely works well on all browsers.

After all this though, it seems there are javascript 'extensions' which implement sleep-like syntax, which is obviously better than all of the above. Thanks for the help!

Community
  • 1
  • 1
Gilthans
  • 1,434
  • 1
  • 14
  • 23
  • Why can't you just use the method in http://stackoverflow.com/questions/951021/javascript-sleep ? Why is it unacceptable? – JoshNaro May 21 '12 at 18:51
  • 1
    @JoshNaro well the answers to that question are all about rewriting everything to work with `setTimeout()`, which the OP here is reluctant (though doomed) to do. – Pointy May 21 '12 at 19:08
  • "but to do so on all the algorithms I have in mind would take a great deal of time" [anti-pattern](http://en.wikipedia.org/wiki/Don%27t_Repeat_Yourself) detected. You should think twice before repeating code... And now you will add the same code to every piece of code and if future updates are needed, you will need to modify every one. If I were you, I would rewrite the whole code first – ajax333221 May 21 '12 at 19:16
  • @ajax333221 You misunderstood me. I have many DIFFERENT algorithms, all of which I want to display. There is no repeated code, these are completely different algorithms which I'd have to rewrite recursively. – Gilthans May 21 '12 at 20:16
  • @Gilthans when you finish the tool, could you please post a link here, it would be very interesting to see it. – Alexey Lebedev May 21 '12 at 20:30

6 Answers6

6

Recently I made a visualization of sub-palindrome finder algorithm, it used setTimeout and didn't require rewriting of the algorithm in recursive form.

See this example.

The general principle is to build up a stack of commands, for bubble sort that would be a stack of highlight and swap commands. Then you can have a function running each N milliseconds which takes a command from the stack and visualizes it.

commands = [
    ['highlight', 1, 5]
    ['swap', 1, 5]
    ['highlight', 3, 7]
    ...
];

setInterval(function() {
    var cmd = commands.shift();
    visualize(cmd);
}, 1300);

In my problem the finder algorithm was written in Python and was provided by the user, and I couldn't modify it. Fortunately Python allows to overload access and comparison operators and record each action the algorithm takes. RecString class. In JavaScript you can't do that, but that's not a problem in your case, because you can modify the original algorithm.

I can email you the JS source if you want, it was written in haste, but might be useful anyway.

Alexey Lebedev
  • 11,344
  • 3
  • 36
  • 46
  • 1
    That looks beautiful - but I was hoping to avoid using a different programming language. This did give me an idea. What if the sorting function didn't actually display the results right away, but only logged the required actions, to be later called? This might do the work. I'll try it and report back. – Gilthans May 21 '12 at 19:05
  • @Gilthans you could augment your actual sort algorithm to output the "moves" it makes while sorting as a separate list. You'd then perform the sort and "play back" the moves afterwards. – Pointy May 21 '12 at 19:10
  • @Gilthans, yes, that's exactly what I mean. Python was only an example of how sometimes you can avoid modifying the algorithm altogether. You can do almost the same in JavaScript if you define your own array class with methods for swap and comparison, and then internally record them, i.e. `arr.gt(j-1, j)` instead of `arr[j-1] > arr[j]` – Alexey Lebedev May 21 '12 at 19:25
2

Another idea - StratifiedJS. Here's a simple jsFiddle example:

<script src="http://code.onilabs.com/apollo/0.13/oni-apollo.js"></script>
<script type="text/sjs">
  for (var i = 1; i < 4; i++) {
      alert(i);
      hold(1000);
  }
</script>
Alexey Lebedev
  • 11,344
  • 3
  • 36
  • 46
  • This is perfect! I don't require compatibility or anything, so this fits in perfectly. Worse trouble I have with this is eclipse won't recognize my javascript anymore, but I'll find a way to deal. :) – Gilthans May 21 '12 at 20:18
  • How is this different than the code given in the question for http://stackoverflow.com/questions/951021/javascript-sleep ? function pausecomp(millis) { var date = new Date(); var curDate = null; do { curDate = new Date(); } while(curDate-date < millis); } – JoshNaro May 22 '12 at 14:29
  • 1
    @JoshNaro it doesn't block UI and doesn't cause high CPU utilization. StratifiedJS automatically rewrites your code to continuation-passing style. `hold()` calls translate to `setTimeout()`. – Alexey Lebedev May 22 '12 at 16:41
1

I would work with setTimeout, I believe that is the closest you are going to get to a "sleep" equivalent on the client-side.

Steve Binder
  • 2,130
  • 1
  • 12
  • 6
1

This answer doesn't solve the general case, but perhaps you can increment the interval for each instruction so that they run one second after each other.

function bubble_sort(arr){
    var interval = 0;  // increases with each loop
    for(var i=0;i<arr.length;i++){
        for(var j=1;j<arr.length;j++){
            (function(i, j) {
                setTimeout(function() {
                    highlight(j-1);
                    highlight(j);
                    if(arr[j-1] > arr[j]){
                        visible_swap(arr, j, j-1);
                    }
                }, interval);
            })(i, j);
            interval += 1000;
        }
    }
    exhibit_array(arr);
}

Thus, the first operation runs at once, the next runs after one second, the thrid after a total of two seconds, etc.

This solution provides the benefit of minimal code rewriting: just wrap your loop contents in a setTimeout (which is wrapped inside a closure with your loop variables) and add a line to increment interval after each loop iteration.

apsillers
  • 101,930
  • 15
  • 206
  • 224
0

Using setTimeout() is not recursion.

You can work with a closure to keep track of state. The for loops, however, have to be changed into while for this to work:

function bubbleSort(arr) {
  (function(i, j) { // <- this closes over i and j
    function nextSortStep() {
      while (i < arr.length) {
        while (j < arr.length) {
          highlight(j - 1);
          highlight(j);
          if (arr[j - 1] > arr[j]) {
            visibleSwap(arr, j, j - 1);
          }
          j++;
          return setTimeout(nextSortStep, 1000);
        }
        i++;
        j = 1;
        return setTimeout(nextSortStep, 1000);
      }
      exhibitArray(arr);
    }
    nextSortStep();
  })(0, 1); // <- loop initialization
}

As an aside, JavaScript is not PHP, function names generally are in camelCase.

Tomalak
  • 306,836
  • 62
  • 485
  • 598
  • This is exactly implementing bubble sort recursively. Just swap setTimeout with bubbleSort(i, j). Obviously this can be a solution (recursion is turing complete), but its not always a good one. – Gilthans May 21 '12 at 20:11
  • @Gilthans This is not recursion. `setTimeout()` registers a callback, which is placed in a queue and executed when the timer fires. The function stack will not grow over time. It is the equivalent to calling a function in a loop. – Tomalak May 21 '12 at 20:20
  • Calling a function in a loop IS recursion, from a programmer's PoV. My problem was never performance, but the overhead work of rewriting all the algorithms in a recursive paradigm. – Gilthans May 21 '12 at 20:57
  • @Gilthans Calling a function in a loop is not recursion. It is calling a function in a loop. It is like calling a function ten times on ten lines of code, which I'm sure you would not call "recursion". Calling a function from within itself is recursion, and my code above does not do that. – Tomalak May 21 '12 at 21:03
0

Following Lebedev's idea, I would store the "evolution of the sorting of the array" and then use setInterval() to show them. http://jsfiddle.net/mari/EaYRZ/

mari
  • 239
  • 1
  • 4