Si tengo una lista, digamos l = [1, 8, 8, 8, 1, 3, 3, 8] y está garantizado que cada elemento aparece un número par de veces, ¿cómo hago una lista con todos los elementos de l ahora? ocurriendo n/2 veces. Entonces, dado que 1 ocurrió 2 veces, ahora debería ocurrir una vez. Dado que 8 ocurre 4 veces, ahora debería ocurrir dos veces. Dado que 3 ocurrió dos veces, debería ocurrir una vez.
Así que la nueva lista será algo así como k=[1,8,8,3]
¿Cuál es la forma más rápida de hacer esto? Hice list.count() para cada elemento pero fue muy lento.
Utilice un contador para realizar un seguimiento del recuento de cada elemento
from collections import Counter l = [1,8,8,8,1,3,3,8] res = [] count = Counter(l) # its like dict(1: 2, 8: 4, 3: 2) for key, val in count.items(): res.extend(val//2 * [key]) print(res) # output [1, 8, 8, 3]Si no le preocupa conservar el orden relativo, primero puede obtener un recuento de cada elemento usando collections.Counter y luego crear una nueva lista con cada elemento duplicado la mitad de veces.
>>> from collections import Counter >>> from itertools import chain >>> list(chain.from_iterable([key]*(count//2) for key, count in Counter(l).items())) [1, 8, 8, 3]Tal vez esto.
newList = [] for number in l: if(newList.count(number) < l.count(number)/2): newList.append(number) print(newList)Si el orden no es importante, una forma sería obtener los índices pares o impares solo después de una ordenación. Esas listas serán las mismas, por lo que solo necesita una de ellas.
l = [1,8,8,8,1,3,3,8] l.sort() # Get all odd indexes odd = l[1::2] # Get all even indexes even = l[::2] print(odd) print(odd == even)Resultado:
[1, 3, 8, 8] TrueDado que garantiza que cada elemento de la lista sea un múltiplo de 2, entonces es más rápido crear el contador a medida que crea la lista de salida, en lugar de crear un contador (o ordenar) primero y usarlo más tarde.
l = [1,8,8,8,1,3,3,8] count={} res=[] for i in l: if i in count: count[i]+=1 else: count[i]=1 if count[i]%2: res.append(i) print(res)Producción
[1,8,8,3]EDITAR Comparando tiempo/gasto de cada método
El uso del módulo timeit muestra que este enfoque es 2,7 veces más rápido que el uso de un contador primero.
es decir
def one(): l = [1,8,8,8,1,3,3,8] count={} res=[] for i in l: if i in count: count[i]+=1 else: count[i]=1 if count[i]%2: res.append(i) #print(res) def two(): from collections import Counter l = [1,8,8,8,1,3,3,8] res = [] count = Counter(l) # its like dict(1: 2, 8: 4, 3: 2) for key, val in count.items(): res.extend(val//2 * [key]) o=timeit.Timer(one) t=timeit.Timer(two) print(o.timeit(100000)) print(t.timeit(100000)) print(o.timeit(100000)) print(t.timeit(100000))Salida (segundos)
0.28666 0.80822 0.28678 0.80113Si el orden no es importante, entonces se preferiría el método de Wimanicesir con una aceleración 4 veces mayor, con un resultado de 0.07037 (~11 veces más rápido que con el enfoque contrario).
ACTUALIZACIÓN Sospechaba que usar el método Counter en two (desordenado) puede generar un aumento significativo o una ralentización en la importación, así que probé el método "contar primero, compilar el resultado después" mientras contaba con el método simple aquí desde one (ordenado)
count={} for i in l: if i in count: count[i]+=1 else: count[i]=1 que era mucho más rápido que Counter . Reemplazar Counter en two de las pruebas definidas resultó en un tiempo de 0,31 en lugar de 0,80. Sin embargo, aún es un poco más rápido compilar el resultado (ordenado) durante el conteo como en two . Y mucho más rápido para resultados desordenados al usar el método de Wimanicesir.
mantiene una lista de todos los elementos que se han visitado un número impar de veces. luego itera sobre todos los elementos de la lista.
en otros idiomas, probablemente usaría algún método map() o filter(), pero aquí hay un código simple ya que no conozco Python lo suficientemente bien. :)
l = [1,8,8,8,1,3,3,8] seen = [] result = [] for num in l: if num in seen: seen.remove(num) #result.append(num) #print every even appearance else: seen.append(num) result.append(num) #print every odd appearance if len(seen)==0: print(result) else: print("Error: uneven elements found:", seen)al final, la matriz visitada debe estar vacía, por lo que puede usar eso como una verificación de cordura antes de devolver la matriz de resultados.
editar: aquí hay una versión con filtro que devuelve las apariencias extrañas
l = [1,8,8,8,1,3,3,8] seen = [] result = list(filter(lambda x: seen.append(x) is None if x not in seen else not seen.remove(x) is None, l)) if len(seen)==0: print(result) else: print("Error: uneven elements found:", seen)y este devuelve las apariencias pares:
l = [1,8,8,8,1,3,3,8] seen = [] result = list(filter(lambda x: seen.remove(x) is None if x in seen else not seen.append(x) is None, l)) if len(seen)==0: print(result) else: print("Error: uneven elements found:", seen)En lugar de usar un contador, que realiza un seguimiento de un número entero para cada elemento posible de la lista, intente asignar elementos a booleanos usando un diccionario. Asigne a verdadero la primera vez que se ven, y luego cada vez después de eso, cambie el bit, y si es verdadero, omita el elemento.
Este es un caso de uso clásico de conjuntos y estoy bastante sorprendido de que nadie más lo haya probado para ver cómo se compara con las implementaciones de Counter y dict .
Implementé una solución usando set en su lugar de la siguiente manera:
def set_impl(l): bag = set() res = [] for i in l: if i in bag: res.append(i) bag.remove(i) else: bag.add(i) Esta implementación es un 28 % más rápida que usar Counter y un 51 % más rápida que usar un diccionario.
La implementación de ordenar y dividir proporcionada por Wimanicesir es la más rápida y brinda resultados 17 veces más rápido que cuando se usa set . Sin embargo, tenga en cuenta que debido a que ordena los elementos antes de eliminar los duplicados, el orden de aparición no se conserva a diferencia de los otros tres.
Aquí están todas las implementaciones sugeridas con el tiempo para la evaluación del rendimiento comparativo.
https://repl.it/@franzalex/StackOverflow-py#removeDuplicateHalf.py
import random import statistics as stats from collections import Counter as counter from timeit import Timer def slice_impl(l): l.sort() res = l[::2] def dict_impl(l): count={} res=[] for i in l: if i in count: count[i] += 1 else: count[i] = 1 if count[i] % 2: res.append(i) def counter_impl(l): count = counter(l) res = [] for key, val in count.items(): res.extend(val//2 * [key]) def set_impl(l): bag = set() res = [] for i in l: if i in bag: res.append(i) bag.remove(i) else: bag.add(i) def timed_run(): for name, func in {"Sort and Slice": slice_impl, "Dictionary": dict_impl, "Counter": counter_impl, "Set": set_impl}.items(): seq = list(range(50))*2 results = [] print(f"{name} Implementation Results") for i in range(50): if len(results) % 10: random.shuffle(seq) # shuffle after 10 runs results.append(Timer(lambda: func(seq)).timeit(10**4)) # print(f"Run {i+1:02}: {results[i]:.6f}") print("") print(f"Median: {stats.median(results):.6f}") print(f"Mean: {stats.mean(results):.6f}") print(f"Std Dev: {stats.stdev(results):.6f}") print("\n\n") timed_run()Resultado de la ejecución de la muestra
Ordenar y dividir resultados de implementación Mediana: 0.009686 Media: 0.009721 Desv estándar: 0.000529 Resultados de la implementación del diccionario Mediana: 0.230081 Media: 0.227631 Desv estándar: 0.014584 Resultados de la implementación del contador Mediana: 0.192730 Media: 0.194577 Desv estándar: 0.008015 Establecer resultados de implementación Mediana: 0.149604 Media: 0.151227 Desv estándar: 0.006838
import itertools st=time.time() lst = [1,8,8,8,1,3,3,8] list(itertools.chain.from_iterable(itertools.repeat(x, int(lst.count(x)/2)) for x in list(set(lst)) if lst.count(x)%2==0))Esto da una lista ordenada
Si el orden es importante, el siguiente código puede funcionar con O(N):
import collections c = collections.Counter(l) c2 = collections.Counter() i, n = 0, len(l) res=[] for x in l: if i == n//2:break if c2[x] < c[x] // 2: res.append(x) c2[x] += 1 i += 1Sé que esto ha sido respondido y hay algunas soluciones bastante largas. Y mencionó específicamente a Python. Sin embargo, pensé que una solución de Powershell podría ser interesante (¡y simple!) para algunos:
Versión 1 (agrupación de elementos - menos eficiente)
$OriginalArray = @("1","8","8","8","1","3","3","8") $NewArray = New-ObjectSystem.Collections.ArrayList $ArrayGroup = $OriginalArray | Group-Object | Select-Object Count,Name ForEach ($EachNumber in $ArrayGroup) { $HalfTheCount = (1..([Math]::Round($EachNumber.Count / 2))) ForEach ($Item in $HalfTheCount) {$NewArray.Add($EachNumber.Name) | Out-Null} } $NewArrayVersión 2 (elegir todos los demás elementos de una matriz ordenada, más eficiente)
$OriginalArray = @("1","8","8","8","1","3","3","8") $NewArray = New-Object System.Collections.ArrayList $OddOrEven = "Even" ForEach ($SortedItem in ($OriginalArray | Sort-Object)) { If ($OddOrEven -eq "Even") {$NewArray.Add($SortedItem);$EvenNumber = $True} If ($OddOrEven -eq "Odd") {$EvenNumber = $False} If ($EvenNumber -eq $True) {$OddOrEven = "Odd"} Else {$OddOrEven = "Even"} } $NewArrayMe gusta usar un conjunto de prueba, ya que necesitas detectar duplicados para eliminarlos, o un gran conjunto de hash (muchos cubos). El trie no se desequilibra y no es necesario saber el tamaño del conjunto final. Una alternativa es un tipo muy paralelo: fuerza bruta.