开发者

Randomly iterate over ArrayList<Integer> in Java

Seems a very basic question. I've an ArrayList<Integer> al and I would lik开发者_StackOverflow社区e to iterate over it. Normally,

for(int i : al) {
    // some code
}

does the trick. But my requirement is to iterate not in sequence but randomly.


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.


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;
    }
}


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();


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!).


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