Reputation: 4387
Lets say I have a list of words and i want to create a method which takes the size of the new list as a parameter and returns the new list. How can i get random words from my original sourceList?
public List<String> createList(int listSize) {
Random rand = new Random();
List<String> wordList = sourceWords.
stream().
limit(listSize).
collect(Collectors.toList());
return wordList;
}
So how and where can I use my Random?
Upvotes: 27
Views: 36445
Reputation: 4387
I've found a proper solution. Random provides a few methods to return a stream. For example ints(size) which creates a stream of random integers.
public List<String> createList(int listSize)
{
Random rand = new Random();
List<String> wordList =
rand
.ints(listSize, 0, sourceWords.size())
.mapToObj(i -> sourceWords.get(i))
.collect(Collectors.toList());
return wordList;
}
Upvotes: 29
Reputation: 489
I realise this is a very old question, but it came up as one of the first results I found when I was trying to do a similar thing.
I wasn't keen on any of the solutions I found, and in the end I went with this kind of approach:
public List<String> createList(int listSize) {
return sourceWords.stream()
.map(word -> new SimpleEntry<>(word, rand.nextDouble()))
.sorted(comparingDouble(SimpleEntry::getValue))
.limit(listSize)
.map(SimpleEntry::getKey)
.toList();
}
This first maps each entry of the stream to pair it with a random number, it then sorts by that random number, then limits the list, then retrieves the original word from the pair.
I use AbstractMap.SimpleEntry
as a convenient class for holding a pair, available in the standard Java library, but you may have a nicer Pair
or Tuple2
class available, depending which libraries you use.
Upvotes: 0
Reputation: 55
One liner to randomize a stream:
Stream.of(1, 2, 3, 4, 5).sorted(Comparator.comparingDouble(x -> Math.random()))
Upvotes: 0
Reputation: 1
If the source list is generally much larger than the new list, you might gain some efficiencies by using a BitSet
to get random indices:
List<String> createList3(int listSize, List<String> sourceList) {
if (listSize > sourceList.size()) {
throw new IllegalArgumentException("Not enough words in the source list.");
}
List<String> newWords = randomWords(listSize, sourceList);
Collections.shuffle(newWords); // optional, for random order
return newWords;
}
private List<String> randomWords(int listSize, List<String> sourceList) {
int endExclusive = sourceList.size();
BitSet indices = new BitSet(endExclusive);
Random rand = new Random();
while (indices.cardinality() < listSize) {
indices.set(rand.nextInt(endExclusive));
}
return indices.stream().mapToObj(i -> sourceList.get(i))
.collect(Collectors.toList());
}
Upvotes: 0
Reputation: 1
A stream is probably overkill. Copy the source list so you're not creating side-effects, then give back a sublist of the shuffled copy.
public static List<String> createList(int listSize, List<String> sourceList) {
if (listSize > sourceList.size()) {
throw IllegalArgumentException("Not enough words for new list.");
}
List<String> copy = new ArrayList<>(sourceList);
Collections.shuffle(copy);
return copy.subList(0, listSize);
}
Upvotes: 0
Reputation: 1169
@kozla13 improved version:
List<String> st = Arrays.asList("aaaa","bbbb","cccc");
st.stream().min((o1, o2) -> o1 == o2 ? 0 : (ThreadLocalRandom.current().nextBoolean() ? -1 : 1)).orElseThrow();
Upvotes: 0
Reputation: 93
If you want non repeated items in the result list and your initial list is immutable:
You can try something like:
public List<String> getStringList(final List<String> strings, final int size) {
if (size < 1 || size > strings.size()) {
throw new IllegalArgumentException("Out of range size.");
}
final List<String> stringList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
getRandomString(strings, stringList)
.ifPresent(stringList::add);
}
return stringList;
}
private Optional<String> getRandomString(final List<String> stringList, final List<String> excludeStringList) {
final List<String> filteredStringList = stringList.stream()
.filter(c -> !excludeStringList.contains(c))
.collect(toList());
if (filteredStringList.isEmpty()) {
return Optional.empty();
}
final int randomIndex = new Random().nextInt(filteredStringList.size());
return Optional.of(filteredStringList.get(randomIndex));
}
Upvotes: 0
Reputation: 12688
Here's a solution I came up with which seems to differ from all the other ones, so I figured why not add it to the pile.
Basically it works by using the same kind of trick as one iteration of Collections.shuffle
each time you ask for the next element - pick a random element, swap that element with the first one in the list, move the pointer forwards. Could also do it with the pointer counting back from the end.
The caveat is that it does mutate the list you passed in, but I guess you could just take a copy as the first thing if you didn't like that. We were more interested in reducing redundant copies.
private static <T> Stream<T> randomStream(List<T> list)
{
int characteristics = Spliterator.SIZED;
// If you know your list is also unique / immutable / non-null
//int characteristics = Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.SIZED;
Spliterator<T> spliterator = new Spliterators.AbstractSpliterator<T>(list.size(), characteristics)
{
private final Random random = new SecureRandom();
private final int size = list.size();
private int frontPointer = 0;
@Override
public boolean tryAdvance(Consumer<? super T> action)
{
if (frontPointer == size)
{
return false;
}
// Same logic as one iteration of Collections.shuffle, so people talking about it not being
// fair randomness can take that up with the JDK project.
int nextIndex = random.nextInt(size - frontPointer) + frontPointer;
T nextItem = list.get(nextIndex);
// Technically the value we end up putting into frontPointer
// is never used again, but using swap anyway, for clarity.
Collections.swap(list, nextIndex, frontPointer);
frontPointer++;
// All items from frontPointer onwards have not yet been chosen.
action.accept(nextItem);
return true;
}
};
return StreamSupport.stream(spliterator, false);
}
Upvotes: 3
Reputation: 1942
This is my one line solution:
List<String> st = Arrays.asList("aaaa","bbbb","cccc");
st.stream().sorted((o1, o2) -> RandomUtils.nextInt(0, 2)-1).findFirst().get();
RandomUtils are from commons lang 3
Upvotes: 4
Reputation: 253
I think the most elegant way is to have a special collector.
I am pretty sure the only way you can guarantee that each item has an equal chance of being picked, is to collect, shuffle and re-stream. This can be easily done using built-in Collectors.collectingAndThen(...) helper.
Sorting by a random comparator or using randomized reducer, like suggested on some other answers, will result in very biased randomness.
List<String> wordList = sourceWords.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), collected -> {
Collections.shuffle(collected);
return collected.stream();
}))
.limit(listSize)
.collect(Collectors.toList());
You can move that shuffling collector to a helper function:
public class CollectorUtils {
public static <T> Collector<T, ?, Stream<T>> toShuffledStream() {
return Collectors.collectingAndThen(Collectors.toList(), collected -> {
Collections.shuffle(collected);
return collected.stream();
});
}
}
I assume that you are looking for a way to nicely integrate with other stream processing functions. So following straightforward solution is not what you are looking for :)
Collections.shuffle(wordList)
return wordList.subList(0, limitSize)
Upvotes: 16
Reputation: 829
The answer is very simple(with stream):
List<String> a = src.stream().sorted((o1, o2) -> {
if (o1.equals(o2)) return 0;
return (r.nextBoolean()) ? 1 : -1;
}).limit(10).collect(Collectors.toList());
You can test it:
List<String> src = new ArrayList<String>();
for (int i = 0; i < 20; i++) {
src.add(String.valueOf(i*10));
}
Random r = new Random();
List<String> a = src.stream().sorted((o1, o2) -> {
if (o1.equals(o2)) return 0;
return (r.nextBoolean()) ? 1 : -1;
}).limit(10).collect(Collectors.toList());
System.out.println(a);
Upvotes: -1
Reputation: 2955
Try something like that:
List<String> getSomeRandom(int size, List<String> sourceList) {
List<String> copy = new ArrayList<String>(sourceList);
Collections.shuffle(copy);
List<String> result = new ArrayList<String>();
for (int i = 0; i < size; i++) {
result.add(copy.get(i));
}
return result;
}
Upvotes: -1