Elige un elemento aleatorio de una matriz

189

Supongamos que tengo una matriz y quiero elegir un elemento al azar.

¿Cuál sería la forma más sencilla de hacer esto?

La forma obvia sería array[random index]. ¿Pero tal vez hay algo como Ruby array.sample? O si no, ¿podría crearse un método de este tipo utilizando una extensión?

Fela Winkelmolen
fuente
1
¿Has probado algún método diferente todavía?
Ford prefecto
Lo intentaría array[random number from 0 to length-1], pero no puedo encontrar cómo generar un int aleatorio en forma rápida, lo preguntaría en el desbordamiento de la pila si no estuviera bloqueado :) No quería contaminar la pregunta con medias soluciones cuando tal vez haya algo así como el rubí dearray.sample
Fela Winkelmolen
1
Utiliza arc4random () como lo haría en Obj-C
Arbitur
No hay explicación de por qué su pregunta no recibió los mismos comentarios que la contraparte de JQuery. Pero en general, debe seguir estas pautas cuando publique una pregunta. ¿Cómo hacer una buena pregunta? . Haga que parezca que se esforzó un poco para encontrar una solución antes de pedir ayuda a otra persona. Cuando busco en Google "elegir número aleatorio rápido", la primera página está llena de respuestas que sugieren arc4random_uniform. Además, RTFD ... "lea la documentación de f'ing". Es sorprendente cuántas preguntas se pueden responder de esta manera.
Austin A
Gracias por sus amables comentarios. Sí, supongo que debería haber respondido la pregunta yo mismo, pero parecía bastante fácil que fuera agradable darle a alguien más los puntos de reputación casi gratuitos. Y lo escribí cuando ni siquiera los documentos oficiales de Apple Swift eran públicos, definitivamente no había resultados de Google en ese momento. Pero la pregunta fue una vez a -12, así que estoy bastante seguro de que eventualmente será positiva :)
Fela Winkelmolen

Respuestas:

321

Swift 4.2 y superior

El nuevo enfoque recomendado es un método integrado en el protocolo de recogida: randomElement(). Devuelve un opcional para evitar el caso vacío contra el que asumí anteriormente.

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

Si no crea la matriz y no se garantiza el conteo> 0, debe hacer algo como:

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

Swift 4.1 y abajo

Solo para responder a su pregunta, puede hacer esto para lograr una selección de matriz aleatoria:

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

Los castings son feos, pero creo que son obligatorios a menos que alguien más tenga otra forma.

