Splitting a list into n other lists, and other questions

119 views Asked by At

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

2

There are 2 answers

15
gdejohn On BEST ANSWER

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("♠") {}
4
Gavin King On

How about something like:

alias Deck => {Card*};

{Deck*} split(Deck cards, Integer ways) {
    assert(ways.divides(cards.size));
    return { for i in (0:ways) { for (j->card in entries(cards)) if (j%ways==i) card } };
}

If Deck is actually a class, not very clear from your description, then this:

class Deck(shared {Card*} cards) { ... }

{Deck*} split(Deck deck, Integer ways) {
    assert(ways.divides(deck.cards.size));
    return { for i in (0:ways) Deck { for (j->card in entries(deck.cards)) if (j%ways==i) card } };
}

Note: I have not tested this.