Это достаточный способ перетасовать колоду карт?

Я пытаюсь перетасовать колоду карт в своем приложении и использую следующий код. Будет ли это достаточно рандомизировать колоду? Я почти уверен, что это просто хочет другое мнение. Спасибо!

for (int i = 0; i < 40000; i++) {
    int randomInt1 = arc4random() % [deck.cards count];
    int randomInt2 = arc4random() % [deck.cards count];
    [deck.cards exchangeObjectAtIndex:randomInt1 withObjectAtIndex:randomInt2];

РЕДАКТИРОВАТЬ: В случае, если кто-то задается вопросом или столкнется с этим в будущем. Это то, что я использовал, чтобы перетасовать свою колоду карт, это реализация алгоритма Фишера-Йейтса. Я получил это из сообщения @MartinR, предложенного ниже, которое можно найти здесь: Как лучше всего перемешать NSMutableArray?

NSUInteger count = [deck.cards count];
    for (uint i = 0; i < count; ++i)
    {
        // Select a random element between i and end of array to swap with.
        int nElements = count - i;
        int n = arc4random_uniform(nElements) + i;
        [deck.cards exchangeObjectAtIndex:i withObjectAtIndex:n];
    }

person mattman88    schedule 30.04.2014    source источник
comment
Два улучшения: 1) Сохраните [deck.cards count] в переменной перед циклом, поэтому вам не нужно вызывать этот метод 80 000 раз. 2) Используйте arc4random_uniform(count) вместо arc4random с модулем.   -  person rmaddy    schedule 30.04.2014
comment
@Rob Я имел в виду, что [deck.cards count] следует хранить в переменной перед циклом, а затем в цикле следует использовать переменную. Это экономит 80 000 вызовов методов.   -  person rmaddy    schedule 30.04.2014
comment
@rmaddy А, я неправильно понял твою мысль. Вы совершенно правы, что он может избежать избыточных вызовов методов. Я сосредоточился на более вопиющей проблеме, заключающейся в том, что он делает что-то 40 000 раз, тогда как при правильном алгоритме [deck.card count] итераций справится с задачей идеально.   -  person Rob    schedule 30.04.2014
comment
@Rob, да, сокращение цикла с 40 000 до 52 - гораздо лучшее улучшение. :)   -  person rmaddy    schedule 30.04.2014
comment
Привет, ребята, посмотрев на все, я думаю, что я просто пойду с алгоритмом Фишера-Йейтса. Похоже, это делает трюк довольно хорошо! Спасибо за помощь. Вот код, который я использовал, если кому-то интересно. интервал jj = 0; for (int i = [количество карточек]; i ›= 1; i--) { jj = arc4random() % i; [deck.cards exchangeObjectAtIndex:jj withObjectAtIndex:i-1]; }   -  person mattman88    schedule 30.04.2014


Ответы (3)


Ваш код должен работать довольно хорошо, если [количество карточек] ‹ 40000, но лучше следовать

for (int i = [deck.cards count] - 1; i > 0 ; i--) {
    int randomInt1 = arc4random_uniform(i + 1);
    [deck.cards exchangeObjectAtIndex:randomInt1 withObjectAtIndex:i];
}

из документов:

arc4random_uniform() вернет равномерно распределенное случайное число меньше, чем upper_bound. arc4random_uniform() рекомендуется вместо таких конструкций, как ``arc4random() % upper_bound'', поскольку она позволяет избежать "смещения по модулю", когда верхняя граница не является степенью двойки.

person Avt    schedule 30.04.2014
comment
Почему вы изменили счет с прямого на обратный? - person Guy Kogus; 30.04.2014
comment
@GuyKogus для использования arc4random_uniform без дополнительных вычислений. - person Avt; 30.04.2014
comment
Это не правильная реализация. Пожалуйста, смотрите мой ответ. - person Guy Kogus; 30.04.2014
comment
Это все еще неправильно. По мере того, как i приближается к 0, диапазон элементов, с которыми вы меняете местами объекты, становится меньше. Рандомизация предназначена для охвата всего размера массива. Измените i + 1 на [deck.cards count], и вы попадете в точку. - person Guy Kogus; 30.04.2014
comment
@GuyKogus Здесь это не имеет значения. Например, у первого элемента есть возможность менять свое местоположение "количество раз" и занимать любое место. Так что, когда я приближаюсь к 0, элементы на первых позициях, скорее всего, уже заменены некоторыми другими. - person Avt; 30.04.2014
comment
@GuyKogus Это правильная реализация современного алгоритма Перетасовка Фишера-Йейтса. - person Rob; 30.04.2014
comment
Это действительно так! Моя ошибка. - person Guy Kogus; 01.05.2014

