Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Randomly iterate over ArrayList<Integer> in Java

Tags:

java

Seems a very basic question. I've an ArrayList<Integer> al and I would like 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.

like image 702
Jungle Hunter Avatar asked Oct 08 '10 02:10

Jungle Hunter


4 Answers

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.

like image 79
NullUserException Avatar answered Oct 07 '22 10:10

NullUserException


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;
    }
}
like image 41
aykutfirat Avatar answered Oct 07 '22 10:10

aykutfirat


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();
like image 35
Christian Bongiorno Avatar answered Oct 07 '22 09:10

Christian Bongiorno


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

like image 27
Zpetkov Avatar answered Oct 07 '22 10:10

Zpetkov