Split list recursively until flat

Viewed 603

I'm writing a passion program that will determine the best poker hand given hole cards and community cards. As an ace can go both ways in a straight, I've coded this as [1, 14] for a given 5 card combination.

I understand recursion but implementing it is a different story for me. I'm looking for a function that will split all aces recursively, into all possible hand combinations until all nested lists are exhausted. This should obviously work with up to 4 aces, overlooking the fact that you wouldn't care about a straight at that point in all likelihood.

hand = [[1, 14], 2, 3, [1, 14], 7]

desired_output = [
        [1, 2, 3, 1, 7],
        [1, 2, 3, 14, 7],
        [14, 2, 3, 1, 7],
        [14, 2, 3, 14, 7]
        ]

I'm not proud of what I have so far, especially because it returns a list instead of something like a yield which would build the list I'm looking for:

def split_first_ace(hand):
    aces = [True if isinstance(x, list) else False for x in hand]
    for i, x in enumerate(aces):
        if x:
            ranks_temp = hand.copy()
            ace = ranks_temp.pop(i)
            return [[ace[0]] + ranks_temp, [ace[1]] + ranks_temp]

Any help on a solution would be appreciated, mostly because it'll help me understand how to implement recursion. But I'm open to other solutions as well.

3 Answers

Well, there is an easier way to do this:

from itertools import product

product(*[i if isinstance(i, list) else [i] for i in hand])

I challenge everybody to come up with a simpler solution

The itertools.product() function might be useful. If we assume that the recursion will only be 1 level deep (aces don't have nested lists themselves), then we could use the following:

from itertools import product

hand = [[1, 14], 2, 3, [1, 14], 7]

aces = [x for x in hand if isinstance(x, list)]
rest = [x for x in hand if isinstance(x, int)]

combinations = [list(x) + rest for x in product(*aces)]
print(combinations)

Yields:

[[1, 1, 2, 3, 7], [1, 14, 2, 3, 7], [14, 1, 2, 3, 7], [14, 14, 2, 3, 7]]

This might be overkill since you only need one level of recursion (like @Aaron Keesing's answer) but this should work:

def iter_hands(hand, __current_index=0, __current_combo=None):
    __current_combo = __current_combo or []
    if __current_index == len(hand):
        yield __current_combo.copy()
        return

    choices = hand[__current_index]
    if not isinstance(choices, list):
        choices = [choices]
    for c in choices:
        __current_combo.append(c)
        yield from iter_hands(hand, __current_index + 1, __current_combo)
        __current_combo.pop()


def main():
    input_hand = [[1, 14], 2, 3, [1, 14], 7]
    want_hands = [
        [1, 2, 3, 1, 7],
        [1, 2, 3, 14, 7],
        [14, 2, 3, 1, 7],
        [14, 2, 3, 14, 7]
    ]
    got_hands = [hand for hand in iter_hands(input_hand)]

    assert want_hands == got_hands


if __name__ == '__main__':
    main()
Related