24

For any given set, for instance,

val fruits = Set("apple", "grape", "pear", "banana")

how to get a random element from fruits ?

Many Thanks.

Govind Singh
  • 15,282
  • 14
  • 72
  • 106
elm
  • 20,117
  • 14
  • 67
  • 113

8 Answers8

22

So, every answer posted before has complexity O(n) in terms of space, since they create a copy a whole collection in some way. Here is a solution without any additional copying (therefore it is "constant space"):

def random[T](s: Set[T]): T = {
  val n = util.Random.nextInt(s.size)
  s.iterator.drop(n).next
}
Rok Kralj
  • 46,826
  • 10
  • 71
  • 80
  • 1
    This is a better answer for the reason you put forward. It could be done in a one liner like this: val s = Set(1,2,3); s.drop(random.nextInt(s.size).head – justinhj Feb 12 '16 at 19:59
  • 9
    @justinhj: Nope, your solution is O(n) space again, since `drop` on `Set` causes a set with the remaining elements to be created. You *have* to use iterators, because they are non-strict. – Rok Kralj Feb 12 '16 at 21:52
  • Good solution. Its worth pointing out that if the set is empty `it.next` will throw. – Antony Perkov Nov 09 '16 at 05:22
  • 1
    @AntonyPerkov which makes sense because ... what does it mean to sample from an empty set? – Jus12 Feb 08 '17 at 12:10
  • 1
    This is still O(n) time, though. – Brian McCutchon Apr 28 '17 at 15:34
  • Yes, I also look for a O(1) time and O(1) space version – pihentagy Aug 29 '17 at 16:05
  • You will have to create your own Set implementation for that. and the time will probably be O(log n) if you want to have other operations efficient. – Rok Kralj Aug 29 '17 at 19:09
21

convert into Vector and get random element from it

scala> val fruits = Set("apple", "grape", "pear", "banana")
fruits: scala.collection.immutable.Set[String] = Set(apple, grape, pear, banana)

scala> import scala.util.Random
import scala.util.Random

scala> val rnd=new Random
rnd: scala.util.Random = scala.util.Random@31a9253

scala> fruits.toVector(rnd.nextInt(fruits.size))
res8: String = apple
Govind Singh
  • 15,282
  • 14
  • 72
  • 106
3

Drawing inspiration from the other answers to this question, I've come up with:

private def randomItem[T](items: Traversable[T]): Option[T] = {
  val i = Random.nextInt(items.size)
  items.view(i, i + 1).headOption
}

This doesn't copy anything, doesn't fail if the Set (or other type of Traversable) is empty, and it's clear at a glance what it does. If you're certain that the Set is not empty, you could use .head instead of headOption, returning a T.

László van den Hoek
  • 3,955
  • 1
  • 23
  • 28
2

Solution1

Random way ( import scala.util.Random )

scala>  fruits.toList(Random.nextInt(fruits.size))
res0: java.lang.String = banana

Solution2

Math way (no imports)

scala> fruits.toList((math.random*fruits.size).toInt)
res1: String = banana
matanster
  • 15,072
  • 19
  • 88
  • 167
AmeyChavan
  • 362
  • 1
  • 2
  • 14
  • Chained List is not a good option to pick one element at random. Chose array or vector instead – Boris Sep 21 '17 at 08:03
2

You can directly access an element of a Set with slice. I used this when I was working with a set that was changing in size, so converting it to a Vector every time seemed like overkill.

val roll = new Random ()

val n = roll nextInt (fruits size)
fruits slice (n, n + 1) last
Wayne
  • 933
  • 7
  • 11
1
   import Scala.util.Random

   val fruits = Set("apple", "grape", "pear", "banana").toVector

   val sz =fruits.size

   val num = Random.nextInt(sz)

   fruits(num)
Govind Singh
  • 15,282
  • 14
  • 72
  • 106
Ashalynd
  • 12,363
  • 2
  • 34
  • 37
0

Not converting the Set to an ordered collection but using zipWithIndex we can attribute an index to each item in the collection,

fruits.zipWithIndex
Set((apple,0), (grape,1), (pear,2), (banana,3))

Thus for val rnd = util.Random.nextInt(fruits.size),

fruits.zipWithIndex.find( _._2 == rnd)
Option[(String, Int)] = Some((banana,3))

Given an empty set,

Set[String]().zipWithIndex.find( _._2 == 3)
Option[(String, Int)] = None
elm
  • 20,117
  • 14
  • 67
  • 113
0

If you don't mind an O(n) solution:

import util.Random

// val fruits = Set("apple", "grape", "pear", "banana")
Random.shuffle(fruits).head
// "pear"
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190