Como randomizar (aleatoriamente) uma matriz JavaScript?


1265

Eu tenho uma matriz como esta:

var arr1 = ["a", "b", "c", "d"];

Como posso aleatorizar / embaralhar?



6
Apenas jogando isso aqui que você pode visualizar como aleatório uma função Shuffle é realmente com este visualizador Mike Bostock fez: bost.ocks.org/mike/shuffle/compare.html
agosto

5
@Blazemonger jsPref está morto. Você pode simplesmente postar aqui qual é o mais rápido?
eozzy

Que tal um one-liner? A matriz retornada é embaralhada. arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && uma, [])
brunettdan

A solução de redução possui complexidade O (n ^ 2). Tente executá-lo em uma matriz com um milhão de elementos.
riv

Respostas:


1542

O algoritmo shuffle imparcial de fato é o Shuffle de Fisher-Yates (também conhecido como Knuth).

Consulte https://github.com/coolaj86/knuth-shuffle

Você pode ver uma ótima visualização aqui (e a postagem original vinculada a isso )

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

// Used like so
var arr = [2, 11, 37, 42];
shuffle(arr);
console.log(arr);

Mais algumas informações sobre o algoritmo usado.


13
A resposta acima ignora o elemento 0, a condição i--não deve ser --i. Além disso, o teste if (i==0)...é supérfluo, pois se i == 0o tempo laço nunca será inserido. A chamada para Math.floorpode ser feita mais rapidamente usando ...| 0. De qualquer tempi ou tempj pode ser removida e o valor ser directamente atribuídos a Matriz [i] ou j conforme apropriado.
RobG

23
@prometheus, todos os RNGs são pseudo-aleatórios, a menos que estejam conectados a hardware caro.
21740 Phil

38
@RobG a implementação acima está funcionalmente correta. No algoritmo Fisher-Yates, o loop não deve ser executado para o primeiro elemento na matriz. Confira a Wikipedia, onde há outras implementações que também ignoram o primeiro elemento. Verifique também este artigo, que fala sobre por que é importante que o loop não seja executado para o primeiro elemento.
theon

34
@nikola "não é aleatório" é uma qualificação um pouco forte para mim. Eu diria que é suficientemente aleatório, a menos que você seja um criptógrafo; nesse caso, você provavelmente não está usando Math.Random () em primeiro lugar.
toon81

20
Ugh, yoda ( 0 !== currentIndex).
Ffxsam

745

Aqui está uma implementação em JavaScript do Durstenfeld shuffle , uma versão otimizada do Fisher-Yates:

/* Randomize array in-place using Durstenfeld shuffle algorithm */
function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.random() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

Ele escolhe um elemento aleatório para cada elemento original da matriz e o exclui do próximo sorteio, como escolher aleatoriamente um baralho de cartas.

Essa exclusão inteligente troca o elemento selecionado pelo atual e, em seguida, escolhe o próximo elemento aleatório do restante, fazendo um loop para trás para obter uma eficiência ideal, garantindo que a seleção aleatória seja simplificada (sempre pode começar em 0) e, assim, pulando o elemento final.

O tempo de execução do algoritmo é O(n). Observe que o shuffle é feito no local, portanto, se você não quiser modificar a matriz original, primeiro faça uma cópia dela .slice(0).


EDIT: Atualizando para ES6 / ECMAScript 2015

O novo ES6 nos permite atribuir duas variáveis ​​ao mesmo tempo. Isso é especialmente útil quando queremos trocar os valores de duas variáveis, pois podemos fazer isso em uma linha de código. Aqui está uma forma mais curta da mesma função, usando esse recurso.

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

22
ps O mesmo algoritmo que a resposta de ChristopheD, mas com explicação e implementação mais limpa.
Laurens Holst

12
As pessoas estão atribuindo a pessoa errada para o algoritmo. Não é o embaralhamento de Fisher-Yates, mas o de Durstenfeld . O verdadeiro algoritmo original Fisher-Yates é executado em n ^ 2 tempo, não n tempo
Pacerier

7
Não é necessário, return arraypois o JavaScript passa matrizes por referência quando usado como argumentos de função. Suponho que isso economize espaço na pilha, mas é um pequeno recurso interessante. A execução da reprodução aleatória na matriz embaralha a matriz original.
Joel Trauger