Вот правильно реализованный алгоритм Фишера-Йейтса. И да, это достаточно рандомизирует ваш массив, я использовал его много раз, и это просто замечательно!

NSUInteger count = [deck.cards count];
if (count > 0) {
    for (NSUInteger i = count - 1; i > 0 ; --i) {
        [deck.cards exchangeObjectAtIndex:i
                        withObjectAtIndex:arc4random_uniform(i + 1)];
    }
}
person Guy Kogus    schedule 30.04.2014
comment
При повторном прочтении деталей алгоритма FY я понимаю, что это не то же самое. Но он по-прежнему работает очень хорошо, так что неважно. - person Guy Kogus; 30.04.2014
comment
Перетасовка таким образом на самом деле имеет небольшую проблему, поэтому я бы не сказал, что она все еще работает очень хорошо. См.: Опасность наивности. Правильная тасовка FY позволяет избежать этого. - person Blastfurnace; 30.04.2014
comment
Ваше предложение всегда менять местами индекс во всем диапазоне на самом деле вносит наблюдаемую погрешность. См. раздел ошибки реализации на странице Fisher-Yates, который что постоянный выбор j из всего диапазона допустимых индексов массива на каждой итерации также дает результат, который является смещенным, хотя и менее очевидным. Я смонтировал как ответ Avt, так и ваш, и предвзятость, вызванная вашим, была нетривиальной (хотя я не уверен, заметили бы вы, если бы сидели там, наблюдая, как приложение раздает карты). - person Rob; 30.04.2014
comment
@GuyKogus Спасибо, я решил пойти по этому пути! - person mattman88; 30.04.2014
comment
@user3361608: Я думаю, это весело. - person Blastfurnace; 30.04.2014
comment
@Blastfurnace, черт возьми, почему это так сложно, можно подумать, что четкий ответ будет доступен в Интернете только для перетасовки карт. - person mattman88; 01.05.2014
comment
@ user3361608: Я знаю, но этот код немного сломан, и исправить его, чтобы он был правильным, тасовкой Фишера-Йейтса очень просто. Этот код выглядит простым и кажется правильным, но именно поэтому он так пагубен. Я верю, что ты поступишь правильно. - person Blastfurnace; 01.05.2014
comment
@Blastfurnace, еще раз просмотрев вики-страницу Fisher-Yates, я думаю, что правильный путь - ограничить thewithObjectAtIndex:arc4random_uniform(count) от n до 1 по мере прохождения итерации. это то, что вы имели ввиду? - person mattman88; 01.05.2014
comment
@ user3361608: Вы можете реализовать это самостоятельно на основе вики-страницы или есть связанные вопросы/ответы прямо здесь, на SO или в ответе Avt выше, которые могут помочь. - person Blastfurnace; 01.05.2014
comment
@Blastfurnace отлично, это то, что я имел в виду! Спасибо за вашу помощь. - person mattman88; 01.05.2014
comment
Вы, ребята, потрясающие! Спасибо, что научил меня этому. Я обновляю ответ. - person Guy Kogus; 01.05.2014

В зависимости от того, как вы реализовали свою колоду, вы можете просто использовать Collections.sort() или вы можете использовать ArrayList, предполагая, что ваша реализация похожа на следующую

    ArrayList<Integer> originalDeck = new ArrayList<Integer>();
    ArrayList<Integer> randomDeck = new ArrayList<Integer>();

    //Initalize array
    for (int i = 0; i < 52; i++) {
        originalDeck.add(i, i);
    }

    //Smart Implementation
    Collections.shuffle(originalDeck);

    Collections.sort(originalDeck);

    //Long Implementation
    int i=0, max = 0;
    while (originalDeck.size() != 0) {
        max = originalDeck.size();
        i = (int) (Math.random() * max); // goes from 0 to size-1 so always in bounds
        randomDeck.add(originalDeck.remove(i));
    }
person primChareka    schedule 25.10.2018