Randomly iterate over ArrayList in Java

后端 未结 5 1367
滥情空心
滥情空心 2020-12-30 14:36

Seems a very basic question. I\'ve an ArrayList al and I would like to iterate over it. Normally,

for(int i : al) {
    // some c         


        
相关标签:
5条回答
  • 2020-12-30 14:49
    public class RandomIterator<T> implements Iterator<T> {
    
      private static final SecureRandom RANDOM = new SecureRandom();
    
      private final int[] order;
    
      private final List<T> elements;
    
      public RandomIterator(List<T> elements) {
         this.elements = elements;
         this.order = generateRandomOrder(elements.size());
      }
    
      private int[] generateRandomOrder(int size) {
         int[] index = new int[size];
         for (int i = 0; i < size; i++) {
            index[i] = i;
         }
    
         int swap;
         for (int i = 0; i < size; i++) {
            int randomIndex = getRandomInt(0, size);
            swap = index[i];
            index[i] = index[randomIndex];
            index[randomIndex] = swap;
         }
    
         return index;
      }
    
      private int currentIndex = 0;
    
      private int getRandomInt(int lowerBound, int upperBound) {
         return RANDOM.nextInt(upperBound - lowerBound) + lowerBound;
      }
    
      @Override
      public boolean hasNext() {
         return currentIndex != elements.size();
      }
    
      @Override
      public T next() {
         return elements.get(order[currentIndex++]);
      }
    }
    

    This only works if you guarantee that the elements in the list are placed in positions exactly between indexes [0,size).

    You can use the normal Random if you are concerned about the performance penalty of SecureRandom (I prefer secure, yeah 8 times slower than normal random but secure!).

    0 讨论(0)
  • 2020-12-30 14:55

    Use the following class:

    import java.util.Enumeration;
    import java.util.Random;
    
    public class RandomPermuteIterator implements Enumeration<Long> {
        int c = 1013904223, a = 1664525;
        long seed, N, m, next;
        boolean hasNext = true;
    
        public RandomPermuteIterator(long N) throws Exception {
            if (N <= 0 || N > Math.pow(2, 62)) throw new Exception("Unsupported size: " + N);
            this.N = N;
            m = (long) Math.pow(2, Math.ceil(Math.log(N) / Math.log(2)));
            next = seed = new Random().nextInt((int) Math.min(N, Integer.MAX_VALUE));
        }
    
        public static void main(String[] args) throws Exception {
            RandomPermuteIterator r = new RandomPermuteIterator(100);
            while (r.hasMoreElements()) System.out.print(r.nextElement() + " ");
            //output:50 52 3 6 45 40 26 49 92 11 80 2 4 19 86 61 65 44 27 62 5 32 82 9 84 35 38 77 72 7 ...
        }
    
        @Override
        public boolean hasMoreElements() {
            return hasNext;
        }
    
        @Override
        public Long nextElement() {
            next = (a * next + c) % m;
            while (next >= N) next = (a * next + c) % m;
            if (next == seed) hasNext = false;
            return  next;
        }
    }
    
    0 讨论(0)
  • 2020-12-30 14:56

    How about this way (more functional); it even includes a main() to demonstrate. Basically, generate random numbers in the range of your list size until you have 1 of them all. We use HashSet to take care of duplicate random numbers in our range. Then, delegate iteration to the iterator of your index hashset. Logic of "hasNext()" becomes trivial through delegate.

    public class RandomIterator<T> implements Iterator<T> {
    
        private Iterator<Integer> indicies;
    
        List<T> delegate;
    
        public static void main(String[] args) {
            Random r = new Random();
    
            List<Integer> numbers = IntStream.generate(r::nextInt).limit(10).boxed().collect(toCollection(ArrayList::new));
            List<Integer> results = new ArrayList<>();
    
            for(RandomIterator<Integer> test = new RandomIterator<>(numbers); test.hasNext(); ) {
                results.add(test.next());
            }
            System.out.println("In list: " + numbers);
            System.out.println("random iteration " + results);
            if(results.containsAll(numbers) && results.size() == numbers.size())
                System.out.println("Everything accounted for");
            else
                System.out.println("Something broke!");
    
        }
    
        public RandomIterator(List<T> delegate) {
            Random r = new Random();
            this.delegate = delegate;
            Set<Integer> indexSet = new LinkedHashSet<>();
            while(indexSet.size() != delegate.size())
                indexSet.add(r.nextInt(delegate.size()));
    
            System.out.println(indexSet);
            indicies = indexSet.iterator();
        }
    
        @Override
        public boolean hasNext() {
            return indicies.hasNext();
        }
    
        @Override
        public T next() {
            return delegate.get(indicies.next());
        }
    }
    

    If you just want an print 100 random numbers:

         IntStream.generate(r::nextInt).limit(100).forEach(System.out::println);
    

    if you want an iterator (for whatever reason:)

    IntStream.generate(r::nextInt).limit(100).boxed().collect(Collectors.toList()).iterator();
    
    0 讨论(0)
  • 2020-12-30 14:56

    Here is a shuffling iterator that is very space&time-efficient when requesting only a few elements. It's very reasonable in all other cases too; runtime and space are both O(n). The only dynamic structure is a HashMap with up to half the size of the input list (although it will be much smaller near the beginning and the end) and Integer keys.

    static <A> Iterator<A> shuffledIterator(final List<A> l) {
      return new Iterator<A>() {
        Random randomizer = new Random();
        int i = 0, n = l.size();
        HashMap<Integer, A> shuffled = new HashMap();
    
        public boolean hasNext() { return i < n; }
    
        public A next() {
          int j = i+randomizer.nextInt(n-i);
          A a = get(i), b = get(j);
          shuffled.put(j, a);
          shuffled.remove(i);
          ++i;
          return b;
        }
    
        A get(int i) {
          return shuffled.containsKey(i) ? shuffled.get(i) : l.get(i);
        }
      };
    }
    
    0 讨论(0)
  • 2020-12-30 14:59

    You can use Collections.shuffle() on the list.

    Note that this will shuffle the list itself, so if order is important you should make a copy of it (and shuffle the copy).

    List<Customer> newList = new ArrayList<>( oldList ) ;
    Collections.shuffle( newList ) ;
    

    Alternatively you could create a random array which has elements 0 - List.size()-1 and using those as indices to access "random" elements in the List.

    0 讨论(0)
提交回复
热议问题