Ефективний спосіб переміщення предметів


20

Я пишу програму для певного програмного забезпечення для вікторини. У мене є клас запитань, що містить ArrayLists для запитання, відповіді, варіантів, міток та негативних оцінок. Щось на зразок цього:

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>вводив як змінні екземпляри, а потім використовував би Collections.shuffleметод для переміщення об'єктів. Але моя команда наполягає на цій конструкції.

Тепер клас запитань містить зростаючі списки ArrayLists, коли робиться запис до питань. Як перетасувати питання зараз?


30
Я ненавиджу говорити в абсолютах, але якщо ваша команда наполягає на такому дизайні, то вони помиляються. Скажіть їм! Скажіть їм, що я так сказав (і я написав це в Інтернеті, тож маю бути прав).
Йоахім Зауер

10
Так, скажіть їм, що тут багато людей, які говорять вам про те, що такий дизайн є типовою помилкою для початківців.
Док Браун

6
З цікавості: які переваги бачить ваша команда в цьому дизайні?
Йоахім Зауер

9
Умови іменування Java - це CamelCase для імен класів та camelCase для імен змінних.
Тулен Кордова

Я думаю, вам потрібно протистояти своїй команді щодо цього жахливого дизайнерського рішення. Якщо вони продовжують наполягати, з’ясуйте, чому. Якщо це просто впертість, можливо, почніть думати про пошук нової команди в не надто віддаленому майбутньому. Якщо вони мають причини цієї структури, то розгляньте ці причини по суті.
Бен Лі

Відповіді:


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
Я не хочу це проголосувати: це наступне найкраще рішення, якщо ця конструкція справді виправлена, але наполягати на такому дизайні настільки неправильно, що я не хотів би давати будь-які пропозиції щодо цього. (Мех, все-таки оголошений ;-))
Йоахім Зауер

3
@joachimSauer - хоча я згоден, існує безліч інших (менш образливих) сценаріїв, коли оригінальна колекція повинна залишатися статичною, тоді як шлях до них повинен змінюватися.
Теластин

4
так, я знаю. І перемішування колекції індексів - це правильний підхід для цих ситуацій. Мене єдине страх полягає в тому, що команда ОП просто сприйме це і скаже «досить добре», не переглядаючи їх дизайн.
Йоахім Зауер

1
Цей відповідь цінний особливо для випадку, коли людина не має свободи переглядати / перекодувати базовий клас або структуру колекції, наприклад, це стосується API до колекції, що підтримується ОС. Переміщення індексів є чудовим розумінням і є само собою, навіть якщо воно не таке проникливе розуміння, як переробка основної конструкції.
hardmath

@Joachim Sauer: насправді переміщення індексів не обов'язково є найкращим рішенням проблеми, як заявлено. Дивіться мою відповідь щодо альтернативи.
Майкл Боргвардт

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

Ось… акуратний спосіб це зробити! Тепер для випадку "сортування" нам просто потрібно знайти насіння, яке створює відсортований список, коли застосовується таким чином, а потім ми перетасовуємо всі списки з цим насінням!
Йоахім Зауер

1
@JoachimSauer: ну, сортування не було частиною проблеми. Хоча цікавим є питання, чи існує систематичний спосіб знайти таке насіння для даної РНГ.
Майкл Боргвардт

2
@MichaelBorgwardt як тільки ви отримаєте більше 17 питань ви просто не можете висловити кількість можливих перестановок в 48 біт на Java Випадкові застосування (log_2 (17) = 48.33!)
тріскачки урод

@ratchetfreak: не здається справжньою проблемою для мене. І використовувати бажання SecureRandom тривіально, якщо потрібно.
Майкл Боргвардт

4
@Telastyn: список індексів є IMO шаром непрямості, який робить ваше рішення концептуально складнішим, і чи є він більш-менш ефективним, залежить від того, як часто до списків звертаються після перетасування. Але відмінності в ефективності були б незначними, враховуючи реалістичні розміри вікторини, на яку люди відповіли.
Майкл Боргвардт

3

Створіть клас Question2:

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

Потім створіть функцію відображення questionдо ArrayList<Question2>, використайте Collection.Shuffleдля цього результату та створіть другу функцію для відображення ArrayList<Question2>назад до question.

Після цього перейдіть до своєї команди та спробуйте переконати їх, що використання ArrayList<Question2>замість цього questionзначно покращить їхній код, оскільки це заощадить їм чимало непотрібних перетворень.


1
Це гарна ідея, але лише після апріорних спроб змінити дизайн не вдалося.
Себастьян Редл

@SebastianRedl: іноді легше переконати людей у ​​кращому дизайні, просто показуючи їм рішення в коді.
Док Браун

1

Моя оригінальна наївна і неправильна відповідь:

Просто створіть (принаймні) nвипадкові числа та обміняйте елемент n з елементом iу циклі for для кожного списку, який у вас є.

У псевдокоді:

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 за вказівку статті жахів кодування. Зараз я відчуваю себе набагато розумнішим :-) У будь-якому випадку Джефф Етвуд також показує, як це зробити правильно, використовуючи алгоритм Фішера-Йейтса :

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

Основна відмінність тут полягає в тому, що кожен елемент замінюється лише один раз.

І хоча інші відповіді правильно пояснюють, що ваша об'єктна модель є помилкою, ви, можливо, не зможете її змінити. Тож алгоритм Фішера-Йейта вирішив би вашу проблему без зміни вашої моделі даних.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.