5
A implementação nesta resposta favorece a extremidade inferior da matriz. Descobri da maneira mais difícil . Math.random() should not be multiplied with the loop counter + 1, but with array.lengt () `. Consulte Gerando números inteiros aleatórios em JavaScript em um intervalo específico? para uma explicação muito abrangente.
Marjan Venema

13
@MarjanVenema Não tenho certeza se você ainda está assistindo este espaço, mas esta resposta está correta, e a mudança que você está sugerindo realmente apresenta viés. Consulte blog.codinghorror.com/the-danger-of-naivete para obter uma boa descrição desse erro.
user94559

133

Atenção!
O uso desse algoritmo não é recomendado , pois é ineficiente e fortemente enviesado ; Ver comentários. Ele está sendo deixado aqui para referência futura, porque a idéia não é tão rara.

[1,2,3,4,5,6].sort(function() {
  return .5 - Math.random();
});

13
eu gosto desta solução, o suficiente para dar uma básica aleatória
Alex K

147
Voto negativo, pois isso não é realmente tão aleatório. Não sei por que tem tantos votos positivos. Não use esse método. Parece bonito, mas não está completamente correto. Aqui estão os resultados após 10.000 iterações de quantas vezes cada número no seu array atinge o índice [0] (também posso fornecer os outros resultados): 1 = 29,19%, 2 = 29,53%, 3 = 20,06%, 4 = 11,91%, 5 = 5,99%, 6 = 3,32%
radtad 13/11/2013

8
Tudo bem se você precisar aleatoriamente um array relativamente pequeno e não lidar com coisas criptográficas. Concordo plenamente que, se você precisar de mais aleatoriedade, precisará usar uma solução mais complexa.
deadrunk


12
O problema é que não é determinístico, o que dará resultados errados (se 1> 2 e 2> 3, deve-se dar 1> 3, mas isso não garante isso. Isso confunde o tipo e dá o resultado comentado por @radtad).
MatsLindh

73

Pode-se (ou deveria) usá-lo como um protoype da Matriz:

Partida ChristopheD:

Array.prototype.shuffle = function() {
  var i = this.length, j, temp;
  if ( i == 0 ) return this;
  while ( --i ) {
     j = Math.floor( Math.random() * ( i + 1 ) );
     temp = this[i];
     this[i] = this[j];
     this[j] = temp;
  }
  return this;
}

42
Realmente nenhum benefício para isso, IMOHO, exceto, possivelmente, pisando sobre a implementação .. alguém
user2864740

2
Se usado no protótipo Array, ele deve ter outro nome que não seja apenas shuffle .
Wolf

57
Um poderia (ou deveria) evitar estendendo Native Protótipos: javascriptweblog.wordpress.com/2011/12/05/...
Wédney Yuri

12
Você não deveria fazer isso; toda matriz afetada por isso não pode mais ser iterada com segurança usando for ... in. Não estenda protótipos nativos.

18
@TinyGiant Na verdade: não use for...inloops para iterar sobre matrizes.
Conor O'Brien

69

Você pode fazer isso facilmente com o mapa e classificar:

let unshuffled = ['hello', 'a', 't', 'q', 1, 2, 3, {cats: true}]

let shuffled = unshuffled
  .map((a) => ({sort: Math.random(), value: a}))
  .sort((a, b) => a.sort - b.sort)
  .map((a) => a.value)
  1. Colocamos cada elemento na matriz em um objeto e atribuímos a ele uma chave de classificação aleatória
  2. Classificamos usando a chave aleatória
  3. Desmapeamos para obter os objetos originais

Você pode embaralhar matrizes polimórficas e a classificação é tão aleatória quanto Math.random, o que é bom o suficiente para a maioria dos propósitos.

Como os elementos são classificados com chaves consistentes que não são regeneradas a cada iteração e cada comparação é extraída da mesma distribuição, qualquer não aleatoriedade na distribuição do Math.random é cancelada.

Rapidez

A complexidade do tempo é O (N log N), o mesmo que a classificação rápida. A complexidade do espaço é O (N). Isso não é tão eficiente quanto um embaralhamento Fischer Yates, mas, na minha opinião, o código é significativamente mais curto e mais funcional. Se você possui uma grande variedade, certamente deve usar o Fischer Yates. Se você tiver uma pequena matriz com algumas centenas de itens, faça isso.


1
@ superluminary Oops, você está certo. Observe que essa resposta já usou a mesma abordagem.
Bergi

@ Bergi - Ah, sim, você está certo, embora eu ache que minha implementação seja um pouco mais bonita.
superluminary

3
Muito agradável. Essa é a transformação schwartziana em js.
Mark Grimes

@torazaburo - Não é tão eficiente quanto Fischer Yates, mas é mais bonito e o código é menor. Código é sempre uma troca. Se eu tivesse uma grande variedade, usaria Knuth. Se eu tivesse algumas centenas de itens, faria isso.
superluminary

1
@ BenCarp - Concordo, não é a solução mais rápida e você não gostaria de usá-lo em uma matriz massiva, mas há mais considerações no código do que a velocidade bruta.
superluminary

64

Use a biblioteca underscore.js. O método _.shuffle()é bom para este caso. Aqui está um exemplo com o método:

var _ = require("underscore");

var arr = [1,2,3,4,5,6];
// Testing _.shuffle
var testShuffle = function () {
  var indexOne = 0;
    var stObj = {
      '0': 0,
      '1': 1,
      '2': 2,
      '3': 3,
      '4': 4,
      '5': 5
    };
    for (var i = 0; i < 1000; i++) {
      arr = _.shuffle(arr);
      indexOne = _.indexOf(arr, 1);
      stObj[indexOne] ++;
    }
    console.log(stObj);
};
testShuffle();

12
Ótima resposta! Obrigado. Eu prefiro as outras respostas, pois incentiva as pessoas a usar bibliotecas em vez de copiar e colar funções potencialmente com erros em todos os lugares.
Frabcus #

60
@frabcus: Não faz sentido incluir uma biblioteca inteira apenas para obter uma shufflefunção.
Blender

11
Discordo do @Blender. Há muitas razões para incluir uma biblioteca inteira apenas para obter uma função que você precisa. Uma delas é que há menos risco de um bug quando você o escreve. Se for um problema de desempenho, você não deve usá-lo. Mas apenas porque poderia ser um problema de desempenho, não significa que será.
22413 Daniel Kaplan

7
@tieTYT: Então, por que você precisa do resto da biblioteca? O embaralhamento de Fisher-Yates é trivial de implementar. Você não precisa de uma biblioteca para escolher um elemento aleatório de uma matriz (espero), então não há razão para usar uma biblioteca, a menos que você realmente utilize mais de uma função nela.
Blender

18
@ Blender: Eu dei uma razão para isso. 1) Garanto que você pode introduzir um bug em qualquer código que escrever, por mais trivial que seja. Por que arriscar? 2) Não pré-otimize. 3) 99% das vezes em que você precisa de algo aleatório, seu aplicativo não trata de escrever algo aleatório. É sobre algo que precisa de algo aleatório. Alavancar o trabalho de outras pessoas. Não pense nos detalhes da implementação, a menos que seja necessário.
21413 Daniel Kaplan

50

NOVO!

Algoritmo de embaralhamento Fisher-Yates mais curto e provavelmente mais rápido

  1. usa enquanto ---
  2. bit a bit para andar (números de até 10 dígitos decimais (32 bits))
  3. removidos fechamentos desnecessários e outras coisas

function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
 c=a.length;while(c)b=Math.random()*(--c+1)|0,d=a[c],a[c]=a[b],a[b]=d
}

tamanho do script (com fy como nome da função): 90bytes

DEMO http://jsfiddle.net/vvpoma8w/

* mais rápido, provavelmente em todos os navegadores, exceto no Chrome.

Se você tiver alguma dúvida, basta perguntar.

EDITAR

sim é mais rapido

DESEMPENHO: http://jsperf.com/fyshuffle

usando as principais funções votadas.

EDITAR Houve um cálculo em excesso (não precisa --c + 1) e ninguém notou

mais curto (4 bytes) e mais rápido (teste!).

function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
 c=a.length;while(c)b=Math.random()*c--|0,d=a[c],a[c]=a[b],a[b]=d
}

Armazenar em cache em outro lugar var rnd=Math.randome depois usá-lo rnd()também aumentaria um pouco o desempenho em grandes matrizes.

http://jsfiddle.net/vvpoma8w/2/

Versão legível (use a versão original. Isso é mais lento, vars são inúteis, como os fechamentos & ";", o próprio código também é mais curto ... talvez leia isto Como 'minificar' o código Javascript , mas você não é capaz de comprima o código a seguir em minificadores de javascript como o acima.)

function fisherYates( array ){
 var count = array.length,
     randomnumber,
     temp;
 while( count ){
  randomnumber = Math.random() * count-- | 0;
  temp = array[count];
  array[count] = array[randomnumber];
  array[randomnumber] = temp
 }
}

6
confira o desempenho ... 2x mais rápido na maioria dos navegadores ... mas precisa de testadores mais JSPerf ...
Cocco

10
js é uma linguagem que aceita muitos atalhos e maneiras diferentes de escrevê-lo. Embora existam muitas funções lentas e legíveis aqui, eu gostaria de mostrar como isso pode ser feito de uma maneira mais eficiente, além de economizar alguns bytes ... A abreviação é realmente subestimada aqui e a web está cheia de código lento e com erros.
cocco 23/09

Não é um aumento do perf slam dunk. Trocando fye shuffle prototype, fico fyconsistente na parte inferior do Chrome 37 no OS X 10.9.5 (81% mais lento ~ 20k ops do que ~ 100k) e o Safari 7.1 é até ~ 8% mais lento. YMMV, mas nem sempre é mais rápido. jsperf.com/fyshuffle/3
Spig

verifique as estatísticas novamente ... eu já escrevi que o chrome é mais lento, porque eles otimizaram a matemática, em todos os outros o piso bit a bit e enquanto é mais rápido. verifique IE, Firefox, mas também devices.Would móvel ser também bom ver ópera ...
Cocco

1
Esta é uma resposta terrível. O SO não é uma competição de obscurecimento.
Puppy

39

Editar: esta resposta está incorreta

Consulte os comentários e https://stackoverflow.com/a/18650169/28234 . Está sendo deixado aqui para referência, porque a idéia não é rara.


Uma maneira muito simples para pequenas matrizes é simplesmente esta:

const someArray = [1, 2, 3, 4, 5];

someArray.sort(() => Math.random() - 0.5);

Provavelmente não é muito eficiente, mas para pequenas matrizes isso funciona muito bem. Aqui está um exemplo para que você possa ver como é aleatório (ou não) e se ele se encaixa no seu caso de uso ou não.

const resultsEl = document.querySelector('#results');
const buttonEl = document.querySelector('#trigger');

const generateArrayAndRandomize = () => {
  const someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
  someArray.sort(() => Math.random() - 0.5);
  return someArray;
};

const renderResultsToDom = (results, el) => {
  el.innerHTML = results.join(' ');
};

buttonEl.addEventListener('click', () => renderResultsToDom(generateArrayAndRandomize(), resultsEl));
<h1>Randomize!</h1>
<button id="trigger">Generate</button>
<p id="results">0 1 2 3 4 5 6 7 8 9</p>


Legal, mas gera elementos aleatórios completos toda vez?
DDD

Não tenho certeza se entendi corretamente. Essa abordagem embaralha o array de maneira aleatória (embora pseudo-aleatória) toda vez que você chama o array de classificação - não é um tipo estável, por razões óbvias.
Kris Selbekk

4
Pelas mesmas razões, como explicado em stackoverflow.com/a/18650169/28234 . É muito mais provável que isso deixe os elementos iniciais próximos ao início da matriz.
AlexC

7
Essa é uma abordagem fácil e excelente para quando você precisa embaralhar uma matriz, mas não se preocupe muito em ter resultados academicamente comprovadamente aleatórios. Às vezes, esses últimos centímetros até a perfeição levam mais tempo do que vale a pena.
Daniel Griscom

1
Seria adorável se isso funcionasse, mas não funciona. Devido à maneira como a pesquisa rápida funciona, é provável que um comparador inconsistente deixe os elementos da matriz próximos à sua posição original. Sua matriz não será embaralhada.
superluminary 14/03

39

Confiável, Eficiente, Curto

Algumas soluções nesta página não são confiáveis ​​(elas apenas aleatoriamente parcialmente a matriz). Outras soluções são significativamente menos eficientes. Com testShuffleArrayFun(veja abaixo), podemos testar as funções de embaralhamento da matriz para obter confiabilidade e desempenho. As seguintes soluções são: confiáveis, eficientes e curtas (usando a sintaxe ES6)

[Testes de comparação foram feitos com testShuffleArrayFunoutras soluções, no Google Chrome]

Shuffle Array In Place

    function getShuffledArr (array){
        for (var i = array.length - 1; i > 0; i--) {
            var rand = Math.floor(Math.random() * (i + 1));
            [array[i], array[rand]] = [array[rand], array[i]]
        }
    }

ES6 Puro, Iterativo

    const getShuffledArr = arr => {
        const newArr = arr.slice()
        for (let i = newArr.length - 1; i > 0; i--) {
            const rand = Math.floor(Math.random() * (i + 1));
            [newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
        }
        return newArr
    };

Teste de confiabilidade e desempenho

Como você pode ver nesta página, houve soluções incorretas oferecidas aqui no passado. Eu escrevi e usei a seguinte função para testar qualquer função aleatória pura de matriz (sem efeitos colaterais).

    function testShuffleArrayFun(getShuffledArrayFun){
        const arr = [0,1,2,3,4,5,6,7,8,9]

        var countArr = arr.map(el=>{
            return arr.map(
                el=> 0
            )
        }) //   For each possible position in the shuffledArr and for 
           //   each possible value, we'll create a counter. 
        const t0 = performance.now()
        const n = 1000000
        for (var i=0 ; i<n ; i++){
            //   We'll call getShuffledArrayFun n times. 
            //   And for each iteration, we'll increment the counter. 
            var shuffledArr = getShuffledArrayFun(arr)
            shuffledArr.forEach(
                (value,key)=>{countArr[key][value]++}
            )
        }
        const t1 = performance.now()
        console.log(`Count Values in position`)
        console.table(countArr)

        const frequencyArr = countArr.map( positionArr => (
            positionArr.map(  
                count => count/n
            )
        )) 

        console.log("Frequency of value in position")
        console.table(frequencyArr)
        console.log(`total time: ${t1-t0}`)
    }

Outras soluções

Outras soluções apenas por diversão.

ES6 Puro, Recursivo

    const getShuffledArr = arr => {
        if (arr.length === 1) {return arr};
        const rand = Math.floor(Math.random() * arr.length);
        return [arr[rand], ...getShuffledArr(arr.filter((_, i) => i != rand))];
    };

ES6 Pure usando array.map

    function getShuffledArr (arr){
        return [...arr].map( (_, i, arrCopy) => {
            var rand = i + ( Math.floor( Math.random() * (arrCopy.length - i) ) );
            [arrCopy[rand], arrCopy[i]] = [arrCopy[i], arrCopy[rand]]
            return arrCopy[i]
        })
    }

ES6 Pure usando array.reduce

    function getShuffledArr (arr){
        return arr.reduce( 
            (newArr, _, i) => {
                var rand = i + ( Math.floor( Math.random() * (newArr.length - i) ) );
                [newArr[rand], newArr[i]] = [newArr[i], newArr[rand]]
                return newArr
            }, [...arr]
        )
    }

Então, onde está o ES6 (ES2015)? [array[i], array[rand]]=[array[rand], array[i]]? Talvez você possa descrever como isso funciona. Por que você escolhe iterar para baixo?
sheriffderek

@sheriffderek Sim, o recurso ES6 que estou usando é a atribuição de dois vars ao mesmo tempo, o que nos permite trocar dois vars em uma linha de código.
Ben Carp

Os nossos agradecimentos a @sheriffderek, que sugeriu o algoritmo ascendente. O algoritmo ascendente pode ser provado em indução.
Ben Carp

23

Adicionando à resposta @Laurens Holsts. Isso é 50% compactado.

function shuffleArray(d) {
  for (var c = d.length - 1; c > 0; c--) {
    var b = Math.floor(Math.random() * (c + 1));
    var a = d[c];
    d[c] = d[b];
    d[b] = a;
  }
  return d
};

3
Deveríamos incentivar as pessoas a usar _.shuffle em vez de colar o código do estouro de pilha; e, devemos desencorajar as pessoas a compactar suas respostas de estouro de pilha. É para isso que serve o jsmin.
David Jones

45
@DavidJones: Por que eu incluiria uma biblioteca inteira de 4kb apenas para embaralhar uma matriz?
Blender

1
A chamada de nomes do @KingKongFrog também não é condutora para uma assembléia de uma comunidade razoável.
wheaties

2
é eficiente fazer var b = um loop em vez de declarar um loop externo b e atribuí-lo com b = um loop?
Alex K

2
@Brian Não fará diferença; a elevação acontece quando o código-fonte é analisado. Provavelmente não envolvido.
user2864740

23

Editar: esta resposta está incorreta

Consulte https://stackoverflow.com/a/18650169/28234 . Está sendo deixado aqui para referência, porque a idéia não é rara.

//one line solution
shuffle = (array) => array.sort(() => Math.random() - 0.5);


//Demo
let arr = [1, 2, 3];
shuffle(arr);
alert(arr);

https://javascript.info/task/shuffle

Math.random() - 0.5 é um número aleatório que pode ser positivo ou negativo; portanto, a função de classificação reordena os elementos aleatoriamente.


17

Com o ES2015, você pode usar este:

Array.prototype.shuffle = function() {
  let m = this.length, i;
  while (m) {
    i = (Math.random() * m--) >>> 0;
    [this[m], this[i]] = [this[i], this[m]]
  }
  return this;
}

Uso:

[1, 2, 3, 4, 5, 6, 7].shuffle();

4
Para truncar, você deve usar em n >>> 0vez de ~~n. Os índices da matriz podem ser maiores que 2³¹-1.
Oriol

1
Desestruturação como isso contribui para uma implementação de tais limpa +1
lukejacksonn

14

Encontrei essa variante nas respostas "excluídas pelo autor" em uma duplicata desta pergunta. Ao contrário de algumas das outras respostas que já têm muitos votos positivos, isso é:

  1. Realmente aleatório
  2. Não está no local (daí o shufflednome shuffle)
  3. Ainda não está presente aqui com várias variantes

Aqui está um jsfiddle mostrando-o em uso .

Array.prototype.shuffled = function() {
  return this.map(function(n){ return [Math.random(), n] })
             .sort().map(function(n){ return n[1] });
}

(Suspeito que ele tenha sido excluído, pois é uma maneira muito ineficiente de randomizar a matriz, especialmente para matrizes maiores ... enquanto a resposta aceita e vários outros clones dessa resposta fazem a randomização no local).
WiredPrairie

1
Sim, mas como a resposta errada bem conhecida ainda tem muitos votos, uma solução ineficiente, mas correta, deve ser mencionada pelo menos.
Daniel Martin

[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });- ele não lhe dá uma espécie aleatória, e se você usá-lo você pode acabar envergonhado: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Daniel Martin

3
Você precisa usar .sort(function(a,b){ return a[0] - b[0]; })se quiser que a classificação compare valores numericamente. O .sort()comparador padrão é lexicográfico, o que significa que será considerado 10menor que, 2pois 1é menor que 2.
4castle

Certo, atualizei o código, mas vou revertê-lo: a distinção entre ordem lexicográfica e ordem numérica não importa para números no intervalo que Math.random()produz. (isto é, a ordem lexicográfica é o mesmo que ordem numérica quando se lida com números de 0 (inclusive) a 1 (exclusive))
Daniel Martin

14
var shuffle = function(array) {
   temp = [];
   originalLength = array.length;
   for (var i = 0; i < originalLength; i++) {
     temp.push(array.splice(Math.floor(Math.random()*array.length),1));
   }
   return temp;
};

Obviamente, isso não é tão ótimo quanto o algoritmo Fisher-Yates, mas funcionaria para entrevistas técnicas?
Davidatthepark 19/16

@Andrea O código foi quebrado devido ao fato de que o comprimento da matriz é alterado dentro do loop for. Com a última edição, isso é corrigido.
30519 Charlie Wallace

12
arr1.sort(() => Math.random() - 0.5);

1
Por que menos 0,5? O que esse número significa?
Sartheris Stormhammer

1
@SartherisStormhammer porque estamos usando um compareFunction para a classificação e, se isso retornar um número maior que 0, os elementos comparados serão ordenados apenas na direção. -0,5 em Math.random () nos dará um número negativo ~ 50% das vezes, o que nos dará a ordem inversa.
Sam Doidge 03/03

Solução simples e direta. Obrigado
deanwilliammills 15/03

9

Você pode fazer isso facilmente com:

// array
var fruits = ["Banana", "Orange", "Apple", "Mango"];
// random
fruits.sort(function(a, b){return 0.5 - Math.random()});
// out
console.log(fruits);

Consulte em JavaScript Sorting Arrays


Este algoritmo tem sido comprovadamente defeituoso.

Por favor, prove para mim. I com base em w3schools
Tinh Ngô Quang

4
Você pode ler o tópico em css-tricks.com/snippets/javascript/shuffle-array ou em news.ycombinator.com/item?id=2728914 . A W3schools sempre foi e continua sendo uma fonte horrível de informação.

Para uma boa discussão sobre por que isso não é uma boa abordagem ver stackoverflow.com/questions/962802/...
Charlie Wallace

8

Uma solução recursiva:

function shuffle(a,b){
    return a.length==0?b:function(c){
        return shuffle(a,(b||[]).concat(c));
    }(a.splice(Math.floor(Math.random()*a.length),1));
};

8

Embaralhamento de Fisher-Yates em javascript. Estou postando isso aqui porque o uso de duas funções utilitárias (swap e randInt) esclarece o algoritmo comparado às outras respostas aqui.

function swap(arr, i, j) { 
  // swaps two elements of an array in place
  var temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}
function randInt(max) { 
  // returns random integer between 0 and max-1 inclusive.
  return Math.floor(Math.random()*max);
}
function shuffle(arr) {
  // For each slot in the array (starting at the end), 
  // pick an element randomly from the unplaced elements and
  // place it in the slot, exchanging places with the 
  // element in the slot. 
  for(var slot = arr.length - 1; slot > 0; slot--){
    var element = randInt(slot+1);
    swap(arr, element, slot);
  }
}

7

Antes de tudo, dê uma olhada aqui para uma ótima comparação visual de diferentes métodos de classificação em javascript.

Em segundo lugar, se você der uma olhada rápida no link acima, verá que o random ordertipo parece ter um desempenho relativamente bom em comparação com outros métodos, além de ser extremamente fácil e rápido de implementar, como mostrado abaixo:

function shuffle(array) {
  var random = array.map(Math.random);
  array.sort(function(a, b) {
    return random[array.indexOf(a)] - random[array.indexOf(b)];
  });
}

Edit : como apontado por @gregers, a função de comparação é chamada com valores em vez de índices, e é por isso que você precisa usar indexOf. Observe que essa alteração torna o código menos adequado para matrizes maiores à medida que indexOfé executado no tempo O (n).


Array.prototype.sortpassa em dois valores como ae b, não o índice. Portanto, esse código não funciona.
Gregers

@gregers você está certo, eu editei a resposta. Obrigado.
Milo Wielondek

1
Isso não é muito aleatório. Dependendo da implementação da classificação, um elemento no índice mais baixo da matriz pode exigir mais comparações para chegar ao índice mais alto do que o elemento próximo ao índice mais alto. Isso significa que é menos provável que o elemento no índice mais baixo chegue ao índice mais alto.
1 'OU 1 -

7

uma função aleatória que não altera a matriz de origem

Atualização : aqui estou sugerindo um algoritmo relativamente simples (não da perspectiva da complexidade ) e curto que funcionará bem com matrizes de tamanho pequeno, mas definitivamente vai custar muito mais do que o algoritmo clássico de Durstenfeld quando você lida com matrizes enormes. Você pode encontrar o Durstenfeld em uma das principais respostas a esta pergunta.

Resposta original:

Se você não deseja que a função de reprodução aleatória mude a matriz de origem , copie-a para uma variável local e faça o resto com uma lógica de reprodução aleatória simples .

function shuffle(array) {
  var result = [], source = array.concat([]);

  while (source.length) {
    let index = Math.floor(Math.random() * source.length);
    result.push(source[index]);
    source.splice(index, 1);
  }

  return result;
}

Baralhar lógica : escolha um índice aleatório e adicione o elemento correspondente à matriz de resultados e exclua-o da cópia da matriz de origem . Repita esta ação até que a matriz de origem fique vazia .

E se você realmente quer que ele seja curto, aqui está o quão longe eu poderia chegar:

function shuffle(array) {
  var result = [], source = array.concat([]);

  while (source.length) {
    let index = Math.floor(Math.random() * source.length);
    result.push(source.splice(index, 1)[0]);
  }

  return result;
}

Este é essencialmente o algoritmo original de Fisher-Yates, splicesendo você uma maneira terrivelmente ineficiente de fazer o que eles chamavam de "eliminar". Se você não deseja alterar a matriz original, copie-a e embaralhe essa cópia no lugar usando a variante Durstenfeld, muito mais eficiente.

@torazaburo, obrigado pelo seu feedback. Eu atualizei a minha resposta, para deixar claro que estou sim oferecer uma solução de aparência agradável, do que um one-scaling de super
Evgenia Manolova

Poderíamos também usar o splicemétodo para criar uma cópia da seguinte forma: source = array.slice();.
Taiga

6

Aqui está o mais fácil ,

function shuffle(array) {
  return array.sort(() => Math.random() - 0.5);
}

Para mais exemplos, você pode conferir aqui


5

mais uma implementação do Fisher-Yates, usando o modo estrito:

function shuffleArray(a) {
    "use strict";
    var i, t, j;
    for (i = a.length - 1; i > 0; i -= 1) {
        t = a[i];
        j = Math.floor(Math.random() * (i + 1));
        a[i] = a[j];
        a[j] = t;
    }
    return a;
}

Que valor a adição estrita de uso fornece sobre a resposta aceita?
shortstuffsushi

Para saber mais sobre o modo estrito e como ela influencia o desempenho que você pode ler sobre ele aqui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Raphael C

Hmm, você poderia apontar para algo específico do documento referenciado? Nada aqui parece fazer referência a "melhoria do desempenho", além de um vago comentário no topo sobre potencialmente dificultar a otimização do mecanismo js. Nesse caso, não está claro para mim que uso estrito melhoraria.
Shortstuffsushi # /

O modo estrito já existe há algum tempo, e há leituras suficientes por aí para que qualquer pessoa faça sua própria opinião se deve sempre usá-lo ou não e por quê. Jslint, por exemplo, deixa claro o suficiente que você sempre deve usar o modo estrito. Douglas Crockford escreveu uma grande quantidade de artigos e alguns ótimos vídeos sobre por que é importante sempre usar o modo estrito não apenas como uma boa prática, mas também como ele é interpretado de maneira diferente pelos mecanismos js do navegador, como o V8. Eu recomendo fortemente que você faça uma pesquisa no Google e faça sua própria opinião.
Raphael C

Aqui é uma discussão antiga sobre perfs no modo estrito, um pouco antiga, mas ainda relevante: stackoverflow.com/questions/3145966/...
Raphael C

5

Todas as outras respostas são baseadas em Math.random (), que é rápido, mas não é adequado para a randomização no nível criptográfico.

O código abaixo está usando o conhecido Fisher-Yatesalgoritmo enquanto utiliza Web Cryptography APIpara o nível criptográfico de randomização .

var d = [1,2,3,4,5,6,7,8,9,10];

function shuffle(a) {
	var x, t, r = new Uint32Array(1);
	for (var i = 0, c = a.length - 1, m = a.length; i < c; i++, m--) {
		crypto.getRandomValues(r);
		x = Math.floor(r / 65536 / 65536 * m) + i;
		t = a [i], a [i] = a [x], a [x] = t;
	}

	return a;
}

console.log(shuffle(d));


4

Solução moderna em linha curta usando os recursos do ES6:

['a','b','c','d'].map(x => [Math.random(), x]).sort(([a], [b]) => a - b).map(([_, x]) => x);

(para fins educacionais)


4

Uma modificação simples da resposta de CoolAJ86 que não modifica a matriz original:

 /**
 * Returns a new array whose contents are a shuffled copy of the original array.
 * @param {Array} The items to shuffle.
 * https://stackoverflow.com/a/2450976/1673761
 * https://stackoverflow.com/a/44071316/1673761
 */
const shuffle = (array) => {
  let currentIndex = array.length;
  let temporaryValue;
  let randomIndex;
  const newArray = array.slice();
  // While there remains elements to shuffle...
  while (currentIndex) {
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;
    // Swap it with the current element.
    temporaryValue = newArray[currentIndex];
    newArray[currentIndex] = newArray[randomIndex];
    newArray[randomIndex] = temporaryValue;
  }
  return newArray;
};

4

Embora já existam várias implementações recomendadas, mas acho que podemos torná-lo mais curto e fácil usando o loop forEach, portanto, não precisamos nos preocupar em calcular o comprimento da matriz e também podemos evitar o uso seguro de uma variável temporária.

var myArr = ["a", "b", "c", "d"];

myArr.forEach((val, key) => {
  randomIndex = Math.ceil(Math.random()*(key + 1));
  myArr[key] = myArr[randomIndex];
  myArr[randomIndex] = val;
});
// see the values
console.log('Shuffled Array: ', myArr)

4

Só para ter um dedo na torta. Aqui apresento uma implementação recursiva do embaralhamento de Fisher Yates (eu acho). Dá aleatoriedade uniforme.

Nota: O ~~(operador double til) na verdade se comporta como Math.floor()para números reais positivos. É só um atalho.

var shuffle = a => a.length ? a.splice(~~(Math.random()*a.length),1).concat(shuffle(a))
                            : a;

console.log(JSON.stringify(shuffle([0,1,2,3,4,5,6,7,8,9])));

Edit: O código acima é O (n ^ 2) devido ao emprego de, .splice()mas podemos eliminar emendas e embaralhamento em O (n) pelo truque de swap.

var shuffle = (a, l = a.length, r = ~~(Math.random()*l)) => l ? ([a[r],a[l-1]] = [a[l-1],a[r]], shuffle(a, l-1))
                                                              : a;

var arr = Array.from({length:3000}, (_,i) => i);
console.time("shuffle");
shuffle(arr);
console.timeEnd("shuffle");

O problema é que o JS não pode cooperar com grandes recursões. Nesse caso em particular, o tamanho da sua matriz é limitado a 3000 ~ 7000, dependendo do mecanismo do navegador e de alguns fatos desconhecidos.


3

Randomize array

 var arr = ['apple','cat','Adam','123','Zorro','petunia']; 
 var n = arr.length; var tempArr = [];

 for ( var i = 0; i < n-1; i++ ) {

    // The following line removes one random element from arr 
     // and pushes it onto tempArr 
     tempArr.push(arr.splice(Math.floor(Math.random()*arr.length),1)[0]);
 }

 // Push the remaining item onto tempArr 
 tempArr.push(arr[0]); 
 arr=tempArr; 

Não deve haver um -1para n que você usou <não<=
Mohebifar

3

a arrayShufflefunção mais curta

function arrayShuffle(o) {
    for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
}

Aparentemente, você está fazendo Sattolo's em vez de Fisher-Yates (Knuth, imparcial).
Arthur2e5

3

Do ponto de vista teórico, a maneira mais elegante de fazê-lo, na minha humilde opinião, é obter um único número aleatório entre 0 e n! -1 e calcular um mapeamento de um para um de {0, 1, …, n!-1}todas as permutações de(0, 1, 2, …, n-1) . Contanto que você possa usar um gerador (pseudo-) aleatório confiável o suficiente para obter um número sem viés significativo, você terá informações suficientes para alcançar o que deseja sem precisar de vários outros números aleatórios.

Ao calcular com números flutuantes de precisão dupla IEEE754, você pode esperar que seu gerador aleatório forneça cerca de 15 casas decimais. Como você possui 15! = 1.307.674.368.000 (com 13 dígitos), é possível usar as seguintes funções com matrizes que contêm até 15 elementos e assumir que não haverá viés significativo com matrizes que contêm até 14 elementos. Se você trabalha com um problema de tamanho fixo que exige calcular muitas vezes essa operação de reprodução aleatória, tente o código a seguir, que pode ser mais rápido que outros códigos, pois ele usaMath.random apenas uma vez (no entanto, envolve várias operações de cópia).

A seguinte função não será usada, mas eu a dou de qualquer maneira; retorna o índice de uma dada permutação de (0, 1, 2, …, n-1)acordo com o mapeamento de um para um usado nesta mensagem (o mais natural ao enumerar as permutas); destina-se a trabalhar com até 16 elementos:

function permIndex(p) {
    var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
    var tail = [];
    var i;
    if (p.length == 0) return 0;
    for(i=1;i<(p.length);i++) {
        if (p[i] > p[0]) tail.push(p[i]-1);
        else tail.push(p[i]);
    }
    return p[0] * fact[p.length-1] + permIndex(tail);
}

O recíproco da função anterior (necessário para sua própria pergunta) está abaixo; destina-se a trabalhar com até 16 elementos; retorna a permutação da ordem n de (0, 1, 2, …, s-1):

function permNth(n, s) {
    var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
    var i, j;
    var p = [];
    var q = [];
    for(i=0;i<s;i++) p.push(i);
    for(i=s-1; i>=0; i--) {
        j = Math.floor(n / fact[i]);
        n -= j*fact[i];
        q.push(p[j]);
        for(;j<i;j++) p[j]=p[j+1];
    }
    return q;
}

Agora, o que você deseja é apenas:

function shuffle(p) {
    var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000];
    return permNth(Math.floor(Math.random()*fact[p.length]), p.length).map(
            function(i) { return p[i]; });
}

Deverá funcionar com até 16 elementos com um pequeno viés teórico (embora imperceptível do ponto de vista prático); pode ser visto como totalmente utilizável por 15 elementos; com matrizes contendo menos de 14 elementos, você pode considerar com segurança que não haverá absolutamente nenhum viés.


Definitivamente elegante!
Gershom
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.