【问题标题】:Javascript, Array shuffle but keeping a certain distanceJavascript, Array shuffle 但保持一定距离
【发布时间】:2014-04-23 16:33:15
【问题描述】:

我需要一个函数来打乱一组元素,但是,保持元素之间的最小距离为 2。

让我解释一下我的情况,我有一个不间断的输入,它每 200 毫秒不断向我发送单词。我还有一个带有预定义单词的独特数组。

我收到的每个单词,我都会检查它是否正常,如果在我的条件下正常,我想将它放入我的数组中。但是,在那一刻,我不再需要一个唯一的数组,所以我的数组中可能会有双精度数,事实上,我希望有适合我条件的双精度词。

好的,所以,问题来了,我收到的每个 OK 字,我想把它放在数组中,然后随机播放,它可以是一个双精度数,但是,在这种情况下,我想在这之间保持距离单词和数组中其他相同的单词。

示例:
唯一数组:[foo, bar, baz, qux]
我启动程序,它每 200 毫秒向我发送一次单词。
得到:fubar > 好的
插入:[foo, bar, baz, qux, fubar]
得到:橙色 > 好的
插入:[foo, bar, baz, qux, fubar, orange]
得到:石灰 > 好的
插入:[foo,bar,baz,qux,fubar,orange,lime]
得到:qux > OK (DOUBLE)

插入正确的方式:[foo, bar, baz, qux, fubar, orange,lime, qux]
插入正确的方式:[foo, qux, bar, baz, qux, fubar, orange,lime]
插入错误的方式:[foo, bar, baz, qux, qux, fubar, orange,lime]

第一个距离为 3,第二个距离为 2... 第三个和错误的,距离为0。

谁能给我一个好的方法和/或逻辑来做到这一点?对于这种情况,最小距离 = 2。

提前谢谢你。


编辑 1: 用户 jfriend00 显示了数组被双精度数填满的可能性,假设它们都有最小距离,并且下一个要插入的元素没有合适的位置,那么,我不能插入它。


编辑 2: 我还认为我必须避免插入可能无法进行下一次插入的位置,如下所示:

得到:苹果 > 好的
(A) 插入正确的方式:[baz, qux, fubar, orange,lime, apple]

得到:qux > OK (DOUBLE)
(A) 插入正确的方式:[baz, qux, fubar, orange, qux,lime, apple]
(B) 插入方式错误:[baz, qux, fubar, orange,lime, qux, apple]

在这里,插入被插入距离 3 (B) 处的 qux “截断”。

得到:qux > OK (DOUBLE)
(A) 插入正确的方式:[baz, qux, fubar, orange, qux,lime, apple, qux]
(B) 插入方式错误:[baz, qux, fubar, orange,lime, qux, apple]

【问题讨论】:

  • 您是否意识到一个项目可能有如此多的副本,以至于您无法将其插入到数组中,除非它位于另一个副本旁边?
  • 现在我确实这样做了,在这种情况下,我一定不能插入到数组中。

标签: javascript arrays distance shuffle


【解决方案1】:

这是一次添加一个项目的算法。基本思路是这样的:

  1. 获取要添加到列表中的项目
  2. 如果列表为空,直接添加即可
  3. 如果列表不为空,则生成一个包含所有可能位置的数组,可以将其添加到列表中
  4. 随机选择其中一个可能的位置
  5. 通过检查当前位于该位置的项目及其之前的项目来检查该位置是否合法。如果合法,请将其添加到您刚刚检查的两项之间。
  6. 如果该位置不合法,则从所有可能位置的数组中删除该位置并随机选择另一个位置。将其从数组中移除可确保您不会再次尝试相同的非法位置
  7. 重复直到找到合法位置(因此您将其插入到那里)或直到您用完可能的位置(因为您检查了所有可能的位置,所以没有地方放置它)

工作演示:http://jsfiddle.net/jfriend00/5xGV3/

var sourceOfItems = ["apple", "grapefruit", "orange", "lime", "pear", "peach", "apricot"];

function insertNextItem(list, item) {

    // special case an empty items array
    if (list.length === 0) {
        list.push(item);
        return true; 
    }

    // build an array of possible insertion positions
    // items.length + 1 means to add it at the end
    var possiblePositions = [];
    for (var i = 0; i < list.length + 1; i++) {
        possiblePositions.push(i);
    }

    // select random position, see if that is allowed
    // if not, remove it from the possibilities and select a new random position
    while (possiblePositions.length > 0) {
        var randIndex = Math.floor(Math.random() * possiblePositions.length);
        var pos = possiblePositions[randIndex];
        // check if this position is allowed
        if (list[pos] !== item && (pos === 0 || list[pos - 1] !== item)) {
            // position is allowed, insert it and return
            list.splice(pos, 0, item);
            return true;
        } else {
            // was not allowed, so remove this possiblePositions choice
            // and let the loop try again
            possiblePositions.splice(randIndex, 1);
        }
    }
    return false;    
}

