Как перемешать массив (shuffle) в Swift? Подробное руководство
Правильное перемешивание массива в 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 Ответов пока нет.
Войдите чтобы оставить ответ