3

Is there a way to "shuffle" up an Iterable (or Sequence), such that elements are subsequently ordered randomly, similar to Java's Collections.shuffle()? I've looked in the API docs for Iterable, Collection, and Sequence, but haven't found anything relevant. (Side note: ceylon.language::shuffle is confusingly named)

I could implement a shuffle myself I suppose, but I'm busy trying to be lazy :-)

Lii
  • 11,553
  • 8
  • 64
  • 88
Max
  • 4,882
  • 2
  • 29
  • 43

3 Answers3

1

I also went looking for this and couldn't find it. Here's an implementation:

import ceylon.collection {ArrayList}
import ceylon.math.float {random}

"Don't interleave multiple iterators!"
Iterable<Element, Absent> shuffle<Element, Absent>(Iterable<Element, Absent> elements)
        given Absent satisfies Null => object satisfies Iterable<Element, Absent> {
    value list = ArrayList{elements = elements;};
    iterator() => object satisfies Iterator<Element> {
        variable value index = list.size;
        shared actual Element|Finished next() {
            value randomIndex = (random() * index--).integer;
            if (exists element = list[index]) {
                assert (exists randomElement = list[randomIndex]);
                list.set(index, randomElement);
                list.set(randomIndex, element);
                return randomElement;
            }
            return finished;
        }
    };
};
gdejohn
  • 7,451
  • 1
  • 33
  • 49
  • 1
    Thanks gdejohn; it'd be nice if there was a Collections or CollectionUtil sort of library. Guess I'll have to start one myself ;) – Max Dec 10 '13 at 06:38
  • 1
    FTR, I implemented `ceylon.collection.ArrayList` a couple of days ago. It will be in the next release. – Gavin King Dec 10 '13 at 13:58
1

The SDK now includes the module ceylon.random with a randomize function:

List<Elements> randomize<Elements>({Elements*} elements)

gdejohn
  • 7,451
  • 1
  • 33
  • 49
0

I ended up implementing my own, based on the last "inside-out" algorithm here.

[Element*] shuffle<Element>({Element*} input) {
    value newList = LinkedList<Element>();
    for(el in input){
        value j = math.randomInteger {lowerBound=0; upperBound=newList.size; inclusiveUpperBound=true;};
        if(j == newList.size){
            newList.add(el);
        } else {
            value elementToMove = newList[j];
            assert(exists elementToMove);
            newList.add(elementToMove);
            newList.set(j, el);
        }
    }
    return newList.sequence;
}

Haven't verified correctness yet. I implemented math.randomInteger too, which you can probably guess at the implementation.

Max
  • 4,882
  • 2
  • 29
  • 43
  • Using a `LinkedList` is problematic since setting the element at a specific index runs in linear time. – gdejohn Dec 10 '13 at 06:48
  • Yeah, I thought of that point after I went to bed -- Array is better here. – Max Dec 10 '13 at 16:00