【问题标题】:Javascript random generator with no repeats till all items have been displayedJavascript 随机生成器,在显示所有项目之前不重复
【发布时间】:2020-11-18 07:15:48
【问题描述】:

我已经阅读了很多关于这个常见问题的其他帖子,我鼓励任何看到这个问题的人通读整个帖子。我发现的其他解决方案都不适合我(我在下面列出了失败的尝试)。

我有一个使用 HTML 和 JavaScript 的正常运行的随机生成器。每次按下按钮时,该函数都会从数组中选择一个项目并使用:'document.getElementById' 显示它。有关工作功能,请参见下面的 sn-p。我的问题是我不喜欢它背靠背或在其他一些项目被看到之前显示相同数组项目的方式;该功能太随机了。我一直在努力寻找一种方法来更改我的随机函数,以便它只在整个数组循环后才显示重复项。

var oddnumber = [
  '111',
  '222',
  '333',
  '444',
  '555',
]
var oddletter = [
  'AAA',
  'BBB',
  'CCC',
  'DDD',
  'EEE',
]
function newThing() {
  if(numberCheck.checked) {
    var randomY = oddnumber;
    }
  if(letterCheck.checked) {
    var randomY = oddletter;
  }
  var randomX = Math.floor(Math.random() * (randomY.length));
  var y = randomY;
  var x = randomX;
  document.getElementById("thingDisplay").innerHTML = y[x];
}
<body>
  <div id='thingDisplay'></div>
  <div>
    <button class="box" id="button01" onclick="newThing()">New Thing</button>
  </div>
  <div>
    <form>
      Number<label>&nbsp;<input type="radio" name="thing" id="numberCheck"/></label>
      <br/>Letter<label>&nbsp;<input type="radio" name="thing" id="letterCheck"/></label>
    </form>
  </div>
</body>

许多答案详细说明了对数组进行切片并将切片推到底部的不同方法,但我不确定这是否是我正在寻找的。将显示的项目放在单独的数组中是我想避免的事情,因为在现实世界中我可能会使用数千个数组项目,所以它可能效率不高。

尝试 1 失败:

var oddnumber = [
  '111',
  '222',
  '333',
  '444',
  '555',
]
var oddletter = [
  'AAA',
  'BBB',
  'CCC',
  'DDD',
  'EEE',
]
function newThing() {
  if(numberCheck.checked) {
    var randomY = oddnumber;
    }
  if(letterCheck.checked) {
    var randomY = oddletter;
  }
  
  var res = randomY.sort(function() {
    return 0.5 - Math.random();
  });
  console.log(res.slice(randomY,1))
  document.getElementById("thingDisplay").innerHTML = [console.log];
}
<body>
  <div id='thingDisplay'></div>
  <div>
    <button class="box" id="button01" onclick="newThing()">New Thing</button>
  </div>
  <div>
    <form>
      Number<label>&nbsp;<input type="radio" name="thing" id="numberCheck"/></label>
      <br/>Letter<label>&nbsp;<input type="radio" name="thing" id="letterCheck"/></label>
    </form>
  </div>
</body>

尝试 2 失败:

var oddnumber = [
  '111',
  '222',
  '333',
  '444',
  '555',
]
var oddletter = [
  'AAA',
  'BBB',
  'CCC',
  'DDD',
  'EEE',
]
function newThing() {
    if(numberCheck.checked) {
        var randomY = oddnumber;
    }
    if(letterCheck.checked) {
        var randomY = oddletter;
    }
    var selected;
    var temp;
    var str = "";
    var stub = "";

    for(var i = 0; i < randomY.length; i++){
        temp = randomY[i][Math.floor(Math.random() * randomY[i].length)];
        while(selected.contains(temp)){
            temp = randomY[i][Math.floor(Math.random() * randomY[i].length)];
        }
    selected.push(temp);
    str += temp;
    if(i < randomY.length - 1){str += stub;}
    }

    var x = i;
    document.getElementById("thingDisplay").innerHTML = y[x];
}
<body>
  <div id='thingDisplay'></div>
  <div>
    <button class="box" id="button01" onclick="newThing()">New Thing</button>
  </div>
  <div>
    <form>
      Number<label>&nbsp;<input type="radio" name="thing" id="numberCheck"/></label>
      <br/>Letter<label>&nbsp;<input type="radio" name="thing" id="letterCheck"/></label>
    </form>
  </div>