function buildList(sourceItems, num) {
    var items = [];
    for (var i = 0; i < num; i++) {
        insertNextItem(items, sourceOfItems[Math.floor(Math.random() * sourceOfItems.length)]);
    }
    return items;
}

function go() {
    var list = buildList(sourceOfItems, 20);
}

对于可以处理任何最小距离的更通用版本,请参阅此处的 jsFiddle:http://jsfiddle.net/jfriend00/LxWNa/

附:在查看 jsFiddle 时,查看调试控制台输出非常有趣,因为它显示了哪些位置已尝试但不起作用,哪些项目无法插入到数组中,因为它没有合法的位置。

【讨论】:

    【解决方案2】:

    这不会在之后积极添加项目,而是会随机打乱数组! 也许它会帮助激发一种只在之后添加索引的方法?希望对您有所帮助!

    //Shuffles an array randomly
    var shuffle = function (arrayinput){
      //loops around shuffling for four times the length of the array
      for(i= 0; i<(4 * arrayinput.length); i++){
        //Math.random generates a pseudo-random number between 0 and 1
        //that multiplied by arrayinput.length gives us a random number between 0 and
        //the array length. Math.floor rounds the number down, so we have a pseudo-random
        //number between 0 and the array length - 1, which is all of the array's indexes.
        var a = arrayinput[Math.floor(Math.random() * (arrayinput.length))];
        //saves the value at index a as var b
        var b = arrayinput[a];
        //deletes the one random value at index a from the array
        arrayinput.splice(a, 1);
        //adds that random value back to the beginning of the array
        arrayinput.unshift(b);
      }
      //returns shuffled array
      return arrayinput;
    }
    

    【讨论】:

      【解决方案3】:

      这并不简单,我必须承认。让我们从一些辅助函数开始:

      Array.prototype.unique = function() {
          var i = 0, t = 1, l = (this.length >>> 0) - 1;
          for (; i < l; t = ++i + 1)
              while ((t = this.indexOf(this[i], t)) !== -1)
                  this.splice(t, 1);
          return this;
      };
      Array.prototype.count = function(item) {
          var i = 0, t = 0;
          while (i = this.indexOf(item, i) + 1) t++;
          return t;
      };
      Array.prototype.remove = function(item) {
          var i = this.indexOf(item);
          if (i !== -1) this.splice(i, 1);
          return this;
      };
      

      我希望意思清楚,即使代码看起来晦涩难懂。 Array.prototype.indexOf 也有适用于旧版浏览器(基本上是 IE8)的 polyfill。

      现在是代码:

      function shuffleArray(array, previous) {
          if (!array.length) return array;
          // The list of unique items in the array
          var univals = array.slice().unique(),
              idx, pivot, rest;
          if (previous != null)
              // The array can't start with the previous element
              univals.remove(previous);
      
          while (univals.length) {
              // We choose an element from the possible ones
              idx = Math.floor(Math.random() * univals.length);
              pivot = univals[idx];
              // We try to shuffle the rest of the array.
              rest = shuffleArray(array.slice().remove(pivot), pivot);
              // If we got a shuffled array, we're done.
              // Else, we remove our pivot element from the possible ones
              if (rest != null)
                  return [pivot].concat(rest);
              else univals.splice(idx, 1);
          }
          return null;
      }
      

      用法是:

      shuffleArray(array);
      

      您可以添加第二个可选参数,以避免从某个元素开始。它在函数本身内部递归使用。如果您得到null,则无法使用给定的约束对数组进行洗牌。

      请记住,可以优化此算法。特别是,您可以更改它,使其不是递归的,而是迭代的,更适合更大的数组。

      【讨论】:

      • 我通常避免向Array 原型添加可枚举的方法,因为当有人编写代码来迭代像for (x in array) 这样的数组时,他们的代码将会中断,因为它会遍历您的方法。现在,你不应该像那样迭代数组,但是人们这样做我避免了这种做法。
      • @jfriend00 没错,扩展Array.prototype 通常已被弃用。但对我来说,这几乎不是原因——更多的是与 Ecmascript 中定义的新方法可能发生冲突,以及在原型中调用方法比调用静态函数慢的事实。为了简洁起见,我确实喜欢这样做。
      • 是的,我也喜欢向原型添加方法的简洁性。对于不太可能使用for (x in obj) 进行迭代的其他对象,这不是问题。
      • 太棒了!我在那里做了几乎同样的事情。 =)
      猜你喜欢
      • 1970-01-01
      • 2015-12-02
      • 2017-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-22
      • 2011-05-16
      • 1970-01-01
      相关资源
      最近更新 更多