19
votes

Diviser la liste de manière récursive jusqu'à ce qu'elle soit plate

J'écris un programme de passion qui déterminera la meilleure main de poker avec les cartes fermées et les cartes communautaires. Comme un as peut aller dans les deux sens dans une suite, j'ai codé ceci comme [1, 14] pour une combinaison donnée de 5 cartes.

Je comprends la récursivité mais la mettre en œuvre est une autre histoire pour moi. Je recherche une fonction qui divisera tous les as de manière récursive, dans toutes les combinaisons de mains possibles jusqu'à ce que toutes les listes imbriquées soient épuisées. Cela devrait évidemment fonctionner avec jusqu'à 4 as, en négligeant le fait que vous ne vous soucieriez pas d'une quinte à ce stade, selon toute vraisemblance.

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]

Je ne suis pas fier de ce que j'ai jusqu'à présent, surtout parce qu'il renvoie une liste au lieu de quelque chose comme un yield qui constituerait la liste que je recherche:

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]
        ]

Toute aide sur une solution serait appréciée, principalement parce qu'elle m'aidera à comprendre comment implémenter la récursivité. Mais je suis également ouvert à d'autres solutions.


4 commentaires

Juste un petit retour, vous pouvez remplacer la liste True / False par une liste d'index contenant un as (comme indiqué ici ). En bref, vous auriez des indices = [i for i, x in enumerate(hand) if x == [1,14]] . Maintenant, vous pouvez simplement parcourir les indices plutôt que d'énumérer sur les aces et vérifier si c'est True


@JolonB Oui, vous avez raison. Cela m'épargnerait un peu de redondance. Appréciez les commentaires. Je prétendrai avoir codé cette partie pour plus de clarté;)


puisque votre sortie sera une liste de mains ... cela pourrait être une bonne idée de définir la main de cette façon depuis le début, hand = [[[1, 14], 2, 3, [1, 14], 7]]


@RichieV J'aurai en effet des mains définies de cette façon, puisque je vais parcourir une liste de toutes les combinaisons de mains possibles pour déterminer si une main est une quinte, ou une couleur, ou ce que vous avez. Je viens d'écrire cet exemple pour l'aide.


3 Réponses :


10
votes

La fonction itertools.product() peut être utile. Si nous supposons que la récursion n'aura qu'un niveau de profondeur (les as n'ont pas de listes imbriquées eux-mêmes), alors nous pourrions utiliser ce qui suit:

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

Rendements:

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)


0 commentaires

0
votes

Cela peut être excessif car vous n'avez besoin que d'un seul niveau de récursivité (comme la réponse de @Aaron Keesing), mais cela devrait fonctionner:

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


0 commentaires

20
votes

Eh bien, il existe un moyen plus simple de le faire:

from itertools import product

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

Je mets tout le monde au défi de trouver une solution plus simple


3 commentaires

Il m'a fallu un certain temps pour comprendre que vous jetez chaque carte dans une liste et c'est pourquoi le product(* fonctionne ...


Est-ce que from collections.abc import Iterable isinstance(i, Iterable) serait pas mieux? (Ou la même chose, avec Sequence place.) Et, si vous voulez une optimisation prématurée , vous devriez probablement écrire *(...) au lieu de *[...] pour éviter la création inutile d'une liste (voir dis.dis() pour plus de détails - c'est une instruction de bytecode supplémentaire, cependant; vous avez besoin d'un POP_TOP supplémentaire car YIELD_VALUE renvoie None ). (Et, en fait, vous pouvez remplacer [i] par (i,) , même si je ne suis pas sûr que cela fasse quelque chose.)


@ wizzwizz4 toutes vos suggestions sont correctes. Vérifier Iterable rendra cela un peu plus général, et l'utilisation d'expressions de générateur et de tuples réduira l'empreinte mémoire. Cependant, étant donné la quantité de données, l'effet attendu sera faible. J'admets cependant que c'est une solution «assez bonne» et qu'en production, il vaut mieux s'en tenir aux meilleures pratiques que vous avez citées.