Maneira eficiente de embaralhar objetos


20

Estou escrevendo um programa para algum software de teste. Eu tenho uma classe de pergunta contendo os ArrayLists para a pergunta, resposta, opções, marcas e marcas negativas. Algo assim:

class question
{
    private ArrayList<Integer> index_list;
    private ArrayList<String> question_list;        
    private ArrayList<String> answer_list;      
    private ArrayList<String> opt1_list;        
    private ArrayList<String> opt2_list;    
}

Quero embaralhar todas as perguntas, mas, para que as perguntas sejam embaralhadas, todos os objetos precisam ser embaralhados. Eu teria abordado esse problema desta maneira:

Primeiro de tudo, eu não teria usado esse design e String não ArrayList<String>digitado como variáveis ​​de instância e, em seguida, teria usado o Collections.shufflemétodo para embaralhar objetos. Mas minha equipe insiste nesse design.

Agora, a classe de perguntas contém ArrayLists crescentes à medida que a entrada para as perguntas é feita. Como embaralhar as perguntas agora?


30
Eu odeio falar em absolutos, mas se sua equipe insistir nesse design, eles estarão errados. Diga a eles! Diga a eles que eu disse isso (e escrevi na Internet, então tenho que estar certo).
Joachim Sauer

10
Sim, diga a eles que há muitas pessoas aqui dizendo que esse tipo de design é um erro típico de iniciante.
Doc Brown

6
Por curiosidade: que vantagens sua equipe vê neste design?
Joachim Sauer

9
As convenções de nomenclatura Java são CamelCase para nomes de classes e camelCase para nomes de variáveis.
Tulains Córdova

Eu acho que você precisa confrontar sua equipe sobre essa terrível decisão de design. Se eles continuarem insistindo, descubra o porquê. Se for apenas teimosia, talvez comece a pensar em encontrar uma nova equipe em um futuro não muito distante. Se eles tiverem motivos para essa estrutura, considere esses motivos por seus méritos.
Ben Lee

Respostas:


95

Sua equipe sofre de um problema comum: negação de objeto .

Em vez de uma classe que contém uma única pergunta com todas as informações associadas, você tenta criar uma classe chamada questionque contém todas as perguntas em uma única instância.

Esse é o caminho errado, e complica o que você tenta fazer muito ! Classificar (e embaralhar) matrizes paralelas (ou Listas) é um negócio desagradável e não existe uma API comum para isso, simplesmente porque você geralmente deseja evitá-lo .

Eu sugiro que você reestruture seu código assim:

class Question
{
    private Integer index;
    private String question;        
    private String answer;      
    private String opt1;        
    private String opt2;    
}

// somewhere else
List<Question> questionList = new ArrayList<Question>();

Dessa forma, embaralhar sua pergunta se torna trivial (usando Collections.shuffle()):

Collections.shuffle(questionList);

39
nem sequer é negação de objeto, é negação de estrutura de dados
jk.

22

Você não Você cria outra lista / fila de índices e embaralha isso. Em seguida, você itera os índices que controlam a ordem "aleatória" de suas outras coleções.

Mesmo fora do seu cenário, com as coisas divididas, a coleção de pedidos separada oferece vários benefícios (paralelismo, velocidade ao recolocar a coleção original é caro, etc.).


10
Estou relutante em votar a favor: é a próxima melhor solução se esse design for realmente corrigido, mas insistir nesse design é tão errado que eu não gostaria de dar sugestões sobre ele. (meh, upvoted qualquer maneira ;-))
Joachim Sauer

3
@joachimSauer - enquanto eu concordo, há muitos outros cenários (menos ofensivos) em que a coleção original deve permanecer estática enquanto o caminho através deles precisa variar.
Telastyn 28/05

4
Sim eu conheço. E embaralhar uma coleção de índices é a abordagem correta para essas situações. Meu único medo é que a equipe de OPs aceite isso e diga "bom o suficiente", sem revisar seu design.
Joachim Sauer

