객체를 셔플하는 효율적인 방법


20

일부 퀴즈 소프트웨어 용 프로그램을 작성 중입니다. 질문, 답변, 옵션, 표시 및 음수에 대한 ArrayList를 포함하는 질문 클래스가 있습니다. 이 같은:

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;    
}

모든 질문을 섞고 싶지만 질문을 섞으려면 모든 개체를 섞어 야합니다. 이 방법 으로이 문제에 접근했을 것입니다.

우선, 나는이 디자인을 사용하지 않았고 String을 ArrayList<String>type 변수를 인스턴스 변수로 사용하지 않았으며 , 그 Collections.shuffle방법을 사용하여 객체를 섞었습니다. 그러나 우리 팀은이 디자인을 고집합니다.

이제 질문 클래스는 질문에 대한 입력이 증가함에 따라 증가하는 ArrayList를 포함합니다. 지금 질문을 섞는 방법?


30
나는 절대적으로 이야기하는 것을 싫어하지만 팀 이이 디자인을 고집하면 잘못되었습니다. 그들에게! 그들에게 내가 그렇게 말했다고 말하십시오 (그리고 인터넷에 그것을 썼기 때문에 나는 옳 아야합니다).
Joachim Sauer

10
그렇습니다. 이런 종류의 디자인은 전형적인 초보자 실수라고 말하는 사람들이 많습니다.
Doc Brown

6
호기심에서 : 이 디자인에서 팀이 보는 이점 은 무엇 입니까?
Joachim Sauer

9
Java 이름 지정 규칙은 클래스 이름의 경우 CamelCase이고 변수 이름의 경우 camelCase입니다.
Tulains Córdova

이 끔찍한 디자인 결정에 대해 팀과 대면해야한다고 생각합니다. 그들이 계속 주장한다면 이유를 찾으십시오. 그것이 완고한 일이라면, 먼 미래에 새로운 팀을 찾는 것에 대해 생각해보십시오. 그들이이 구조에 대한 이유가 있다면, 그 이유를 장점으로 고려하십시오.
벤 리

답변:


95

팀은 일반적인 문제인 객체 거부 문제로 어려움을 겪고 있습니다 .

관련된 모든 정보가 포함 된 단일 질문을 보유한 클래스 대신 모든 질문 을 단일 인스턴스로 question보유 하는 클래스를 작성하려고 합니다.

그것은 잘못된 길이며 , 많은 일을 복잡하게 만듭니다 ! 병렬 배열 (또는 목록)을 정렬 (및 셔플 링)하는 것은 불쾌한 일이며 일반적으로 피하기를 원하기 때문에 일반적인 API가 없습니다 .

다음과 같이 코드를 재구성하는 것이 좋습니다.

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>();

이렇게하면 질문을 섞는 것이 간단 해집니다 (을 사용하여 Collections.shuffle()).

Collections.shuffle(questionList);

39
데이터 거부도 아닙니다
jk.

22

당신은하지 않습니다. 색인의 다른 목록 / 대기열을 만들어 섞습니다. 그런 다음 다른 컬렉션의 "셔플"순서를 유발하는 인덱스를 반복합니다.

시나리오를 벗어난 상황에서도 별도의 주문 컬렉션은 여러 가지 이점을 제공합니다 (병렬, 원본 컬렉션을 다시 배치 할 때의 속도 등)


10
난이 투표를하기를 꺼려 해요 : 그것은 다음 최고의 솔루션입니다 IFF에 이 디자인은 참으로 고정되어 있지만,이 디자인을 주장하는 것은 그래서 나는 그것에 대해 어떤 제안을주고 싶어하지 않을 것이라고 잘못. (meh, 어쨌든 upvoted ;-))
Joachim Sauer

3
@joachimSauer-동의하지만, 원본 컬렉션을 정적으로 유지하면서 경로를 변경 해야하는 다른 (불쾌한) 시나리오가 많이 있습니다.
Telastyn

4
예, 알아요 그리고 이러한 상황에 맞는 인덱스를 섞는 것이 올바른 방법입니다. 내 유일한 두려움은 OP 팀이 디자인을 다시 보지 않고도 이것을 받아들이고 "충분히 좋다"고 말할 것입니다.
Joachim Sauer

