ceylon

Splitting a list into n other lists, and other questions


I haven't quite verified the correctness and lack of off-by-one bugs, but bear with me for a moment.

The goal here is to deal/split a deck of cards (defined as a {Card*}) into multiple Decks (which optionally take a {Card*} as a constructor argument). I want to split the cards in a round-robin fashion, like cards would actually be dealt. Here's the code I have so far:

{Deck*} split(Integer ways) {
    assert(theCards.size % ways == 0);
    {Card*} empty = {};
    value decks = LinkedList { for (i in 0:ways) empty };
    for(i -> card in entries(theCards)) {
        value deckIndex = i % ways;
        assert (exists current = decks[deckIndex]);
        decks.set(deckIndex, {card, *current});
    }
    return { for(cards in decks) Deck(cards) };
}
  1. Is this the correct/idiomatic way to split a list into multiple lists?
  2. If I wanted to not reverse all the cards (that is, append to the list instead of prepend, or reverse the iterable) how might I do that?
  3. How would I initialize the values of the decks variable lazily inside the loop?
  4. Is there any way I can get away from needing the empty variable I have?
  5. Any chance I could write this without the need for mutable data structures?

Thanks, and sorry for the multi-question (I'd have created this on codereview.stackexchange.com but I don't have the rep to create a ceylon tag there).


Solution

  • Not reversed, lazy, no mutable data structures:

    alias Card => String;
    
    {{Card*}*} deal({Card*} cards, Integer players)
            => {for (i in 0:players) cards.skipping(i).by(players)};
    
    void run() {
        value suits = {"♦", "♣", "♥", "♠"};
        value ranks = {for (i in 2..10) i.string}.chain{"J", "Q", "K", "A"};
        value deck = {for (suit in suits) for (rank in ranks) rank + suit};
        print(deal(deck.taking(10), 2)); // { { 2♦, 4♦, 6♦, 8♦, 10♦ }, { 3♦, 5♦, 7♦, 9♦, J♦ } }
    }
    

    The laziness and immutable style comes at the cost of iterating through all of the cards for each hand. I prefer this eager solution:

    {Card[]*} deal({Card*} cards, Integer players) {
        value hands = [for (i in 0:players) SequenceBuilder<Card>()];
        for (card->hand in zipEntries(cards, hands.cycled)) {
            hand.append(card);
        }
        return {for (hand in hands) hand.sequence};
    }
    

    That way, you're just iterating the deck once.


    Note that Ceylon's enumerated types provide a nifty way to represent things like suits and ranks in a type-safe and object-oriented way, analogously to Java's enums:

    abstract class Suit(shared actual String string)
            of diamonds | clubs | hearts | spades {}
    
    object diamonds extends Suit("♦") {}
    object clubs extends Suit("♣") {}
    object hearts extends Suit("♥") {}
    object spades extends Suit("♠") {}