如何在不移动 FALSY 元素的情况下洗牌数组?

How to shuffle an array without moving FALSY elements?

这是我的尝试;稍作修改的 Fisher-Yates 算法。不过我不确定如何确保它是随机的。

const shuffleWithoutMovingFalsies = array => {
  const newArray = [...array];
  const getRandomValue = (i, N) => ~~(Math.random() * (N - i) + i);
  newArray.forEach((elem, i, arr, j = getRandomValue(i, arr.length)) => arr[i] && arr[j] && ([arr[i], arr[j]] = [arr[j], arr[i]]));
  return newArray;
}

const array = [1, 2, null, 3, null, null, 4, 5, 6, null];

const shuffledArray = shuffleWithoutMovingFalsies(array);

console.log(shuffledArray);

我所做的只是添加 arr[i] && arr[j] && 作为检查,以确保要交换的两个元素都不是 falsy

这使它无法公平洗牌。例如,对于数组 [1, null, 2]1 应该有 50% 的机会保持原状,并有 50% 的机会与 2 交换,但分裂是 ⅔–⅓。

只要辅助记忆不是问题,我建议提取元素,洗牌,然后放回去以简单起见:

const shuffle = arr => {
    for (let i = 0; i < arr.length - 1; i++) {
        const j = i + Math.floor(Math.random() * (arr.length - i));
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
};

const shuffleTruthy = arr => {
    const truthy = arr.filter(Boolean);
    shuffle(truthy);

    let j = 0;

    for (let i = 0; i < arr.length; i++) {
        if (arr[i]) {
            arr[i] = truthy[j++];
        }
    }
};