0

I am new to javascript so sorry if I am misunderstanding how the language does some stuff, I am building a sorting algorithms visualizer which orders blocks by their hue value (using chroma-js library) : Each item in screenObject.items is a Color object

//color objects are what I am sorting
class Color {
  constructor(div, color, value) {
    //this div on the html page
    this.div = div;
    this.color = color;
    //hue value of the color
    this.value = value;
  }
  update(color, value) {
    this.color = color;
    this.value = value;
    this.div.style.backgroundColor = color;
  }
}

class ScreenObject {
  constructor() {
    //this is an array of color objects
    this.items = [];
  }
  bubbleSort() {
    let solved = false;
    while (!solved) {
      let swaps = 0;
      this.items.forEach((item, index) => {
        if (index > 0) {
          swaps += compare(this.items[index - 1], item);
        }
      });
      if (swaps === 0) {
        solved = true;
      }
    }
  }
}

function compare(color1, color2) {
  if (color1.value > color2.value) {
    swap(color1, color2);
    return 1;
  } else {
    return 0;
  }
}

function swap(color1, color2) {
  colorStore = color1.color;
  valueStore = color1.value;
  color1.update(color2.color, color2.value);
  color2.update(colorStore, valueStore);
}

The issue I have is that this colors only update after the program is completed, and if I add an setIterval, or setTimeout I have only been able to make the colors update after each pass, instead of after each comparison/swap (I want to add special styling when the colors are being compared):

  bubbleSort() {
    let solved = false;
    while (!solved) {
      let swaps = 0;
      setInterval(() => {
        this.items.forEach((item, index) => {
          if (index > 0) {
            swaps += compare(this.items[index - 1], item);
          }
        });
      }, 50);
      if (swaps === 0) {
        solved = true;
      }
    }
  }

I want to be able to see the colours update after every single comparison for example swap(1, 2) the user sees 1 get 2's color and 2 get 1's color.

Thanks in advance!

Micheal Nestor
  • 91
  • 1
  • 10
  • 1
    You can't pause a loop. The solution here is to not use a loop in the first place and just use a recursive `setTimeout()` or a `setInterval() with a counter. Each time the timer callback fires, you update the counter and you have the effect of a timed loop without an actual loop. – Scott Marcus May 26 '20 at 15:32
  • You can add `setInterval` inside `forEach` loop – Kiran Shinde May 26 '20 at 15:34
  • @Kenny That is essentially what the OP is doing now and it doesn't solve the problem because the interval callbacks will just stack up in the event queue and won't fire until the loop is finished, then all the callbacks will just fire one after the other with no interval between them. – Scott Marcus May 26 '20 at 15:37

1 Answers1

0

I'm going to assume you're doing this on a browser. You need to yield back to the event loop in order for other things to happen, such as repainting the page. Probably the simplest thing is to make your bubbleSort an async method and have it await something, such as a timer callback:

async bubbleSort() { // <=== Note the `async`
  let solved = false;
  while (!solved) {
    let swaps = 0;
    // *** No `setInterval`
    for (const [index, item] of this.items.entries()) {
      if (index > 0) {
        const result = compare(this.items[index - 1], item);
        if (result) {         // *** Did it do something?
          swaps += result;
          await delay(0);     // *** Wait for it to be redrawn
        }
      }
    });
    if (swaps === 0) {
      solved = true;
    }
  }
}

...where delay might be:

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

When you call bubbleSort, it will return a promise almost immediately, and continue its logic asynchronously until it's done, then settle the promise.

Note that async functions were added in ES2018. The're well-supported by modern browsers, but you may need a tool like Babel to transpile for older ones.


If you want to be even more precise, you could use requestAnimationFrame rather than setTimeout with a zero-length timeout. Here's a somewhat counter-intuitive function for that:

const nextFrame = (cb = () => {}) => new Promise(resolve => {
    requestAnimationFrame(() => {
        cb();
        resolve();
    });
});

...which you'd use in place of delay:

await nextFrame();

(In your case, you don't need to pass any callback, the default do-nothing callback is sufficient.)

I explain the reason for the odd design of that function in this tweet (which in turn is asking whether it really needs to be designed that oddly).


Another approach is to invert your logic a bit and use a generator function that generates each swap. Then you can drive that generator in a loop. That's the approach I used when answering this other question about visualizing buble sort.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • *(Oops! If you see `item, index` instead of `index, item` in the `for (const [/*...*/] of this.items.entries())` line above, hit refresh.)* – T.J. Crowder May 26 '20 at 15:46