</body>

编辑: 保留确定 randomY 值的“document.getElementById”和“if”语句至关重要。

【问题讨论】:

标签: javascript html math button random


【解决方案1】:

规范的方法是简单地使用例如洗牌你的数组。 Fisher-Yates shuffle 算法,然后从中按顺序选择项目,直到您用尽列表,此时您再次洗牌。 (此时,您可以注意看到新洗牌的列表中的第一项不是您最后选择的。)

// To make it easier to store the states for the "decks" we can pick items from,
// they're stored in an object, not in free variables:

const values = {
  "number": [
    '111',
    '222',
    '333',
    '444',
    '555',
  ],
  "letter": [
    'AAA',
    'BBB',
    'CCC',
    'DDD',
    'EEE',
  ],
};

// Keeps track of the shuffled pile of values per type.
const shuffledValues = {};

// Keeps track of the last value picked per type.
const lastValue = {};

// via https://bost.ocks.org/mike/shuffle/
function shuffle(array) {
  let m = array.length,
    t, i;
  while (m) {
    i = Math.floor(Math.random() * m--);
    t = array[m];
    array[m] = array[i];
    array[i] = t;
  }
  return array;
}

function populateDebug() {
  document.getElementById("debug").value = `
shuffledValues = ${JSON.stringify(shuffledValues)}
lastValue = ${JSON.stringify(lastValue)}
`.trim();
}

function newThing() {
  const checkedTypeRadio = document.querySelector("input[name=thing]:checked");
  const type = checkedTypeRadio ? checkedTypeRadio.value : null;
  if (!values[type]) return; // No type chosen, or it is an invalid one.
  if (!shuffledValues[type] || !shuffledValues[type].length) {
    // No shuffled values left? Shuffle a new one,
    // and take care that the new first value is
    // not the last value we picked.
    do {
      shuffledValues[type] = [...shuffle(values[type])];
    } while (shuffledValues[type][0] === lastValue[type]);
  }
  // Pick off the next value from the shuffled pile.
  const nextValue = shuffledValues[type].shift();
  // Save it for the take-care check.
  lastValue[type] = nextValue;
  // Print it out.
  document.getElementById("thingDisplay").innerHTML = nextValue;

  populateDebug();
}
<div id='thingDisplay'></div>
<button class="box" id="button01" onclick="newThing()">New Thing</button>
<div>
  <label>Number&nbsp;<input type="radio" name="thing" value="number" checked></label>
  <label>Letter&nbsp;<input type="radio" name="thing" value="letter"></label>
</div>
<textarea id="debug" rows=5 cols=60 placeholder="Ssshh, don't tell anyone about this secret debug area"></textarea>

【讨论】:

  • 这可能很有用。有没有办法洗牌数组的副本,而不是实际的东西本身?我的生成器还有一个有序模式,它从上到下一次显示一个项目。我担心改组数组会破坏“有序”模式。
  • 浅拷贝数组,例如const copiedArray = [...array];,然后随机播放。
  • 恐怕我无法让它工作。您能否编辑您的答案以包含一个 sn-p?
  • @guyw 添加了一个示例。 :)
【解决方案2】:

如果您不想通过改组来更改原始数组,可以将未使用的项目存储在单独的数组中,然后在外出时将其重置。查看 cmets:

var oddnumber = [
  '111',
  '222',
  '333',
  '444',
  '555',
]
var oddletter = [
  'AAA',
  'BBB',
  'CCC',
  'DDD',
  'EEE',
]

