1

I'd like to build a text string by inserting the characters at random, but in place order (as a kind of effect) . So far I've got:

// make a string and an array
var input = "Hello, world!",
    output = [];
// split the string
input = input.split('');

My idea is then to call this

function addAnElement(){
  // check if there are any left
  if(input.length){
     // pick an element at random
     var rand = Math.floor(Math.random() * input.length);
     // remove it, so we don't call it again
     var element = input.splice(rand,1);
     // insert it
     output[rand] = element;
     // use the string returned as new innerHTML, for example 
     return output.join('');
     // repeat until finished
     setTimeout(addAnElement,5);
  }
}

I'm hoping this would return something like:

'e'
'er'
...
'Hel, or!'
...
'Helo, Word!'
... and finally ...
'Hello, World!'

The problem, of course, is that the array is re-indexed when spliced - and this yields gibberish. I think the answer must be to link the elements to their positions in input and then insert them intact, sorting by key if necessary before returning. How do I do this?

Isaac Lubow
  • 3,557
  • 5
  • 37
  • 53

2 Answers2

1

How about something like this:

var input = 'Hello world',
    inputIndexes = [],
    output = [];

for (var i = 0; i < input.length; i++) { 
    inputIndexes[i] = i;
};

function addAnElement() {
    if (inputIndexes.length) {
        var rand = Math.floor(Math.random() * inputIndexes.length);
        var element = inputIndexes.splice(rand, 1);
        output[element] = input[element];
        //console.log(output.join(' '));
        document.getElementById('text').innerHTML = output.join(' ');
        setTimeout(addAnElement, 2000);

    }
}

addAnElement();

http://jsfiddle.net/fg2ybz8j/

kriskot
  • 303
  • 1
  • 8
0

You can avoid it by not using splice. Instead, clear an entry when you've used it, and keep a count of the entries you've cleared.

E.g.:

var entriesLeft = input.length;
function addAnElement(){
   // pick an element at random, re-picking if we've already
   // picked that one
   var rand;
   do {
      rand = Math.floor(Math.random() * input.length);
   }
   while (!input[rand]);

   // get it
   var element = input[rand];
   // clear it, so we don't use it again
   input[rand] = undefined;
   // insert it
   output[rand] = element;
   // repeat until finished
   if (--entriesLeft) {
       setTimeout(addAnElement,5);
   }
   // use the string returned as new innerHTML, for example 
   return output.join('');
}

Of course, that loop picking a random number might go on a while for the last couple of characters. If you're worried about that, you can create a randomized array of the indexes to use up-front. This question and its answers address doing that.

Live Example:

var input = "Hello, there!".split("");
var output = [];
var entriesLeft = input.length;

function addAnElement() {
  // pick an element at random, re-picking if we've already
  // picked that one
  var rand;
  do {
    rand = Math.floor(Math.random() * input.length);
  }
  while (!input[rand]);

  // get it
  var element = input[rand];
  // clear it, so we don't use it again
  input[rand] = undefined;
  // insert it
  output[rand] = element;
  // repeat until finished
  if (--entriesLeft) {
    setTimeout(addAnElement, 5);
  }
  // use the string returned as new innerHTML, for example 
  document.body.innerHTML = output.join('');
}

addAnElement();

Side note: Notice how I've moved the call to setTimeout before the return. return exits the function, so there wouldn't be any call to setTimeout. That said, I'm confused by the need for the return output.join(''); at all; all calls but the first are via the timer mechanism, which doesn't care about the return value. In the live example, I've replaced that return with an assignment to document.body.innerHTML.


Here's a demonstration of the method that shuffles an array of indexes instead. It uses the shuffle method from this answer, but I'm not saying that's necessarily the best shuffle method.

function shuffle(array) {
  var tmp, current, top = array.length;
  if (top)
    while (--top) {
      current = Math.floor(Math.random() * (top + 1));
      tmp = array[current];
      array[current] = array[top];
      array[top] = tmp;
    }
  return array;
}

var input = "Hello, there".split("");
var output = [];
var indexes = input.map(function(entry, index) {
  return index;
});
shuffle(indexes);
var n = 0;

function addAnElement() {
  // get this index
  var index = indexes[n];
  // get this loop's element
  var element = input[index];
  // insert it
  output[index] = element;
  // repeat until finished
  if (++n < indexes.length) {
    setTimeout(addAnElement, 5);
  }
  // use the string returned as new innerHTML, for example 
  document.body.innerHTML = output.join("");
}

addAnElement();
Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Yeah, I don't need to return anything to make the function work, but in my actual code I'm doing the writing from within the function as you do in your snippet above with `document.body.innerHTML` – Isaac Lubow Dec 14 '15 at 08:29
  • Thanks for this. I had tried setting the values to `undefined` to preserve the array keys, but the actual string is far longer than that and it seems like a waste of power to roll a 1000-sided die over and over in search of what we all know should be the answer. – Isaac Lubow Dec 14 '15 at 08:31
  • @IsaacLubow: That's why I linked to the question and answers about creating a randomized array (e.g., of the indexes). Then you just loop through the randomized array of indexes, and use the characters at those indexes. That said, a quick test on Chrome suggests a modern JavaScript engine will find that one-in-a-thousand number in a couple of tenths of a millisecond. Naturally, test on your target browsers/devices. – T.J. Crowder Dec 14 '15 at 08:31
  • Yes, we can shuffle the input, but how then do we know where the [randomly chosen character] at index [0] goes within the output string? The keys aren't preserved. – Isaac Lubow Dec 14 '15 at 08:36
  • @IsaacLubow: You don't shuffle the input. You create a second array of indexes, and shuffle that. Then loop through that array, using each index. – T.J. Crowder Dec 14 '15 at 08:37
  • @IsaacLubow: :-) I was worried I was being unclear, so I added an example to the answer -- but you got there. – T.J. Crowder Dec 14 '15 at 08:45
  • @IsaacLubow: Did you have a further question about the above? If not, please accept the answer so the question goes off the list of unanswered questions, or people will continue to spend their time answering it. – T.J. Crowder Dec 14 '15 at 09:04