Escolha um elemento aleatório de uma matriz

189

Suponha que eu tenha uma matriz e queira escolher um elemento aleatoriamente.

Qual seria a maneira mais simples de fazer isso?

O caminho óbvio seria array[random index]. Mas talvez haja algo como o ruby array.sample? Ou, se não, esse método poderia ser criado usando uma extensão?

Fela Winkelmolen
fonte
1
Você já tentou métodos diferentes?
ford prefect
Eu tentaria array[random number from 0 to length-1], mas não consigo encontrar como gerar um int aleatório rapidamente, eu perguntaria no estouro de pilha se não estivesse bloqueado :) Eu não queria poluir a pergunta com meias soluções, quando talvez haja algo como rubyarray.sample
Fela Winkelmolen
1
Você usa arc4random () como você faria em Obj-C
Arbitur
Não há explicação para o motivo de sua pergunta não ter recebido o mesmo feedback que o equivalente do JQuery. Mas, em geral, você deve seguir estas diretrizes ao postar uma pergunta. Como fazer uma boa pergunta? . Faça com que pareça que você se esforça um pouco para descobrir uma solução antes de pedir ajuda a alguém. Quando pesquiso no Google "escolher número aleatório rápido", a primeira página é preenchida com respostas sugerindo arc4random_uniform. Além disso, o RTFD ... "lê a documentação referente". É surpreendente quantas perguntas podem ser respondidas dessa maneira.
Austin A
Obrigado pelo seu feedback. Sim, acho que eu deveria ter respondido à pergunta pessoalmente, mas parecia fácil o bastante para dar a alguém os pontos de reputação quase gratuitos. E eu escrevi quando nem os documentos oficiais da Apple eram públicos, definitivamente não havia resultados do Google naquele momento. Mas a pergunta era uma vez a -12, por isso estou bastante confiante de que será positivo, eventualmente :)
Fela Winkelmolen

Respostas:

320

Swift 4.2 e superior

A nova abordagem recomendada é um built-in método no protocolo Coleção: randomElement(). Ele retorna um opcional para evitar o caso vazio que assumi anteriormente.

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

Se você não criar a matriz e não tiver uma contagem garantida> 0, faça algo como:

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

Swift 4.1 e abaixo

Apenas para responder sua pergunta, você pode fazer isso para obter a seleção aleatória de arrays:

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

As peças são feias, mas acredito que são necessárias, a menos que alguém tenha outra maneira.

