简体   繁体   中英

Javascript Shuffle Array from Txt File

I am looking to pull 5 "random" lines from a text file while not repeating any. Each line from the text file has html code which would be inserted into a side menu. I have read up on the Fisher–Yates shuffle but not sure how to incorporate it in this manner with javascript. Presently I have the following throwing an error:

var request = new XMLHttpRequest();
request.onload = function() {
    var i = 0;
    // get the file contents
    var fileContent = this.responseText;
    // split into lines
    var fileContentLines = fileContent.split( '\n' );

    var target = document.getElementById( 'random-testimonial' );
    var targetHTML = target.innerHTML;

    while ( i < 5 ) {
        // get a random index (line number)
        var randomLineIndex = Math.floor( Math.random() * fileContentLines.length );
        // extract the value
        var randomLine = fileContentLines[ randomLineIndex ];

        // add the random line in a div if not duplicate            
        if ( ! targetHTML.contains(randomLine) ) {
            targetHTML += randomLine;
            i += 1;
        }
    }

    target.innerHTML = targetHTML;
};
request.open( 'GET', 'content.txt', true );
request.send();

and

<div id="random-content"><script src="content.js"></script></div>

Error:

content.js:19 Uncaught TypeError: targetHTML.contains is not a functionrequest.onload @ content.js:19

OK, so the way Fisher-Yates shuffle works is

  • get a random index, r , from the input array
  • copy element r from the input array to the output array
  • remove element r from the input array
  • repeat n times, where n is the length of the input array

By the end of the loop, the output will be a shuffled copy of the entire input array.

I'm going to glance over one small gotcha: this algorithm shouldn't mutate the input array. Instead it should leave the input array untouched and return a new array that is a shuffled copy of the input array. (You can see how this is done in my implementation below).

So knowing how Fisher-Yates works, in your case, we don't have to shuffle the entire array since you know before-hand that you only want N elements.

Let's first look at your input.

var fileContent = this.responseText;
var fileContentLines = fileContent.split( '\n' );

OK, perfect. You've defined the input array, fileContentLines . Now let's make a function to sample some random elements from it

// fisher-yates sample
// • sample n elements from xs
// • does not mutate xs
// • guarantees each sampled element is unique
function sample (n,xs) {
  function loop(i, output, input, len) {
    if (i === n) return output;                   // loop exit condition
    let r = Math.floor(Math.random() * len);      // rand number between 0 and len
    return loop(                                  // continue loop
      i + 1,                                      // increment loop counter
      output.concat(input[r]),                    // copy element r from input
      input.slice(0,r).concat(input.slice(r+1)),  // remove element from input
      len - 1                                     // decrement length
    );
  }
  return loop(0, [], xs, xs.length);              // begin loop
}

Alright ! Let's first check it with a simple input

// sample 3 random inputs from numbers 1 through 10
console.log(sample(3, [1,2,3,4,5,6,7,8,9,10])); //=> [9,7,5]

Perfect. Now just call it on your array of lines

var result = sample(5, fileContentLines);
console.log(result); // ...

The above code works, but let's not stop here. Our code is taking on too much responsibility and we can separate some of the behaviours into reusable functions.

// get rand number between 0 and n
function rand(x) {
  return Math.floor(Math.random() * x);
}

// splice of xs at index i
// • return a new output array
// • does not mutate xs
function del(i,xs) {
  return xs.slice(0,i).concat(xs.slice(i+1));
}

// fisher-yates sample
// • sample n elements from xs
// • does not mutate xs
// • guarantees each sampled element is unique
function sample (n,xs) {
  function loop(i, output, input, len) {
    if (i === n) return output;       // loop exit condition
    let r = rand(len);                // rand number between 0 and len
    return loop(                      // continue loop
      i + 1,                          // increment loop counter
      output.concat(input[r]),        // copy element r from input
      del(r,input),                   // remove element from input
      len - 1                         // decrement length
    );
  }
  return loop(0, [], xs, xs.length);  // begin loop
}

// fisher-yates shuffle
// • does not mutate xs
function shuffle(xs) {
  return sample(xs.length, xs);
}

Let's take a quick look at the individual behaviour of each function

// generate random number between 0 and 10 (exclusive)
console.log(rand(10)); //=> 5

// delete 2nd letter from letters a through d
console.log(del(1, ['a', 'b', 'c', 'd'])); // => ['a', 'c', 'd]

// sample 3 random inputs from numbers 1 through 10
console.log(sample(3, [1,2,3,4,5,6,7,8,9,10])); //=> [9,7,5]

// shuffle entire input array
console.log(shuffle([1,2,3,4,5,6,7,8,9,10])); //=> [8,9,1,3,7,6,10,5,4,2]

And there you have it: 4 functions for the price of 1 . In my opinion, this is a much better way to solve the problem because each function is useful on its own and therefore can be used in multiple places. Having lots of little reusable functions will drastically reduce the amount of work you'll have to do in the future.


With all of this complexity nicely compartmentalized, let's see what your final code might look like.

function createParagraph(text) {
  var p = document.createElement('p');
  p.innerHTML = text;
  return p;
}

var request = new XMLHttpRequest();
request.onload = function() {

  var fileContent = this.responseText;
  var fileContentLines = fileContent.split('\n');
  var target = document.getElementById('random-testimonial');

  sample(5, fileContentLines).map(function(testimonial) {
    var p = createParagraph(testimonial);
    target.appendChild(p);
  });
};
request.open('GET', 'content.txt', true);
request.send();

PS I highly recommend you write reusable functions for your ajax requests, or better, use a library. Writing them by hand is extremely cumbersome and error prone. Most people use jQuery but lately I've been reaching for axios

var request = new XMLHttpRequest();
request.onload = function() {
    var i = 0;
    // get the file contents
    var fileContent = this.responseText;
    // split into lines
    var fileContentLines = fileContent.split( '\n' );

    var target = document.getElementById( 'random-testimonial' );

    var HTMLLines = [];

    while ( i < 5 ) {
        // get a random index (line number)
        var randomLineIndex = Math.floor( Math.random() * fileContentLines.length );
        // extract the value
        var randomLine = fileContentLines[ randomLineIndex ];

        // add the random line if not duplicate            
        if ( HTMLLines.indexOf(randomLine) === -1) {
            HTMLLines.push(randomLine);
            i += 1;
        }
    }

    target.innerHTML = HTMLLines.join('\n');
};
request.open( 'GET', 'content.txt', true );
request.send();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM