Cerber
Cerber

Reputation: 73

Kotlin - Random numbers without repeating

I have a question, how to prevent random numbers from being repeated. By the way, can someone explain to me how to sort these random numbers?

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val textView = findViewById<TextView>(R.id.textView)
    val button = findViewById<Button>(R.id.buttom)


    button.setOnClickListener {

        var liczba = List(6){Random.nextInt(1,69)}
        textView.text = liczba.toString()
    }
}

Upvotes: 7

Views: 7598

Answers (6)

Saeed Ir
Saeed Ir

Reputation: 2332

You can use Set and MutableSet instead of List:

val mySet = mutableSetOf<Int>() 
while (mySet.size < 6) 
   mySet.add(Random.nextInt(1, 69))

Upvotes: -1

aSemy
aSemy

Reputation: 7159

Sequences are a great way to generate streams of data and limit or filter the results.

import kotlin.random.Random
import kotlin.random.nextInt

val randomInts = generateSequence {
  // this lambda is the source of the sequence's values
  Random.nextInt(1..69)
}
  // make the values distinct, so there's no repeated ints
  .distinct()
  // only fetch 6 values
  // Note: It's very important that the source lambda can provide
  //       this many distinct values! If not, the stream will
  //       hang, endlessly waiting for more unique values.
  .take(6)
  // sort the values
  .sorted()
  // and collect them into a Set
  .toSet()

run in Kotlin Playground

To make sure this works, here's a property-based-test using Kotest.

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeMonotonicallyIncreasing
import io.kotest.matchers.collections.shouldBeUnique
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.property.Arb
import io.kotest.property.arbitrary.positiveInts
import io.kotest.property.checkAll
import kotlin.random.Random
import kotlin.random.nextInt


class RandomImageLogicTest : FunSpec({

  test("expect sequence of random ints is distinct, sorted, and the correct size") {
    checkAll(Arb.positiveInts(30)) { limit ->

      val randomInts = generateSequence { Random.nextInt(1..69) }
        .distinct()
        .take(limit)
        .sorted()
        .toSet()

      randomInts.shouldBeMonotonicallyIncreasing()
      randomInts.shouldBeUnique()
      randomInts.shouldHaveSize(limit)
    }
  }

})

The test passes!

Test                                      Duration  Result
expect sequence of random ints is di...   0.163s    passed

Upvotes: 9

Brambora0
Brambora0

Reputation: 91

I create simple class, in constructor you enter "from" number (minimal possible number) and "to" (maximal posible number), class create list of numbers. "nextInt()" return random item from collection and remove it.

class RandomUnrepeated(from: Int, to: Int) {
    private val numbers = (from..to).toMutableList()
    fun nextInt(): Int {
        val index = kotlin.random.Random.nextInt(numbers.size)
        val number = numbers[index]
        numbers.removeAt(index)
        return number
    }
}

usage:

val r = RandomUnrepeated(0,100)
r.nextInt()

Upvotes: 1

cactustictacs
cactustictacs

Reputation: 19582

Similar to @IR42's answer, you can do something like this

import kotlin.random.Random

fun getUniqueRandoms() = sequence {
    val seen = mutableSetOf<Int>()
    while(true) {
        val next = Random.nextInt()
        // add returns true if it wasn't already in the set - i.e. it's not a duplicate
        if (seen.add(next)) yield(next)
    }
}

fun main() {
    getUniqueRandoms().take(6).sorted().forEach(::println)
}

So getUniqueRandoms creates an independent sequence, and holds its own internal state of which numbers it's produced. For the caller, it's just a basic sequence that produces unique values, and you can consume those however you like.

Like @rossum says, this really depends on how many you're going to produce - if you're generating a lot, or this sequence is really long-lived, that set of seen numbers will get very large over time. Plus it will start to slow down as you get more and more collisions, and have to keep trying to find one that hasn't been seen yet.

But for most situations, this kind of thing is just fine - you'd probably want to benchmark it if you're producing, say, millions of numbers, but for something like 6 it's not even worth worrying about!

Upvotes: 0

IR42
IR42

Reputation: 9722

val size = 6
val s = HashSet<Int>(size)
while (s.size < size) {
    s += Random.nextInt(1,69)
}

Upvotes: 2

rossum
rossum

Reputation: 15693

There are three basic methods to avoid repeating 'random' numbers. If they don't repeat then they are not really random of course.

  • with a small range of numbers, randomly shuffle the numbers and pick them in order after the shuffle.

  • with a medium size range of numbers, record the numbers you have picked, and reject any repeats. This will get slow if you pick a large percentage of the numbers available.

  • with a very large range of numbers you need something like an encryption: a one-to-one mapping which maps 0, 1, 2, 3 ... to the numbers in the (large) range. For example a 128 bit encryption will give an apparently random ordering of non-repeating 128-bit numbers.

Upvotes: 8

Related Questions