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

#ios #arrays #swift #xcode

#iOS #массивы #swift #xcode — код

Вопрос:

Я хотел бы инициализировать массив следующим образом:

 func initRoundsArray(playersArray: [String]) -> [String] {

    let rounds: [String] = [
        "ROUND 1: First player: (String(playersArray.randomElement()!)), Second player: (String(playersArray.randomElement()!))",
        "ROUND 2: First player: (String(playersArray.randomElement()!)), Second player: (String(playersArray.randomElement()!))",
        "ROUND 3: First player: (String(playersArray.randomElement()!)), Second player: (String(playersArray.randomElement()!))"
    ]

    return rounds
}
 

С помощью следующего кода в моем контроллере представления:

 let playersArrayInput: [String] = ["Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8", "Player 9"]
var arrayOfRounds: [String]?

// Called like so in viewDidLoad:
arrayOfRounds = initRoundsArray(playersArray: playersArrayInput)
 

Однако я изо всех сил пытаюсь понять, как выбрать 2 случайных и уникальных элемента для каждого раунда. Например, arrayOfRounds[0] в настоящее время может быть "ROUND 1: First player: Player 6, Second player: Player 6" .

Поскольку initRoundsArray вызывается только один раз ( arrayOfRounds позже мутируется), я не думаю, что уместно просто перетасовывать массив и выбирать первые 2 элемента, так как в этом случае в каждом раунде будут участвовать одни и те же 2 игрока.

Я не уверен, как этого добиться (или если это вообще возможно). В идеале все, что необходимо, — это, например, при выборе двух игроков для раунда 1 проверяется, отличаются ли они друг от друга.

Комментарии:

1. перетасуйте массив, а затем выберите элементы

2. @dahiya_boy Я обсуждал этот подход в самом вопросе, предпоследний абзац.

3. вам нужно перетасовывать один раз не для каждого раунда

4. @dahiya_boy Каждый раз может быть разное количество игроков, массив, приведенный в вопросе, является лишь примером. Если бы я должен был перемешать, как вы предлагаете, как я мог бы продолжать предотвращать дублирование?

5. @chumps52 Ваш вопрос немного сбивает с толку. Таким образом, в одном раунде должны участвовать 2 разных игрока. Но тогда вы хотите, чтобы уникальные пары прошли как можно больше раундов. И игроки могут меняться во время исполнения. Это то, что вы пытаетесь сделать? Например, запускается сервер, и люди начинают входить и выходить из системы, поэтому вы хотите продолжать создавать пары игроков для каждого раунда, какие бы вы хотели, чтобы пары не дублировались?

Ответ №1:

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

 func pick<T>(_ n: Int, from array: [T]) -> [T] {
    var copy = array // make a copy so we can make changes
    var result = [T]()
    for _ in 0..<n {
        let randomElementIndex = Int.random(in: 0..<copy.count) // generate random index
        let randomElement = copy[randomElementIndex]
        copy.remove(at: randomElementIndex) // remove the generated element
        result.append(randomElement) // add it to the result
    }
    return result
}
 

Чтобы сгенерировать игроков на 3 раунда, вызовите это с n=6 помощью .

Комментарии:

1. Это Int.random(in: 0..<n) выглядит неправильно, поскольку вы истощаете элементы из copy . Предположим, что у него было 2 элемента и n равно 2. Первый элемент правильный, но второй имеет 50%-ную вероятность отката индекса 1 , но единственным допустимым индексом остается 0, поскольку copy теперь в нем осталось только 1 элемент.

2. @Kamran ой! Исправлено.

Ответ №2:

Я бы выбрал что-то вроде следующего:

 func extractRandomElementsFromArray<Generic>(_ array: [Generic], numberOfElements: Int) -> [Generic]? {
    guard array.count >= numberOfElements else { return nil }

    var toDeplete = array
    var toReturn = [Generic]()

    while toReturn.count < numberOfElements {
        toReturn.append(toDeplete.remove(at: Int.random(in: 0..<toDeplete.count)))
    }

    return toReturn
}
 

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

В вашем случае это можно использовать как:

 let playersArrayInput: [String] = ["Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8", "Player 9"]

let pairArray = extractRandomElementsFromArray(playersArrayInput, numberOfElements: 2)!
let player1 = pairArray[0]
let player2 = pairArray[1]
 

Ответ №3:

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

Пример.

 func initRoundsArray(playersArray: [String]) -> [String] {

    var playersArrayCoppy = playersArray
    let round1Item = playersArrayCoppy.remove(at: Int.random(in: 0...(playersArrayCoppy.count - 1)))
    let round2Item = playersArrayCoppy.remove(at: Int.random(in: 0...(playersArrayCoppy.count - 1)))
    let round3Item = playersArrayCoppy.remove(at: Int.random(in: 0...(playersArrayCoppy.count - 1)))

    let rounds: [String] = [
        "ROUND 1: First player: (round1Item), Second player: (round1Item)",
        "ROUND 2: First player: (round2Item), Second player: (round2Item)",
        "ROUND 3: First player: (round3Item), Second player: (round3Item)"
    ]
    return rounds
}
 

