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?
6 Réponses :
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.
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.
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)
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]
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]
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.
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.
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.
>>> 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]
Véritable consommation intelligente du générateur là-bas! Un +1 bien mérité. ;)
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