// to store unused items
var unused_letters = [];
var unused_numbers = [];

function newThing() {
  if (numberCheck.checked) {
    // if there are no unused items, copy them form the source array
    if (!unused_numbers.length) unused_numbers = [...oddnumber];
    var randomY = unused_numbers;
  }
  if (letterCheck.checked) {
    // if there are no unused items, copy them form the source array
    if (!unused_letters.length) unused_letters = [...oddletter];
    var randomY = unused_letters;
  }
  var randomX = Math.floor(Math.random() * (randomY.length));

  var y = randomY;
  var x = randomX;
  document.getElementById("thingDisplay").innerHTML = y[x];

  // remove randomx from the unused array since it's been used now
  randomY.splice(randomX, 1);
}
<body>
  <div id='thingDisplay'></div>
  <div>
    <button class="box" id="button01" onclick="newThing()">New Thing</button>
  </div>
  <div>
    <form>
      Number<label>&nbsp;<input type="radio" name="thing" id="numberCheck"/></label>
      <br/>Letter<label>&nbsp;<input type="radio" name="thing" id="letterCheck"/></label>
    </form>
  </div>
</body>

【讨论】:

    【解决方案3】:

    好吧,正如我之前所说,你所要做的就是

    1. 从当前数组创建副本
    2. 从复制的数组中选择一个元素
    3. 弹出之前选择的项目
    4. 从剩余的中选择
    5. 这样做直到所有项目都用完
    6. 所有这些都重新做一遍 (回到第 1 步)

    这就是生成不重复的随机索引所需的全部内容。所以除了算法部分,由于主函数包含一个closure(它会神奇地记住切片数组的前一个值),所以你只需要调用主函数一次,当radio check 得到改变,所以你需要监听 radio change 事件来调用包含算法闭包的 main 函数。那么为避免在 HTML 元素上使用unobtrusive effect 而不是使用onclick,请对按钮单击事件使用addEventListener 方法。

    所以你的最终代码应该是这样的:

    var chooser = null;
    var radios = document.querySelectorAll('input[type=radio]');
    var button = document.getElementById('button01');
    var oddnumber = ['111', '222', '333', '444', '555'];
    var oddletter = ['AAA', 'BBB', 'CCC', 'DDD', 'EEE'];
    
    function randomNoRepeats(array) {
      var copy = array.slice(); // Create a copy of input array
      return function() {
        if (copy.length < 1) { // This line exist to create copy and make a new array from actual array whenever all possible options are selected once
          copy = array.slice();
        }
        var index = Math.floor(Math.random() * copy.length); // Select an index randomly
        var item = copy[index]; // Get the index value
        copy.splice(index, 1); // Remove selected element from copied array
        return item; // Return selected element
      };
    }
    
    Array.from(radios).forEach(radio => { // Listening to all radio inputs change events
      radio.addEventListener('change', function() {
        if (numberCheck.checked) {
          chooser = randomNoRepeats(oddnumber);
        }
        if (letterCheck.checked) {
          chooser = randomNoRepeats(oddletter);
        }
      });
    });
    
    button.addEventListener('click', function() { // Listen to submit button click
      document.getElementById("thingDisplay").innerHTML = chooser();
    });
    <div id='thingDisplay'></div>
    <div>
      <button class="box" id="button01">New Thing</button>
    </div>
    <div>
      <form>
        Number<label>&nbsp;<input type="radio" name="thing" id="numberCheck"/></label>
        <br/>Letter<label>&nbsp;<input type="radio" name="thing" id="letterCheck"/></label>
      </form>
    </div>

    【讨论】:

      猜你喜欢
      • 2016-01-02
      • 1970-01-01
      • 1970-01-01
      • 2019-09-22
      • 2012-05-28
      • 1970-01-01
      • 2012-11-16
      • 1970-01-01
      • 2017-04-11
      相关资源
      最近更新 更多