1
이 답변은 특히 기본 컬렉션 클래스 또는 구조를 수정 / 코딩 할 자유가없는 경우, 특히 OS 유지 관리 컬렉션에 대한 API를 사용해야하는 경우에 유용합니다. 인덱스를 섞는 것은 훌륭한 통찰력이며 기본 디자인을 다시 실행하는 것만 큼 통찰력이 없더라도 그 자체로 유지됩니다.
hardmath

@Joachim Sauer : 실제로 지수를 섞는 것이 반드시 언급 된대로 문제에 대한 최상의 해결책은 아닙니다. 대안에 대한 내 대답을 참조하십시오.
Michael Borgwardt

16

나는 올바른 해결책이 적절한 객체 모델을 사용하는 것이라는 다른 답변에 동의합니다.

그러나 실제로 여러 목록을 동일한 방식으로 섞는 것은 매우 쉽습니다.

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);
...

그것은 ... 깔끔한 방법입니다! 이제 "정렬"사례의 경우이 방법을 적용 할 때 정렬 된 목록을 생성하는 시드를 찾은 다음이 목록으로 모든 목록을 섞습니다.
Joachim Sauer

1
@JoachimSauer : 글쎄, 정렬은 문제의 일부가 아니 었습니다. 주어진 RNG에 대해 그러한 씨앗을 찾는 체계적인 방법이 있는지 여부는 흥미로운 질문입니다.
Michael Borgwardt

2
당신이 17 개 질문을 극복 한 번 @MichaelBorgwardt 당신은 단순히 임의 사용 (! log_2 (17) 48.33 =) 48 비트 자바를 가능 셔플의 양을 표현할 수 없습니다
래칫 괴물

@ratchetfreak : 나에게 실제 문제처럼 들리지 않습니다. 필요한 경우 SecureRandom을 대신 사용하는 것은 쉽지 않습니다.
Michael Borgwardt 2013 년

4
@Telastyn : 인덱스 목록은 IMO의 간접적 인 계층으로, 솔루션을 개념적으로 더 복잡하게 만들며, 성능이 향상되는지 여부는 셔플 후 목록에 액세스하는 빈도에 따라 다릅니다. 그러나 사람이 퀴즈에 대답 할 수있는 현실적인 크기를 고려할 때 성능 차이는 중요하지 않습니다.
Michael Borgwardt 2014 년

3

수업 만들기 Question2:

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

그런 다음 매핑 함수 작성 questionArrayList<Question2>사용, Collection.Shuffle그 결과에 대한, 그리고 매핑을위한 두 번째 함수 작성 ArrayList<Question2>에 다시 question.

그 후 팀으로 가서 ArrayList<Question2>대신 대신 사용하면 question코드를 많이 개선 할 수 있다는 확신을 심어보십시오 . 불필요한 변환을 많이 절약 할 수 있기 때문입니다.


1
이것은 좋은 생각이지만, 디자인을 변경하려는 사전 시도가 실패한 후에 만 ​​가능합니다.
Sebastian Redl

@SebastianRedl : 때로는 코드로 솔루션을 보여줄 때 더 나은 디자인을 사람들에게 확신시키는 것이 더 쉬운 경우가 있습니다.
Doc Brown

1

내 원래 순진하고 잘못된 대답 :

가지고있는 각 목록에 대해 (적어도) n임의의 숫자를 만들고 for 루프의 항목 n 과 항목 i을 교환 하십시오.

의사 코드에서 :

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);

}

최신 정보:

코딩 공포 기사를 지적 해 주신 the_lotus에게 감사드립니다. 어쨌든 Jeff Atwood는 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);
}

여기서 가장 큰 차이점은 각 요소가 한 번만 교체된다는 것입니다.

그리고 다른 답변은 객체 모델에 결함이 있음을 올바르게 설명하지만 위치를 변경하지 않을 수도 있습니다. 따라서 Fisher-Yates 알고리즘은 데이터 모델을 변경하지 않고도 문제를 해결할 수 있습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.