Выбрать случайный элемент из массива

189

Предположим, у меня есть массив, и я хочу выбрать один элемент случайным образом.

Какой самый простой способ сделать это?

Очевидный путь был бы array[random index]. Но, может быть, есть что-то вроде рубина array.sample? Или, если нет, такой метод может быть создан с использованием расширения?

Фела Винкельмолен
источник
1
Вы уже пробовали разные методы?
префект форд
Я бы попробовал array[random number from 0 to length-1], но я не могу найти, как генерировать случайное int в swift, я бы спросил его о переполнении стека, если бы меня не заблокировали :) Я не хотел загрязнять вопрос половиной решений, когда, возможно, есть что-то вроде ruby'sarray.sample
Фела Винкельмолен
1
Вы используете arc4random () так же, как в Obj-C
Arbitur
Нет объяснения, почему ваш вопрос не получил такую ​​же обратную связь, как у JQuery. Но в целом вы должны следовать этим рекомендациям при публикации вопроса. Как задать хороший вопрос? , Создайте впечатление, что вы приложили немного усилий, чтобы найти решение, прежде чем просить кого-то еще о помощи. Когда я гуглю "выберите случайное число быстро", первая страница заполняется ответами, предлагающими arc4random_uniform. Кроме того, RTFD ... «читать документацию». Удивительно, сколько вопросов можно ответить таким образом.
Остин,
Спасибо за ваш отзыв. Да, я полагаю, что должен был ответить на вопрос сам, но это казалось достаточно простым, чтобы было приятно дать кому-то еще почти бесплатные очки репутации. И я написал это, когда даже официальные документы Apple Swift не были общедоступными, в то время результатов Google точно не было. Но вопрос был когда-то в -12, так что я вполне уверен, что в конечном итоге он будет положительным :)
Фела Винкельмолен

Ответы:

321

Swift 4.2 и выше

Новый рекомендуемый подход является встроенным методом по протоколу сбора: randomElement(). Он возвращает необязательный параметр, чтобы избежать пустого регистра, который я предполагал ранее.

let array = ["Frodo", "Sam", "Wise", "Gamgee"]
print(array.randomElement()!) // Using ! knowing I have array.count > 0

Если вы не создаете массив и не уверены, что count> 0, вы должны сделать что-то вроде:

if let randomElement = array.randomElement() { 
    print(randomElement)
}

Swift 4.1 и ниже

Просто чтобы ответить на ваш вопрос, вы можете сделать это для достижения случайного выбора массива:

let array = ["Frodo", "sam", "wise", "gamgee"]
let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
print(array[randomIndex])

Кастинги безобразны, но я считаю, что они необходимы, если у кого-то еще нет другого пути.