Lucas Derraugh
fuente
44
¿Por qué Swift no ofrece un generador de números aleatorios que devuelva un Int? Esta segunda línea parece muy detallada solo para devolver Int elegido aleatoriamente. ¿Existe alguna ventaja computacional / sintáctica para devolver un UInt32 en lugar de un Int? Además, ¿por qué Swift no ofrece una alternativa Int a esta función o no permite que un usuario especifique qué tipo de entero desea que se devuelva?
Austin A
Para agregar una nota, este método generador de números aleatorios podría prevenir el "sesgo de módulo". Consulte man arc4randomy stackoverflow.com/questions/10984974/…
Kent Liau
1
@AustinA, Swift 4.2 TIENE una función nativa de generador de números aleatorios que se implementa en todos los tipos de datos escalares que puede esperar: Int, Double, Float, UInt32, etc. Y le permite proporcionar rangos objetivo para los valores. Muy útil. Puede usar array [Int.random (0 .. <array.count)] `en Swift 4.2
Duncan C
Deseo que Swift 4.2 implemente una removeRandomElement()función además de randomElement(). Se modelaría removeFirst(), pero eliminaría un objeto en un índice aleatorio.
Duncan C
@DuncanC Debe evitarlo 0..<array.count(por múltiples razones, las principales son que no funciona para sectores y es propenso a errores). Puedes hacerlo let randomIndex = array.indices.randomElement(), seguido de let randomElement = array.remove(at: randomIndex). Incluso podrías alinearlo let randomElement = array.remove(at: array.indices.randomElement()).
Alexander - Restablece a Mónica el
137

Refiriéndose a lo que dijo Lucas, podría crear una extensión para la clase Array como esta:

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

Por ejemplo:

let myArray = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16]
let myItem = myArray.randomItem() // Note: myItem is an Optional<Int>
Phae Deepsky
fuente
2
En swift 2 Tha sido renombrado a Element.
GDanger
25
Tenga en cuenta que una matriz vacía provocará un bloqueo aquí
Berik,
1
@Berik Bueno, podría devolver un Elemento opcional y luego siempre hacer una guardverificación para ver si la matriz está vacía y luego regresar nil.
Harish
1
Convenido. Las matrices se bloquean en fuera de límite para que puedan ser eficaces. Llamar arc4randomhace que cualquier ganancia de rendimiento sea completamente insignificante. He actualizado la respuesta.
Berik
45

Versión 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)))]
    }

}
Andrey Gordeev
fuente
Esto puede bloquearse con un índice fuera de los límites en colecciones dondestartIndex != 0
dan
21

En Swift 2.2 esto se puede generalizar para que tengamos:

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

Primero, implementando randompropiedad estática para 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)
    }
}

Entonces, para ClosedIntervals con UnsignedIntegerTypelímites:

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

Luego (un poco más complicado), para ClosedIntervals con SignedIntegerTypelímites (usando los métodos auxiliares que se describen más adelante):

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

... donde unsignedDistanceTo, unsignedDistanceFromMiny los plusMinIntMaxmétodos auxiliares se pueden implementar de la siguiente manera:

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

Finalmente, para todas las colecciones donde 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]
    }
}

... que puede optimizarse un poco para enteros 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
    }
}
milos
fuente
18

También puede usar la función aleatoria () incorporada de Swift para la extensión:

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
fuente
En realidad, random () es del puente de la biblioteca Standard C, puedes verlo y amigos en la Terminal, "man random". ¡Pero me alegra que hayas señalado la disponibilidad!
David H
1
esto produce la misma secuencia aleatoria cada vez que se ejecuta
iTSangar
1
@iTSangar tienes razón! rand () es el correcto para usar. Actualizando mi respuesta.
NatashaTheRobot
66
Esto también es susceptible al sesgo de módulo.
Aidan Gómez
@mattt escribió un buen artículo sobre la generación de números aleatorios . TL; DR cualquiera de la familia arc4random es una mejor opción.
elitalon
9

Otra sugerencia de Swift 3

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

Siguiendo a otros responden pero con soporte 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]
    }
}

P.ej:

let arr = [2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29, 31]
let randomSample = arr.sample()
Aidan Gómez
fuente
2

Una implementación funcional alternativa con verificación de matriz vacía.

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])
Evgenii
fuente
2

Aquí hay una extensión en matrices con una verificación de matriz vacía para mayor seguridad:

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

Puede usarlo tan simple como esto :

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

Si prefiere un Framework que también tenga algunas características más útiles, entonces consulte HandySwift . Puede agregarlo a su proyecto a través de Carthage y luego usarlo exactamente como en el ejemplo anterior:

import HandySwift    

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

Además, también incluye una opción para obtener múltiples elementos aleatorios a la vez :

digits.sample(size: 3) // => [8, 0, 7]
Jeehut
fuente
2

Swift 3

importar GameKit

func getRandomMessage() -> String {

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

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

    return messages[randomNumber].description

}
Deslumbrar
fuente
2

Swift 3: simple y fácil de usar.

  1. Crear matriz

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. Crear color aleatorio

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. Establece ese color en tu objeto

    your item = arrayOfColors[Int(randomColor)]

Aquí hay un ejemplo de un SpriteKitproyecto que actualiza a SKLabelNodecon un azar String:

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

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

    let labelNode = SKLabelNode(text: array[Int(randomNumber)])
Timmy Sorensen
fuente
2

Si quieres poder obtener más de un elemento aleatorio de tu matriz sin duplicados , GameplayKit te tiene cubierto:

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

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

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

Tiene un par de opciones de aleatoriedad, consulte GKRandomSource :

La GKARC4RandomSourceclase usa un algoritmo similar al empleado en la familia arc4random de funciones C. (Sin embargo, las instancias de esta clase son independientes de las llamadas a las funciones arc4random).

los GKLinearCongruentialRandomSource clase usa un algoritmo que es más rápido, pero menos aleatorio, que la clase GKARC4RandomSource. (Específicamente, los bits bajos de números generados se repiten con más frecuencia que los bits altos). Utilice esta fuente cuando el rendimiento sea más importante que la imprevisibilidad robusta.

La GKMersenneTwisterRandomSourceclase usa un algoritmo que es más lento, pero más aleatorio, que la clase GKARC4RandomSource. Utilice esta fuente cuando sea importante que su uso de números aleatorios que no muestren patrones repetitivos y el rendimiento sean menos preocupantes.

bcattle
fuente
1

Creo que usar GKRandomSource.sharedRandom () de GameKit funciona mejor para mí.

import GameKit

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

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

o puede devolver el objeto en el índice aleatorio seleccionado. Asegúrese de que la función devuelva una Cadena primero y luego devuelva el índice de la matriz.

    return array[randomNumber]

Corto y al grano.

djames04
fuente
1

Hay un método incorporado Collectionahora:

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

Si desea extraer hasta nelementos aleatorios de una colección, puede agregar una extensión como esta:

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

Y si desea que sean únicos, puede usar a Set, pero los elementos de la colección deben cumplir con el Hashableprotocolo:

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

El último código swift3 prueba que funciona bien

 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)])
Gangireddy Rami Reddy
fuente
-2

Descubrí una forma muy diferente de hacerlo utilizando las nuevas características introducidas en 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. declaramos una función con parámetros que toman una matriz de cadenas y devuelven una cadena.

  2. Luego tomamos ArrayOfStrings en una variable.

  3. Luego llamamos a la función aleatoria y la almacenamos en una variable. (Solo admitido en 4.2)
  4. Luego declaramos una variable que guarda un valor aleatorio del recuento total de la Cadena.
  5. Por último, devolvemos la cadena aleatoria en el valor de índice de countS.

Básicamente está barajando el conjunto de cadenas y luego también tiene una selección aleatoria del número del número total de conteo y luego devuelve el índice aleatorio del conjunto aleatorio.

Nachos y Cheetos
fuente