6
votes

Python trouve les doublons qui se produisent plus de 3 fois

J'essaie de trouver un moyen efficace de rechercher trois doublons consécutifs ou plus et de les remplacer par un seul dans une liste Python.

list_before = [1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8]

# expected
list_after = [1, 2, 3, 4, 5, 6, 6, 7, 8]

def replace(list_to_replace):
    for idx, val in enumerate(list_to_replace):
        if idx + 3 < len(list_to_replace):
            if val == list_to_replace[idx+1] == list_to_replace[idx+2]:
                del list_to_replace[idx+1]
                del list_to_replace[idx+2]
    return list_to_replace

>>> replace(list_before)
[1, 1, 3, 4, 5, 5, 6, 7, 7, 8, 8, 8]

Ce qui semble être le problème ici? Existe-t-il un moyen plus efficace?


1 commentaires

Deux problèmes: modifier la liste au fur et à mesure que vous l'itérez et effectuer l'opération O (n) del au milieu de la liste. Les deux seraient résolus en ajoutant à une nouvelle liste (comme cela est fait par la réponse groupby) voir wiki.python. org / moin / TimeComplexity


6 Réponses :


10
votes

J'ai un bon cas d'utilisation pour itertools.groupby:

>>> from itertools import groupby
>>> list_before = [1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8]
>>> list_after = []
>>> for k, group in groupby(list_before):
...     lst = list(group)
...     if len(lst) >= 3:
...         list_after.append(k)
...     else:
...         list_after.extend(lst)
>>> list_after
[1, 2, 3, 4, 5, 6, 6, 7, 8]

Il serait possible de faire un one-liner avec itertools.chain mais la boucle for est presque certainement plus lisible et tout aussi performante.


1 commentaires

Je préfère de loin utiliser le vôtre que d'opter pour le one-liner, qui doit non seulement faire face aux bizarreries de la consommation de générateurs / itérateurs, mais également de la haine apparente du non de chain -diterables, lol.



-2
votes

Voici ma solution:

list_before = [1, 5, 7, 8, 6, 1, 4, 5, 6, 7, 1, 8, 8, 5, 2, 3, 7, 8, 8]

list_after = []
for item in list_before:
    if not item in list_after:
        list_after.append(item)


3 commentaires

comment cela contrôle-t-il l'apparition de 3 fois d'un élément?


Cela ne répond pas à la question.


En utilisant l'entrée de la question, cela donne [1, 2, 3, 4, 5, 6, 7, 8] et non [1, 2, 3, 4, 5, 6 , 6, 7, 8]



0
votes

Juste pour ajouter une approche orientée objet, que j'ai utilisée sur les streams:

input_values = [None]

Tests

input_values = []

sortie: [1, 2, 3, 4, 5, 6, 6, 7, 8]

input_values = [1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8]
StreamCount(input_values).process_all()

sortie: []

class StreamCount:
    def __init__(self, input_values):
        self.input_values = input_values
        self.output = []
        self.current_value = next(iter(input_values), None) # first element if there is any
        self.current_count = 0

    def process_new(self, value):
        if value == self.current_value:
            self.current_count += 1

        else:
            self.update_output()
            self.current_count = 1
            self.current_value = value

    def process_all(self):
        for v in self.input_values:
            self.process_new(v)

        # handle last values suite
        self.update_output()

        return self.output

    def update_output(self):
        if self.current_count > 2:
            self.output.append(self.current_value)
        else:
            self.output += [self.current_value for _ in range(self.current_count)]

sortie: [Aucun]


0 commentaires

1
votes

Comme l'a souligné Chris dans sa réponse, un one-liner est possible mais ce n'est pas joli du tout.

In [88]: list(chain.from_iterable([(x,) if len(y) >= 3 else y for x, y in [(k, tuple(g)) for k, g in groupby(list_before)]]))
Out[88]: [1, 2, 3, 4, 5, 6, 6, 7, 8]

Je pense qu'il devrait y avoir un meilleur moyen mais chain est suffisamment piraté pour être traité lorsqu'il s'agit de non-itérables.


5 commentaires

this;) list (chain.from_iterable ([k] * (sum (1for _ in islice (g, 3))% 2 or 2) for k, g in groupby (list_before)))


list (chain.from_iterable ([k] * (len (list (g) [: 3])% 2 ou 2) pour k, g dans groupby (list_before)))


@Chris_Rands Ack, les one-liners sont parfois le fléau de la programmation. Lol


@jamylak Lol, joli oneliner! On dirait que nous devrions aller coder le golf maintenant! :RÉ


Merci, oui quand je le faisais, j'ai pensé comment cela pourrait être plus court s'il était range (-1, len (list (g)) == 2) mais il semble moins lisible. Même si cela le pousse déjà. La solution @Chris_Rands est probablement la voie à suivre pour plus de clarté, la mienne est un peu trop rusée.



0
votes

Essayez de cette façon, en définissant une méthode personnalisée pour découper le tableau en fonction de la condition:

array = [1, 1, 2, 3, 4, 5, 5, 1, 5, 6, 6, 6, 7, 3, 7, 7, 8, 8, 8, 8, 8, 9]
take_max_three(array)
# => [[1, 1, 1], [2], [3, 3], [4], [5, 5, 5], [6, 6, 6], [7, 7, 7], [8, 8, 8], [9]]

Ensuite, appelez simplement la méthode sur le tableau, il s'agit d'un tableau légèrement modifié:

def take_max_three(iterable):
  iterable = sorted(iterable) # It requires the iterable to be sorted, remove if already sorted
  i, x, size = 0, 0, len(iterable)
  while i < size-1:
    if iterable[i] < iterable[i+1]:
      ready = iterable[x:i+1]
      if len(ready) <= 3:
        yield ready
      else:
        yield ready[0:3]
      x = i + 1
    i += 1
  yield iterable[x:x+3]

Vous pouvez personnaliser davantage la méthode en passant le nombre d'éléments à prendre.


0 commentaires

2
votes
>>> from itertools import groupby
>>> nums = [1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8]
>>> [k for k, g in groupby(nums) for i in range(1 + (len(list(g)) == 2))] 
[1, 2, 3, 4, 5, 6, 6, 7, 8]

1 commentaires

Véritable consommation intelligente du générateur là-bas! Un +1 bien mérité. ;)