Лукас Дерро
источник
4
Почему Swift не предлагает генератор случайных чисел, который возвращает Int? Эта 2-я строка кажется очень многословной только для того, чтобы вернуть случайно выбранный Int. Есть ли какое-то вычислительное / синтаксическое преимущество для возврата UInt32 по сравнению с Int? Кроме того, почему Swift не предлагает альтернативу Int для этой функции и не позволяет пользователю указать, какой тип целого числа он хотел бы вернуть?
Остин,
Чтобы добавить примечание, этот метод генератора случайных чисел может предотвратить «смещение по модулю». Обратитесь man arc4randomи stackoverflow.com/questions/10984974/…
Кент Ляу
1
@AustinA, Swift 4.2 DOES имеет встроенную функцию генератора случайных чисел, которая реализована для всех скалярных типов данных, которые вы можете ожидать: Int, Double, Float, UInt32 и т. Д. И позволяет вам предоставлять целевые диапазоны для значений. Очень кстати. Вы можете использовать массив [Int.random (0 .. <array.count)] `в Swift 4.2
Дункан C
Я бы хотел, чтобы Swift 4.2 реализовал removeRandomElement()функцию в дополнение к randomElement(). Он будет смоделирован removeFirst(), но удалит объект со случайным индексом.
Дункан C
@DuncanC Вам следует избегать 0..<array.count(по нескольким причинам, главным из которых является то, что он не работает для срезов и подвержен ошибкам). Вы можете сделать let randomIndex = array.indices.randomElement(), а затем let randomElement = array.remove(at: randomIndex). Вы могли бы даже вставить это let randomElement = array.remove(at: array.indices.randomElement()).
Александр - Восстановить Монику
137

Следуя тому, что сказал Лукас, вы можете создать расширение для класса Array следующим образом:

extension Array {
    func randomItem() -> Element? {
        if isEmpty { return nil }
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Например:

let myArray = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16]
let myItem = myArray.randomItem() // Note: myItem is an Optional<Int>
Фаэ Дипский
источник
2
В Swift 2 Tбыл переименован в Element.
GDanger
25
Обратите внимание, что пустой массив вызовет сбой здесь
Берик
1
@Berik Ну, вы можете вернуть необязательный элемент, а затем всегда делать guardпроверку, чтобы убедиться, что массив пуст, а затем вернуться nil.
Хариш
1
Согласовано. Массивы выходят из-за пределов, так что они могут быть эффективными. Призвание arc4randomделает любой прирост производительности совершенно незначительным. Я обновил ответ.
Берик,
45

Версия Swift 4 :

extension Collection where Index == Int {

    /**
     Picks a random element of the collection.

     - returns: A random element of the collection.
     */
    func randomElement() -> Iterator.Element? {
        return isEmpty ? nil : self[Int(arc4random_uniform(UInt32(endIndex)))]
    }

}
Андрей Гордеев
источник
Это может привести к сбою с индексом за пределы на сборниках , гдеstartIndex != 0
дан
21

В Swift 2.2 это можно обобщить так, чтобы мы имели:

UInt.random
UInt8.random
UInt16.random
UInt32.random
UInt64.random
UIntMax.random

// closed intervals:

(-3...3).random
(Int.min...Int.max).random

// and collections, which return optionals since they can be empty:

(1..<4).sample
[1,2,3].sample
"abc".characters.sample
["a": 1, "b": 2, "c": 3].sample

Во-первых, реализуем статическое randomсвойство для UnsignedIntegerTypes:

import Darwin

func sizeof <T> (_: () -> T) -> Int { // sizeof return type without calling
    return sizeof(T.self)
}

let ARC4Foot: Int = sizeof(arc4random)

extension UnsignedIntegerType {
    static var max: Self { // sadly `max` is not required by the protocol
        return ~0
    }
    static var random: Self {
        let foot = sizeof(Self)
        guard foot > ARC4Foot else {
            return numericCast(arc4random() & numericCast(max))
        }
        var r = UIntMax(arc4random())
        for i in 1..<(foot / ARC4Foot) {
            r |= UIntMax(arc4random()) << UIntMax(8 * ARC4Foot * i)
        }
        return numericCast(r)
    }
}

Тогда для ClosedIntervals с UnsignedIntegerTypeоценками:

extension ClosedInterval where Bound : UnsignedIntegerType {
    var random: Bound {
        guard start > 0 || end < Bound.max else { return Bound.random }
        return start + (Bound.random % (end - start + 1))
    }
}

Затем (немного больше) для ClosedIntervals с SignedIntegerTypeграницами (используя вспомогательные методы, описанные ниже):

extension ClosedInterval where Bound : SignedIntegerType {
    var random: Bound {
        let foot = sizeof(Bound)
        let distance = start.unsignedDistanceTo(end)
        guard foot > 4 else { // optimisation: use UInt32.random if sufficient
            let off: UInt32
            if distance < numericCast(UInt32.max) {
                off = UInt32.random % numericCast(distance + 1)
            } else {
                off = UInt32.random
            }
            return numericCast(start.toIntMax() + numericCast(off))
        }
        guard distance < UIntMax.max else {
            return numericCast(IntMax(bitPattern: UIntMax.random))
        }
        let off = UIntMax.random % (distance + 1)
        let x = (off + start.unsignedDistanceFromMin).plusMinIntMax
        return numericCast(x)
    }
}

... где unsignedDistanceTo, unsignedDistanceFromMinи plusMinIntMaxвспомогательные методы могут быть реализованы следующим образом :

extension SignedIntegerType {
    func unsignedDistanceTo(other: Self) -> UIntMax {
        let _self = self.toIntMax()
        let other = other.toIntMax()
        let (start, end) = _self < other ? (_self, other) : (other, _self)
        if start == IntMax.min && end == IntMax.max {
            return UIntMax.max
        }
        if start < 0 && end >= 0 {
            let s = start == IntMax.min ? UIntMax(Int.max) + 1 : UIntMax(-start)
            return s + UIntMax(end)
        }
        return UIntMax(end - start)
    }
    var unsignedDistanceFromMin: UIntMax {
        return IntMax.min.unsignedDistanceTo(self.toIntMax())
    }
}

extension UIntMax {
    var plusMinIntMax: IntMax {
        if self > UIntMax(IntMax.max) { return IntMax(self - UIntMax(IntMax.max) - 1) }
        else { return IntMax.min + IntMax(self) }
    }
}

Наконец, для всех коллекций, где Index.Distance == Int:

extension CollectionType where Index.Distance == Int {
    var sample: Generator.Element? {
        if isEmpty { return nil }
        let end = UInt(count) - 1
        let add = (0...end).random
        let idx = startIndex.advancedBy(Int(add))
        return self[idx]
    }
}

... который можно немного оптимизировать для целых чисел Range:

extension Range where Element : SignedIntegerType {
    var sample: Element? {
        guard startIndex < endIndex else { return nil }
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    }
}

extension Range where Element : UnsignedIntegerType {
    var sample: Element? {
        guard startIndex < endIndex else { return nil }
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    }
}
Милош
источник
18

Вы также можете использовать встроенную функцию random () Swift для расширения:

extension Array {
    func sample() -> Element {
        let randomIndex = Int(rand()) % count
        return self[randomIndex]
    }
}

let array = [1, 2, 3, 4]

array.sample() // 2
array.sample() // 2
array.sample() // 3
array.sample() // 3

array.sample() // 1
array.sample() // 1
array.sample() // 3
array.sample() // 1
NatashaTheRobot
источник
Фактически random () происходит из стандартной библиотеки C, вы можете увидеть его и друзей в Terminal, «man random». Но рад, что вы указали на наличие!
Дэвид Х
1
каждый раз при запуске
получается одна и
1
@iTSangar ты прав! rand () является правильным для использования. Обновление моего ответа.
NatashaTheRobot
6
Это также подвержено смещению по модулю.
Эйдан Гомес
@mattt написал хорошую статью о генерации случайных чисел . TL; DR любой из семейства arc4random - лучший выбор.
Элиталон
9

Еще одно предложение Swift 3

private extension Array {
    var randomElement: Element {
        let index = Int(arc4random_uniform(UInt32(count)))
        return self[index]
    }
}
каталажка
источник
4

После ответа других, но с поддержкой Swift 2.

Swift 1.x

extension Array {
    func sample() -> T {
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Swift 2.x

extension Array {
    func sample() -> Element {
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Например:

let arr = [2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29, 31]
let randomSample = arr.sample()
Эйдан Гомес
источник
2

Альтернативная функциональная реализация с проверкой на пустой массив.

func randomArrayItem<T>(array: [T]) -> T? {
  if array.isEmpty { return nil }
  let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
  return array[randomIndex]
}

randomArrayItem([1,2,3])
Евгений
источник
2

Вот расширение для массивов с проверкой пустого массива для большей безопасности:

extension Array {
    func sample() -> Element? {
        if self.isEmpty { return nil }
        let randomInt = Int(arc4random_uniform(UInt32(self.count)))
        return self[randomInt]
    }
}

Вы можете использовать это так просто, как это :

let digits = Array(0...9)
digits.sample() // => 6

Если вы предпочитаете Framework, в котором также есть несколько удобных функций, обратитесь к HandySwift . Вы можете добавить его в свой проект через Carthage, а затем использовать его точно так же, как в примере выше:

import HandySwift    

let digits = Array(0...9)
digits.sample() // => 8

Кроме того, он также включает возможность получить несколько случайных элементов одновременно :

digits.sample(size: 3) // => [8, 0, 7]
Jeehut
источник
2

Swift 3

импортировать GameKit

func getRandomMessage() -> String {

    let messages = ["one", "two", "three"]

    let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: messages.count)

    return messages[randomNumber].description

}
ослепление
источник
2

Swift 3 - простой в использовании.

  1. Создать массив

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. Создать случайный цвет

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. Установите этот цвет для вашего объекта

    your item = arrayOfColors[Int(randomColor)]

Вот пример из SpriteKitпроекта, обновляющего SKLabelNodeслучайным образом String:

    let array = ["one","two","three","four","five"]

    let randomNumber = arc4random() % UInt32(array.count)

    let labelNode = SKLabelNode(text: array[Int(randomNumber)])
Тимми Соренсен
источник
2

Если вы хотите получить более одного случайного элемента из вашего массива без дубликатов , GameplayKit предоставит вам:

import GameplayKit
let array = ["one", "two", "three", "four"]

let shuffled = GKMersenneTwisterRandomSource.sharedRandom().arrayByShufflingObjects(in: array)

let firstRandom = shuffled[0]
let secondRandom = shuffled[1]

У вас есть пара вариантов случайности, смотрите GKRandomSource :

GKARC4RandomSourceКласс использует алгоритм , аналогичный тому , что используется в arc4random семейства функций C. (Однако экземпляры этого класса не зависят от вызовов функций arc4random.)

GKLinearCongruentialRandomSourceКласс использует алгоритм , который быстрее, но менее случайный, чем класс GKARC4RandomSource. (В частности, младшие биты сгенерированных чисел повторяются чаще, чем старшие биты.) Используйте этот источник, когда производительность важнее, чем надежная непредсказуемость.

GKMersenneTwisterRandomSourceКласс использует алгоритм , который медленнее, но более случайный, чем класс GKARC4RandomSource. Используйте этот источник, когда важно, чтобы использование случайных чисел не показывало повторяющиеся шаблоны, а производительность была менее важной.

bcattle
источник
1

Я считаю, что использование GameKit GKRandomSource.sharedRandom () работает лучше всего для меня.

import GameKit

let array = ["random1", "random2", "random3"]

func getRandomIndex() -> Int {
    let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(array.count)
    return randomNumber

или вы можете вернуть объект с выбранным случайным индексом. Убедитесь, что функция сначала возвращает строку, а затем возвращает индекс массива.

    return array[randomNumber]

Коротко и точно.

djames04
источник
1

На данный момент есть встроенный метод Collection:

let foods = ["🍕", "🍔", "🍣", "🍝"]
let myDinner = foods.randomElement()

Если вы хотите извлечь nиз коллекции до случайных элементов, вы можете добавить такое расширение:

extension Collection {
    func randomElements(_ count: Int) -> [Element] {
        var shuffledIterator = shuffled().makeIterator()
        return (0..<count).compactMap { _ in shuffledIterator.next() }
    }
}

И если вы хотите, чтобы они были уникальными, вы можете использовать a Set, но элементы коллекции должны соответствовать Hashableпротоколу:

extension Collection where Element: Hashable {
    func randomUniqueElements(_ count: Int) -> [Element] {
        var shuffledIterator = Set(shuffled()).makeIterator()
        return (0..<count).compactMap { _ in shuffledIterator.next() }
    }
}
Gigisommo
источник
0

Последний код swift3 попробуйте его нормально работает

 let imagesArray = ["image1.png","image2.png","image3.png","image4.png"]

        var randomNum: UInt32 = 0
        randomNum = arc4random_uniform(UInt32(imagesArray.count))
        wheelBackgroundImageView.image = UIImage(named: imagesArray[Int(randomNum)])
Гангиредди Рами Редди
источник
-2

Я нашел совершенно другой способ сделать это, используя новые функции, представленные в Swift 4.2.

// 👇🏼 - 1 
public func shufflePrintArray(ArrayOfStrings: [String]) -> String {
// - 2 
       let strings = ArrayOfStrings
//- 3
       var stringans =  strings.shuffled()
// - 4
        var countS = Int.random(in: 0..<strings.count)
// - 5
        return stringans[countS] 
}

  1. мы объявили функцию с параметрами, которая принимает массив строк и возвращает строку.

  2. Затем мы берем ArrayOfStrings в переменной.

  3. Затем мы вызываем перемешанную функцию и сохраняем ее в переменной. (Поддерживается только в 4.2)
  4. Затем мы объявляем переменную, которая сохраняет перемешанное значение общего количества строк.
  5. Наконец, мы возвращаем перетасованную строку со значением индекса countS.

В основном это тасование массива строк, а также случайный выбор числа от общего числа отсчетов, а затем возврат случайного индекса перетасованного массива.

Начос и Читос
источник