5

How can I return multiple random elements from a List .

This question How to choose a random element from an array in Scala? refers to using :

import scala.util.Random
val A = Array("please", "help", "me")
Random.shuffle(A.toList).head

The mutable in me is thinking I could create a for loop and keep selecting the next random element (excluding the one already selected) and add that to a new List. Is there a more idiomatic/functional way to achieve this in Scala ?

3
  • 3
    Random.shuffle(A.toList).take(n) Commented Jan 9, 2014 at 17:34
  • "take" implements this using a loop. It is probably explained by the performance reasons. Commented Jan 9, 2014 at 17:59
  • 1
    @HappyCoder: yes, Random.shuffle(A.toIndexedSeq).take(n) is better. Commented Jan 9, 2014 at 18:20

2 Answers 2

20

The head method will return the first element of the list, but take(n) will return up to n elements from the front of the list. So after you shuffle the list, just use take:

def takeRandomN[A](n: Int, as: List[A]) =
  scala.util.Random.shuffle(as).take(n)

If your list as is shorter than n then this will simply shuffle as.

It might seem like this is going to be slow for large lists that you only want a small subset from, but a random subset will be likely be uniformly sampled from the list, so you'll have to traverse the whole thing anyway. For an Array or other structure with random access, you can do better, but for List you cannot.

Sign up to request clarification or add additional context in comments.

3 Comments

Just a note that this is fine for small lists but with big ones that you want a small random subset for this can be painfully slow.
@Shawn from curiosity what would be efficient for big list/sets?
Shuffle is O(n). I'm not sure you can do better.
1

More "conservative" variant without using mutables/vars. Just for the sake of exercise:

def takeRandomFrom[T](n: Int, list: List[T]): List[T] = {
  @tailrec
  def innerTake(n:Int, list: List[T], result: List[T]): List[T] = {
    if (n == 0 || list.isEmpty) {
  result
} else {
  innerTake(n - 1, list.tail, list.head :: result)
}
  }

  innerTake(n, Random.shuffle(list), Nil)
}  

takeRandomFrom(2, Array("please", "help", "me").toList)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.