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


189

Припустимо, у мене є масив, і я хочу вибрати один елемент навмання.

Який був би найпростіший спосіб зробити це?

Очевидним був би спосіб array[random index]. Але, можливо, є щось на кшталт рубіну array.sample? Або як не вдалося створити такий метод за допомогою розширення?


1
Ви випробували ще якісь різні методи?
префект форда

Я б спробував array[random number from 0 to length-1], але я не можу знайти, як генерувати випадковий int швидко, я б попросив його переповнювати стек, якби мене не заблокували :) Я не хотів забруднювати питання напіврозв’язаннями, якщо, можливо, є щось на кшталт ruby'sarray.sample
Fela Winkelmolen

1
Ви використовуєте arc4random (), як би ви хотіли в Obj-C
Arbitur

Немає пояснень, чому ваше запитання не отримало таких самих відгуків, як колега JQuery. Але загалом слід дотримуватися цих вказівок, коли публікуєте запитання. Як задати гарне запитання? . Зробіть це схожим на те, що ви доклали трохи зусиль для пошуку рішення, перш ніж попросити когось іншого про допомогу. Коли я в google "вибираю випадковий номер швидкого", перша сторінка заповнюється відповідями, що пропонують arc4random_uniform. Також RTFD ... "читайте документацію". Дивно, на скільки питань можна відповісти таким чином.
Остін A

Дякую за добрі відгуки. Так, я думаю, я мав би відповісти на це питання сам, але це здалося досить простим, що було приємно дати комусь ще майже безкоштовні бали репутації. І я писав це, коли навіть офіційні швидкі документи Apple не були публічними, на той час остаточно не було результатів Google. Але питання було колись у -12, тому я майже впевнений, чи буде згодом позитивним :)
Fela Winkelmolen,

Відповіді:


321

Швидкий 4.2 та вище

Новий рекомендований підхід є вбудованим методом по протоколу збору: randomElement(). Він повертається необов'язково, щоб уникнути порожнього випадку, про який я припускав раніше.

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

Якщо ви не створите масив і не гарантовано вважаєте> 0, вам слід зробити щось на кшталт:

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

Швидкий 4.1 та нижче

Просто щоб відповісти на ваше запитання, ви можете це зробити для досягнення вибору випадкового масиву:

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

Кастинги некрасиві, але я вважаю, що вони потрібні, якщо хтось інший не має іншого способу.


4
Чому Swift не пропонує генератор випадкових чисел, який повертає Int? Цей другий рядок здається дуже багатослівним лише для повернення випадково вибраного Int. Чи є якась обчислювальна / синтаксична перевага повернення UInt32 на відміну від Int? Крім того, чому Swift не пропонує альтернативу Int цій функції та не дозволяє користувачеві вказати, який тип цілого числа він хотів би повернути?
Остін A

Щоб додати зауваження, цей метод генератора випадкових чисел може запобігти "зміщення модуля". Посилання man arc4randomта stackoverflow.com/questions/10984974/…
Кент Ляу,

1
@AustinA, Swift 4.2 DOE має вроджену функцію генератора випадкових чисел, реалізовану на всіх скалярних типах даних, яких ви можете очікувати: Int, Double, Float, UInt32 тощо. Це дозволяє вам надати цільові діапазони для значень. Дуже зручно. Ви можете використовувати масив [Int.random (0 .. <array.count)] `у Swift 4.2
Duncan 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
Швидко 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властивості дляUnsignedIntegerType s:

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

Тоді для ClosedIntervalс с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))
    }
}

Потім (трохи більше залученого), для ClosedIntervalс с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]
    }
}

... які можна трохи оптимізувати для цілих Ranges:

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

Насправді випадковий () - з мостової бібліотеки Standard C, ви можете побачити його та друзів у Терміналі, "людина випадкова". Але радий, що ти вказав на доступність!
Девід Н

1
це створює одну і ту ж випадкову послідовність кожного разу, коли вона працює
iTSangar

1
@iTSangar ви праві! rand () є правильним для використання. Оновлення моєї відповіді.
NatashaTheRobot

6
Це також чутливе до зміщення модуля.
Айдан Гомес

@mattt написав чудову статтю про генерування випадкових чисел . TL; DR будь-якого з сімейства arc4random - кращий вибір.
elitalon

9

Ще одна пропозиція Swift 3

private extension Array {
    var randomElement: Element {
        let index = Int(arc4random_uniform(UInt32(count)))
        return self[index]
    }
}

4

Слідом за іншими відповіді, але за підтримки Swift 2.

Швидкий 1.x

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

Швидкий 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]

2

Швидкий 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 :

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

The GKLinearCongruentialRandomSourceКлас використовує алгоритм , який швидше, але менш випадковий, ніж клас GKARC4RandomSource. (Зокрема, низькі біти згенерованих чисел повторюються частіше, ніж високі біти.) Використовуйте це джерело, коли продуктивність важливіша за надійну непередбачуваність.

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


1

Я вважаю, що GKRandomSource.sharedRandom GameKit найкраще працює для мене.

import GameKit

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

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

або ви можете повернути об'єкт у вибраному випадковому індексі. Переконайтеся, що функція спочатку повертає String, а потім повертає індекс масиву.

    return array[randomNumber]

Короткий і до суті.


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

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. ми оголосили функцію з параметрами, приймаючи масив Strings і повертаючи String.

  2. Потім беремо ArrayOfStrings у змінну.

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

В основному це перетасовує масив рядків, а потім також має випадковий вибір числа загальної кількості підрахунків, а потім повертає випадковий індекс перетасованого масиву.

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