Take n random elements from a List?

后端 未结 12 1452
天涯浪人
天涯浪人 2020-11-27 16:09

How can I take n random elements from an ArrayList? Ideally, I\'d like to be able to make successive calls to the take() method to get an

相关标签:
12条回答
  • 2020-11-27 16:17

    If you want to successively pick n elements from the list and be able to do so without replacement over and over and over again, you are probably best of randomly permuting the elements, then taking chunks off in blocks of n. If you randomly permute the list you guarantee statistical randomness for each block you pick out. Perhaps the easiest way to do this would be to use Collections.shuffle.

    0 讨论(0)
  • 2020-11-27 16:17

    All of these answers require a modifiable list or run into performance issued

    Here's a swift snippet that required O(k) additional space and is guaranteed to run in O(k) time and doesn't need a modifiable array. (Performs shuffles in a map)

      func getRandomElementsFrom(array: [Int], count: Int = 8) -> [Int] {
        if array.count <= count {
            return array
        }
    
        var mapper = [Int: Int]()
        var results = [Int]()
    
        for i in 0..<count {
            let randomIndex = Int.random(in: 0..<array.count - i)
    
            if let existing = mapper[randomIndex] {
                results.append(array[existing])
            } else {
                let element = array[randomIndex]
                results.append(element)
            }
    
            let targetIndex = array.count - 1 - i
            mapper[randomIndex] = mapper[targetIndex] ?? targetIndex 
        }
    
        return results
    }
    
    0 讨论(0)
  • 2020-11-27 16:18

    Keep selecting a random element and make sure you do not choose the same element again:

    public static <E> List<E> selectRandomElements(List<E> list, int amount)
    {
        // Avoid a deadlock
        if (amount >= list.size())
        {
            return list;
        }
    
        List<E> selected = new ArrayList<>();
        Random random = new Random();
        int listSize = list.size();
    
        // Get a random item until we got the requested amount
        while (selected.size() < amount)
        {
            int randomIndex = random.nextInt(listSize);
            E element = list.get(randomIndex);
    
            if (!selected.contains(element))
            {
                selected.add(element);
            }
        }
    
        return selected;
    }
    

    In theory this could run endlessly but in practise it is fine. The closer you get the whole original list the slower the runtime of this gets obviously but that is not the point of selecting a random sublist, is it?

    0 讨论(0)
  • 2020-11-27 16:21

    The following class retrieve N items from a list of any type. If you provide a seed then on each run it will return the same list, otherwise, the items of the new list will change on each run. You can check its behaviour my running the main methods.

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    import java.util.Random;
    
    public class NRandomItem<T> {
        private final List<T> initialList;
    
        public NRandomItem(List<T> list) {
            this.initialList = list;
        }
    
        /**
         * Do not provide seed, if you want different items on each run.
         * 
         * @param numberOfItem
         * @return
         */
        public List<T> retrieve(int numberOfItem) {
            int seed = new Random().nextInt();
            return retrieve(seed, numberOfItem);
        }
    
        /**
         * The same seed will always return the same random list.
         * 
         * @param seed,
         *            the seed of random item generator.
         * @param numberOfItem,
         *            the number of items to be retrieved from the list
         * @return the list of random items
         */
        public List<T> retrieve(int seed, int numberOfItem) {
            Random rand = new Random(seed);
    
            Collections.shuffle(initialList, rand);
            // Create new list with the number of item size
            List<T> newList = new ArrayList<>();
            for (int i = 0; i < numberOfItem; i++) {
                newList.add(initialList.get(i));
            }
            return newList;
        }
    
        public static void main(String[] args) {
            List<String> l1 = Arrays.asList("Foo", "Bar", "Baz", "Qux");
            int seedValue = 10;
            NRandomItem<String> r1 = new NRandomItem<>(l1);
    
            System.out.println(String.format("%s", r1.retrieve(seedValue, 2)));
        }
    }
    
    0 讨论(0)
  • 2020-11-27 16:23

    As noted in other answers, Collections.shuffle is not very efficient when the source list is big, because of the copying. Here is a Java 8 one-liner that:

    • efficient enough on random access lists like ArrayList if you don't need many elements from the source
    • doesn't modify the source
    • DOES NOT guarantee uniqueness, if it's not super important for you. If you pick 5 from a hundred, there's a very good chance the elements will be unique.

    Code:

    private static <E> List<E> pickRandom(List<E> list, int n) {
      return new Random().ints(n, 0, list.size()).mapToObj(list::get).collect(Collectors.toList());
    }
    

    However, for a list with no quick random access (like LinkedList) the complexity would be n*O(list_size).

    0 讨论(0)
  • 2020-11-27 16:28

    Most of the proposed solutions till now suggest either a full list shuffle or successive random picking by checking uniqueness and retry if required.

    But, we can take advantage of the Durstenfeld's algorithm (the most popular Fisher-Yates variant in our days).

    Durstenfeld's solution is to move the "struck" numbers to the end of the list by swapping them with the last unstruck number at each iteration.

    Due to the above, we don't need to shuffle the whole list, but run the loop for as many steps as the number of elements required to return. The algorithm ensures that the last N elements at the end of the list are 100% random if we used a perfect random function.

    Among the many real-world scenarios where we need to pick a predetermined (max) amount of random elements from arrays/lists, this optimized method is very useful for various card games, such as Texas Poker, where you a-priori know the number of cards to be used per game; only a limited number of cards is usually required from the deck.

    public static <E> List<E> pickNRandomElements(List<E> list, int n, Random r) {
        int length = list.size();
    
        if (length < n) return null;
    
        //We don't need to shuffle the whole list
        for (int i = length - 1; i >= length - n; --i)
        {
            Collections.swap(list, i , r.nextInt(i + 1));
        }
        return list.subList(length - n, length);
    }
    
    public static <E> List<E> pickNRandomElements(List<E> list, int n) {
        return pickNRandomElements(list, n, ThreadLocalRandom.current());
    }
    
    0 讨论(0)
提交回复
热议问题