Lucas Derraugh
fonte
4
Por que o Swift não oferece um gerador de números aleatórios que retorna um Int? Essa segunda linha parece muito detalhada apenas para retornar Int escolhido aleatoriamente. Existe alguma vantagem computacional / sintática para retornar um UInt32 em oposição a um Int? Além disso, por que o Swift não oferece uma alternativa Int para essa função ou permite que um usuário especifique que tipo de número inteiro ele gostaria que retornasse?
Austin A
Para adicionar uma observação, esse método gerador de números aleatórios pode evitar "viés de módulo". Consulte man arc4randome stackoverflow.com/questions/10984974/…
Kent Liau
1
@ Austin, Swift 4.2 possui uma função de gerador de números aleatórios nativa que é implementada em todos os tipos de dados escalares que você pode esperar: Int, Double, Float, UInt32, etc. E permite fornecer intervalos de destino para os valores. Muito conveniente. Você pode usar a matriz [Int.random (0 .. <array.count)] `no Swift 4.2
Duncan C
Desejo que o Swift 4.2 tenha implementado uma removeRandomElement()função além de randomElement(). Seria modelado removeFirst(), mas remova um objeto em um índice aleatório.
Duncan C
@DuncanC Você deve evitar 0..<array.count(por vários motivos, os principais são que ele não funciona para fatias e é propenso a erros). Você pode fazer let randomIndex = array.indices.randomElement(), seguido por let randomElement = array.remove(at: randomIndex). Você pode até incorporar let randomElement = array.remove(at: array.indices.randomElement()).
Alexander - Reinstate Monica
137

Analisando o que Lucas disse, você pode criar uma extensão para a classe Array assim:

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

Por exemplo:

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
fonte
2
No swift 2 Tfoi renomeado para Element.
GDanger #
25
Observe que um array vazio irá causar um acidente aqui
Berik
1
@Berik Bem, você pode retornar um elemento opcional e sempre fazer uma guardverificação para ver se a matriz está vazia e depois retornar nil.
Harish 28/07
1
Acordado. As matrizes falham fora dos limites para que possam ter desempenho. Chamar para arc4randomqualquer ganho de desempenho é completamente insignificante. Eu atualizei a resposta.
Berik
45

Versão 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
fonte
Isso pode falhar com um índice fora dos limites das coleções em questartIndex != 0
dan
21

No Swift 2.2, isso pode ser generalizado para que tenhamos:

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

Primeiro, implementando randompropriedade 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)
    }
}

Então, para ClosedIntervals com UnsignedIntegerTypelimites:

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

Então (um pouco mais envolvido), para ClosedIntervals com SignedIntegerTypelimites (usando métodos auxiliares descritos mais abaixo):

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

... onde unsignedDistanceTo, unsignedDistanceFromMine plusMinIntMaxauxiliares métodos podem ser implementados como segue:

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 as coleções em que 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 pode ser otimizado um pouco para o número inteiro 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
    }
}
milos
fonte
18

Você pode usar a função aleatória () interna do Swift também para a extensão:

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
fonte
Na verdade random () é da ponte da biblioteca Standard C, você pode vê-lo e seus amigos no Terminal, "man random". Mas feliz que você apontou a disponibilidade!
David H
1
este produz a mesma sequência aleatória cada tempo de execução
iTSangar
1
@iTSangar você está certo! rand () é o correto para usar. Atualizando minha resposta.
NatshaTheRobot 16/04
6
Isso também é suscetível ao viés do módulo.
9139 Aidan Gomez
A @mattt escreveu um bom artigo sobre como gerar números aleatórios . TL; DR da família arc4random é uma escolha melhor.
Elitalon 29/11/16
9

Outra sugestão do Swift 3

private extension Array {
    var randomElement: Element {
        let index = Int(arc4random_uniform(UInt32(count)))
        return self[index]
    }
}
que bate com violência
fonte
4

Os outros respondem, mas com o suporte do 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]
    }
}

Por exemplo:

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

Uma implementação funcional alternativa com verificação de matriz vazia.

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
fonte
2

Aqui está uma extensão em Matrizes com uma verificação de matriz vazia para mais segurança:

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

Você pode usá-lo tão simples quanto isto :

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

Se você preferir um Framework que também tenha alguns recursos mais úteis, consulte o HandySwift . Você pode adicioná-lo ao seu projeto via Carthage e usá-lo exatamente como no exemplo acima:

import HandySwift    

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

Além disso, também inclui uma opção para obter vários elementos aleatórios ao mesmo tempo :

digits.sample(size: 3) // => [8, 0, 7]
Jeehut
fonte
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
fonte
2

Swift 3 - simples e fácil de usar.

  1. Criar matriz

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. Criar cor aleatória

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. Defina essa cor para o seu objeto

    your item = arrayOfColors[Int(randomColor)]

Aqui está um exemplo de um SpriteKitprojeto que atualiza um SKLabelNodecom um aleatório String:

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

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

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

Se você deseja obter mais de um elemento aleatório da sua matriz sem duplicatas , o GameplayKit já cobriu:

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

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

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

Você tem algumas opções de aleatoriedade, consulte GKRandomSource :

o GKARC4RandomSource classe usa um algoritmo semelhante ao empregado na família arc4random de funções C. (No entanto, as instâncias desta classe são independentes das chamadas para as funções arc4random.)

A GKLinearCongruentialRandomSourceclasse usa um algoritmo que é mais rápido, mas menos aleatório, que a classe GKARC4RandomSource. (Especificamente, os bits baixos dos números gerados se repetem com mais freqüência do que os bits altos.) Use essa fonte quando o desempenho for mais importante do que a imprevisibilidade robusta.

A GKMersenneTwisterRandomSourceclasse usa um algoritmo mais lento, mas mais aleatório, que a classe GKARC4RandomSource. Use essa fonte quando for importante que o uso de números aleatórios não mostre padrões repetidos e o desempenho seja menos preocupante.

bcattle
fonte
1

Acho que usar o GKRandomSource.sharedRandom () do GameKit funciona melhor para mim.

import GameKit

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

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

ou você pode retornar o objeto no índice aleatório selecionado. Verifique se a função retorna uma String primeiro e, em seguida, retorne o índice da matriz.

    return array[randomNumber]

Curto e direto ao ponto.

djames04
fonte
1

Há um método interno Collectionagora:

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

Se você deseja extrair até nelementos aleatórios de uma coleção, você pode adicionar uma extensão como esta:

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

E se você quiser que eles sejam exclusivos, você pode usar a Set, mas os elementos da coleção devem estar em conformidade com o Hashableprotocolo:

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

O código swift3 mais recente experimenta seu bom funcionamento

 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
fonte
-2

Eu descobri uma maneira muito diferente de fazer isso usando os novos recursos introduzidos no 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 uma função com parâmetros pegando uma matriz de Strings e retornando uma String.

  2. Então pegamos o ArrayOfStrings em uma variável.

  3. Em seguida, chamamos a função aleatória e a armazenamos em uma variável. (Suportado apenas no 4.2)
  4. Em seguida, declaramos uma variável que salva um valor aleatório da contagem total da String.
  5. Por fim, retornamos a sequência aleatória no valor de índice de countS.

Basicamente, embaralha a matriz de seqüências de caracteres e, em seguida, também seleciona aleatoriamente o número total de contagens e, em seguida, retorna o índice aleatório da matriz embaralhada.

Nachos e Cheetos
fonte