Конечно, вам нужно добавить некоторые проверки наличия элементов в массиве, если вы не уверены, что количество массивов равно> = 3

РЕДАКТИРОВАТЬ на основе комментариев, которые вы, вероятно, хотите, это эта функция

 func initRoundsArray(roundsNumber: Int, playersArray: [String]) -> [String] {
    var roundsArray:[String] = []
    for i in 1...roundsNumber {
        var playersArrayCoppy = playersArray
        let player1Item = playersArrayCoppy.remove(at: Int.random(in: 0...(playersArrayCoppy.count - 1)))
        let player2Item = playersArrayCoppy.remove(at: Int.random(in: 0...(playersArrayCoppy.count - 1)))

        let round: String = "ROUND (i): First player: (player1Item), Second player: (player2Item)"
        roundsArray.append(round)
    }
    return roundsArray
}
 

и вы вызываете его с initRoundsArray(roundsNumber: 3, playersArray: ["?","?",..."])

Комментарии:

1. Я только что запустил этот код, и он гарантирует дублирование: ["ROUND 1: First player: Player 9, Second player: Player 9", "ROUND 2: First player: Player 8, Second player: Player 8", "ROUND 3: First player: Player 7, Second player: Player 7"]

2. нет, это не так, я просто добавил round1Item к первому игроку и второму игроку! Butt round1Item всегда отличается от round2Item и round3Item Я не понял, что вы хотите другого для каждого игрока, я думал, что вы хотите для каждого раунда. Если вы хотите, чтобы каждый игрок отличался, вам нужно вызывать функцию для каждого раунда и использовать только 2 предмета

Ответ №4:

В итоге я использовал следующий код ( <ACTION> это просто заполнитель):

 func initRoundsArray(playersArray: [String]) -> [String] {

    let round1Players = twoRandomPlayers(playersArray: playersArray)
    let round2Players = twoRandomPlayers(playersArray: playersArray)

    let rounds: [String] = [
        "ROUND 1: (round1players[0]), do <ACTION> to (round1players[1])",
        "ROUND 2: (round2players[0]), do <ACTION> to (round2players[1])",
        "ROUND 3: (String(playersArray.randomElement()!)), do <ACTION>"
    ]

    return rounds
}

func twoRandomPlayers(playersArray: [String]) -> [String] {
    let shuffledPlayersArray = playersArray.shuffled()

    return Array(shuffledPlayersArray.prefix(2))
}
 

Я буду каждый раз вызывать отдельную функцию, которая возвращает массив с 2 случайно выбранными (и уникальными) игроками из playersArray , и использовать это в раундах, в которых требуется 2 игрока.

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

Я прошу прощения, если формулировка в исходном вопросе была запутанной, но я надеюсь, что это прояснит ситуацию, и большое спасибо всем за их предложения / ответы 🙂

Комментарии:

1. Почему ROUND 3 в нем есть только один игрок?

2. @ielyamani Я кратко упомянул об этом в ответе, но в реальной реализации в некоторых раундах будет задействован только один игрок, который должен что-то сделать. В исходном вопросе об этом не упоминалось, поскольку я уже знал, как это реализовать — у меня был вопрос о получении 2 не дубликатов в одной строке / раунде.

Ответ №5:

Прежде всего, давайте предположим, что playersArrayInput у него нет дубликатов.

 let playersArrayInput: [String] = ["Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8", "Player 9"]
 

Эффективная реализация состоит в перетасовке только индексов :

 func initRoundsArray(playersArray: [String]) -> [String] {

    var shuffledIndices = playersArray.indices.shuffled()
    let count = playersArray.count - 1
    var rounds = [String]()
    var i = 0

    while i <= count {
        let round = String(rounds.count   1)
        if i <= count - 1 {
            switch Bool.random() {
            case true:
                rounds.append("ROUND "  
                    round  
                    ": "  
                    playersArray[shuffledIndices[i]]  
                    ", do <ACTION>")
                i  = 1
            default:
                rounds.append("ROUND "  
                    round  
                    ": "  
                    playersArray[shuffledIndices[i]]  
                    ", do <ACTION> to "  
                    playersArray[shuffledIndices[i   1]])
                i  = 2
            }
        } else {
            rounds.append("ROUND "  
                round  
                ": "  
                playersArray[shuffledIndices[count]]  
                ", do <ACTION>")
            break
        }
    }

    return rounds
}
 

И мы могли бы распечатать результат вызова таким образом :

 initRoundsArray(playersArray: playersArrayInput).forEach { print($0) }
 

например, получение :

 ROUND 1: Player 7, do <ACTION> to Player 1
ROUND 2: Player 8, do <ACTION>
ROUND 3: Player 5, do <ACTION>
ROUND 4: Player 9, do <ACTION> to Player 2
ROUND 5: Player 3, do <ACTION> to Player 6
ROUND 6: Player 4, do <ACTION>