Search code examples
javajava-streamjava-17

Java 8 Split a stream into multiple streams of the same size (deck of cards)


I'm using JavaSE-17

A - I have 3 things :

(1) A list of n cards, say:

List<Card> cards;                      // where Card is a Record
Interger numberOfCards = cards.size(); // Usually cards.size() == 52

(2) A number of players, say:

Integer numberOfPlayers;

(3) A number of cards to deal to each player, say:

Integer numberOfCardsPerPlayer;

Assuming :

  • Each player receive the same number of card (numberOfCardsPerPlayer).
  • numberOfCardsPerPlayer * numberOfPlayers <= numberOfCards

B - I want to get a list containing numberOfPlayers lists of cards where each list of cards contains exactly numberOfCardsPerPlayer cards.

For example, if I have a deck of 52 cards, 3 players and 5 cards per player, I should get a list of 3 lists of cards with each list of cards contains 5 cards.

C - Question

Is there a way of doing it without the AtomicInteger and without calling stream two times? (and without using loops or external librairies).

D - The code below works but feels strange (using AtomicInteger feels like cheating because I try to avoid mutability and I'm not sure calling stream two times is good practice)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class DeckOfCards {
    public enum Suit {CLUB, DIAMOND, HEART, SPADE}
    public enum Value {TWO, THREE, FOUR, FIVE, SIX, SEVEN}

    public record Card(Suit suit,  Value value) {}

    public static void main(String[] args) {
        Integer numberOfPlayers = 3;
        Integer numberOfCardsPerPlayer = 2;
        List<Card> cards = new ArrayList<Card>(
                Arrays.asList(new Card(Suit.CLUB, Value.TWO),
                        new Card(Suit.DIAMOND, Value.SIX),
                        new Card(Suit.HEART, Value.FOUR),
                        new Card(Suit.SPADE, Value.SEVEN),
                        new Card(Suit.HEART, Value.FIVE),
                        new Card(Suit.CLUB, Value.FOUR),
                        new Card(Suit.SPADE, Value.THREE)
                        )
                );

        AtomicInteger counter = new AtomicInteger();
        List<List<Card>> result = 
                cards.stream()
                .limit(numberOfCardsPerPlayer * numberOfPlayers)
                .collect(Collectors.groupingBy(x -> counter.getAndIncrement() / numberOfCardsPerPlayer))
                .values()
                .stream()
                .collect(Collectors.toList());
        
        System.out.println(result);
        // Prints :
        // [
        //   [Card[suit=CLUB, value=TWO], Card[suit=DIAMOND, value=SIX]]
        // , [Card[suit=HEART, value=FOUR], Card[suit=SPADE, value=SEVEN]]
        // , [Card[suit=HEART, value=FIVE], Card[suit=CLUB, value=FOUR]]
        // ]

    }
}

Solution

  • Here is one way. I am using a record to simulate cards as as simple integer.

    • First stream the number of players via IntStream.
    • then create a subList taking the proper number of cards for each player.
    • the above is mapped to place each new "hand" on the stream.
    • then return each hand in list of lists.
    record Card(int a) {
    }
    
    Integer numberOfPlayers = 3;
    Integer numberOfCardsPerPlayer = 5;
    List<Card> cards = IntStream.range(0, numberOfPlayer*numberOfCardsPerPlayer).mapToObj(i -> new Card(i))
            .toList();
    
     List<List<Card>> hands = IntStream.range(0, numberOfPlayers)
                    .<List<Card>>mapToObj(player -> new ArrayList<>(
                            cards.subList(player * numberOfCardsPerPlayer,
                                    (player + 1) * numberOfCardsPerPlayer)))
                    .toList();
    
    hands.forEach(System.out::println);
    

    prints

    [Card[a=0], Card[a=1], Card[a=2], Card[a=3], Card[a=4]]
    [Card[a=5], Card[a=6], Card[a=7], Card[a=8], Card[a=9]]
    [Card[a=10], Card[a=11], Card[a=12], Card[a=13], Card[a=14]]
    

    The reason I add the subList as an argument to ArrayList is because a subList provided a view of the original list. So if you alter a subList, you change the original list. Creating a new ArrayList makes the sublist independent.