Avatar ·

Как перемешать массив (shuffle) в Swift? Подробное руководство

Всем привет! Я новичок в Swift и пишу простое приложение для iOS — викторину. У меня есть массив с вопросами, и я хочу, чтобы при каждом запуске игры вопросы отображались в случайном порядке. Я перепробовал несколько способов, но ничего не работает как надо. Вот мой код: ```swift var questions = ["Вопрос 1", "Вопрос 2", "Вопрос 3", "Вопрос 4", "Вопрос 5"] // Моя попытка написать свою функцию shuffle func myShuffle(array: [String]) -> [String] { var newArray = array for i in 0..>>

Правильное перемешивание массива в Swift

Вы на верном пути, но ваша реализация содержит классическую ошибку — алгоритм Фишера-Йетса требует правильного выбора диапазона для случайного индекса. В вашем коде вы меняете местами текущий элемент с любым случайным, включая уже обработанные, что нарушает равномерность распределения.

Стандартный способ (рекомендуемый)

Начиная с Swift 4.2 (Xcode 10+) у массивов есть встроенные методы:

var questions = ["Вопрос 1", "Вопрос 2", "Вопрос 3", "Вопрос 4", "Вопрос 5"]

// 1. Изменяет исходный массив (мутирующая версия)
questions.shuffle()
print(questions) // ["Вопрос 3", "Вопрос 1", "Вопрос 5", "Вопрос 2", "Вопрос 4"]

// 2. Возвращает новый перемешанный массив (не мутирующая версия)
let shuffled = questions.shuffled()
print(shuffled) // ["Вопрос 4", "Вопрос 2", "Вопрос 1", "Вопрос 5", "Вопрос 3"]
print(questions) // Исходный массив не изменился

Правильная ручная реализация (алгоритм Фишера-Йетса)

Если вы хотите понять, как это работает под капотом:

extension Array {
    mutating func customShuffle() {
        // Идем с конца массива к началу
        for i in stride(from: count - 1, through: 1, by: -1) {
            // Выбираем случайный индекс от 0 до i (включительно)
            let j = Int.random(in: 0...i)
            // Меняем местами элементы на позициях i и j
            swapAt(i, j)
        }
    }
    
    func customShuffled() -> [Element] {
        var copy = self
        copy.customShuffle()
        return copy
    }
}

// Использование
var numbers = [1, 2, 3, 4, 5]
numbers.customShuffle()
print(numbers) // [3, 5, 1, 4, 2]

let shuffledNumbers = numbers.customShuffled()
print(shuffledNumbers) // [2, 4, 5, 1, 3]

Почему ваш код не работал?

  • Проблема: Вы выбирали случайный индекс из всего диапазона 0...newArray.count - 1 на каждом шаге. Это означает, что элемент может быть заменён несколько раз, включая уже "правильно" расположенные элементы.
  • Решение: В алгоритме Фишера-Йетса на шаге i мы выбираем случайный индекс только из необработанной части массива (от 0 до i). Это гарантирует равномерное распределение.

Важные моменты

  • Производительность: shuffle() и shuffled() работают за O(n), где n — количество элементов. Это оптимально.
  • Случайность: Встроенные методы используют криптостойкий генератор случайных чисел SystemRandomNumberGenerator.
  • Типы данных: Методы работают с любыми коллекциями, соответствующими протоколу MutableCollection и RandomAccessCollection.

Пример для вашей викторины

struct Question {
    let text: String
    let answer: String
}

var questions = [
    Question(text: "Столица Франции?", answer: "Париж"),
    Question(text: "2 + 2 = ?", answer: "4"),
    Question(text: "Цвет неба?", answer: "Голубой")
]

// Перемешиваем
questions.shuffle()

// Или создаем новую перемешанную копию
let shuffledQuestions = questions.shuffled()

Используйте встроенные методы — они протестированы, оптимизированы и являются стандартом в сообществе Swift.

<<>>

Альтернативный подход: сортировка со случайным компаратором

Хотя это не рекомендуется для production, иногда полезно знать альтернативные методы:

var array = [1, 2, 3, 4, 5]
let shuffled = array.sorted { _ in Bool.random() }
print(shuffled) // [3, 1, 5, 2, 4] (примерный результат)

Почему это плохая практика?

  • Неравномерное распределение: Алгоритм сортировки не гарантирует, что все перестановки будут равновероятны. Некоторые элементы могут "застревать" на своих местах.
  • Непредсказуемое поведение: Функция сравнения должна быть детерминированной и транзитивной. Использование случайности нарушает это требование, что может привести к ошибкам или бесконечным циклам в некоторых реализациях сортировки.
  • Производительность: Сортировка работает за O(n log n), что медленнее, чем O(n) у shuffle.

Когда это может пригодиться?

Только для быстрого прототипирования или в учебных целях. Никогда не используйте этот метод в реальных приложениях — используйте shuffle() или shuffled().

Дополнительный совет

Если вам нужно воспроизводимое перемешивание (например, для тестирования), используйте свой генератор случайных чисел:

struct SeededGenerator: RandomNumberGenerator {
    var state: UInt64
    mutating func next() -> UInt64 {
        state = state &* 6364136223846793005 &+ 1442695040888963407
        return state
    }
}

var generator = SeededGenerator(state: 42)
var numbers = [1, 2, 3, 4, 5]
numbers.shuffle(using: &generator)
print(numbers) // Всегда будет одинаковый результат при seed = 42