1
Esta resposta é valiosa especialmente para o caso em que não se tem a liberdade de revisar / recodificar a classe ou a estrutura subjacente da coleção, por exemplo, é preciso se contentar com uma API para uma coleção mantida pelo SO. Baralhar os índices é uma ótima visão e permanece por si só, mesmo que não seja tão perspicaz quanto refazer o design subjacente.
hardmath

@ Joachim Sauer: na verdade, baralhar os índices não é necessariamente a melhor solução para o problema, como indicado. Veja minha resposta para uma alternativa.
Michael Borgwardt 28/05

16

Concordo com as outras respostas de que a solução correta é usar um modelo de objeto adequado.

No entanto, é realmente muito fácil embaralhar várias listas da mesma maneira:

Random rnd = new Random();
long seed = rnd.nextLong();

rnd.setSeed(seed);
Collections.shuffle(index_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(question_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(answer_list, rnd);
...

Essa é ... uma maneira legal de fazer isso! Agora, para o caso "classificação", apenas precisamos encontrar uma semente que produza uma lista classificada quando aplicada dessa maneira e, em seguida, embaralharemos todas as listas com essa semente!
Joachim Sauer

1
@ JoachimSauer: bem, a classificação não fazia parte do problema. Embora seja uma questão interessante se existe uma maneira sistemática de encontrar essa semente para um determinado RNG.
Michael Borgwardt 28/05

2
@MichaelBorgwardt depois de conseguir mais de 17 perguntas que você simplesmente não pode expressar a quantidade de possíveis embaralha nos 48 bits do Java usa aleatório (log_2 (17) = 48.33!)
catraca aberração

@ratchetfreak: não me parece um problema real. E é trivial usar o SecureRandom, se necessário.
Michael Borgwardt 28/05

4
@ Telastyn: a lista de índices é uma camada indireta da IMO que torna sua solução conceitualmente mais complexa, e se é mais ou menos eficiente depende da frequência com que as listas são acessadas após o shuffle. Mas as diferenças de desempenho seriam insignificantes, considerando tamanhos realistas para que um questionário fosse respondido por humanos.
Michael Borgwardt 28/05

3

Crie uma classe Question2:

class Question2
{
    public Integer index_list;
    public String question_list;        
    public String answer_list;      
    public String opt1_list;        
    public String opt2_list;    
}

Em seguida, crie uma função mapeada questionpara ArrayList<Question2>, use Collection.Shufflepara esse resultado e crie uma segunda função para mapear ArrayList<Question2>novamente question.

Depois, vá para sua equipe e tente convencê-los de que usar um em ArrayList<Question2>vez de questionmelhoraria muito o código deles, pois economizaria muitas conversões desnecessárias.


1
É uma boa idéia, mas somente após tentativas a priori de alterar o design falharam.
Sebastian Redl 28/05

@SebastianRedl: às vezes é mais fácil convencer as pessoas de um design melhor ao mostrar a solução no código.
Doc Brown

1

Minha resposta ingênua e errada original :

Basta criar (pelo menos) nnúmeros aleatórios e trocar itens n com itens iem um loop for para cada lista que você possui.

No pseudo código:

for (in i = 0; i < question_list.length(); i++) {
  int random = randomNumber(0, questions_list.length()-1);
  question_list.switchItems(i, random);
  answer_list.switchItems(i, random);
  opt1_list.switchItems(i, random);
  opt2_list.switchItems(i, random);

}

Atualizar:

Obrigado pelo the_lotus por apontar o artigo sobre horror de codificação. Sinto-me muito mais esperto agora :-) De qualquer forma, Jeff Atwood também mostra como fazê-lo corretamente, usando o algoritmo Fisher-Yates :

for (int i = question_list.Length - 1; i > 0; i--){
  int n = rand.Next(i + 1); //assuming rand.Next(x) returns values between 0 and x-1
  question_list.switchItems(i, n);
  answer_list.switchItems(i, n);
  opt1_list.switchItems(i, n);
  opt2_list.switchItems(i, n);
}

A principal diferença aqui é que cada elemento é trocado apenas uma vez.

E enquanto as outras respostas explicam corretamente que seu modelo de objeto é defeituoso, você pode não estar na posição de alterá-lo. Portanto, o algoritmo Fisher-Yates resolveria seu problema sem alterar seu modelo de dados.


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.