2011-12-16 14 views
13

Tengo una lista de elementos que quiero dividir en función de un delimitador. Quiero eliminar todos los delimitadores y dividir la lista cuando se produce un delimitador dos veces. Por ejemplo, si el delimitador es 'X', a continuación, la siguiente lista:Forma más Pythonic de dividir una matriz repitiendo elementos

['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'] 

se convertiría en:

[['a', 'b'], ['c', 'd'], ['f', 'g']] 

en cuenta que el último grupo no se divide.

He escrito un código feo que hace esto, pero estoy seguro de que hay algo más agradable. Puntos extra si puede establecer un delimitador de longitud arbitrario (es decir, dividir la lista después de ver N delimitadores).

+0

¿Qué debería pasar con menos de N delimitadores? ¿Se eliminan simplemente (sin dividir)? –

+0

@MichaelMior - sí, simplemente se eliminaron (ver el tercer subgrupo). Y "menos" es la palabra que estabas buscando. – PaulMcG

+0

@PaulMcGuire Sí, vi el ejemplo. Pero le pido al OP que me aclare, ya que no quiero generalizar en base a un ejemplo. –

Respuesta

13

no creo que va a ser una buena solución, elegante a este (me encantaría estar equivocado, por supuesto) así que yo sugeriría algo sencillo:

def nSplit(lst, delim, count=2): 
    output = [[]] 
    delimCount = 0 
    for item in lst: 
     if item == delim: 
      delimCount += 1 
     elif delimCount >= count: 
      output.append([item]) 
      delimCount = 0 
     else: 
      output[-1].append(item) 
      delimCount = 0 
    return output 

 

>>> nSplit(['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'], 'X', 2) 
[['a', 'b'], ['c', 'd'], ['f', 'g']] 
+0

Me gusta este, más limpio que mi original.Creo que mi publicación original no estaba clara, pero quería que se dividiera cuando el delimitador se produce dos veces seguidas, no solo dos. El código anterior se puede modificar para hacerlo, todo lo que necesita hacer es establecer 'delimCount = 0' en la rama' else'. – speedplane

+0

Si hay 4 'X's seguidas en la entrada, ¿debería haber una lista vacía en la salida? Si es así, esta solución no hace esto. – PaulMcG

+0

@speedplane Sí, eso fue un error de mi parte. Corregido ahora – cobbal

3
a = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'] 
b = [[b for b in q if b != 'X'] for q in "".join(a).split("".join(['X' for i in range(2)]))] 

esto da

[['a', 'b'], ['c', 'd'], ['f', 'g']]

donde el 2 es el número de elementos que desea. es probable que haya una mejor manera de hacer esto.

+1

Tenga en cuenta que esto solo funciona si cada elemento tiene un solo carácter. –

4

Aquí está una manera de hacerlo con itertools.groupby():

import itertools 

class MultiDelimiterKeyCallable(object): 
    def __init__(self, delimiter, num_wanted=1): 
     self.delimiter = delimiter 
     self.num_wanted = num_wanted 

     self.num_found = 0 

    def __call__(self, value): 
     if value == self.delimiter: 
      self.num_found += 1 
      if self.num_found >= self.num_wanted: 
       self.num_found = 0 
       return True 
     else: 
      self.num_found = 0 

def split_multi_delimiter(items, delimiter, num_wanted): 
    keyfunc = MultiDelimiterKeyCallable(delimiter, num_wanted) 

    return (list(item 
       for item in group 
       if item != delimiter) 
      for key, group in itertools.groupby(items, keyfunc) 
      if not key) 

items = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'] 

print list(split_multi_delimiter(items, "X", 2)) 

I Debe decirse que la solución de cobbal es mucho más simple para los mismos resultados.

0

Ésta es otra manera de hacer esto:

def split_multi_delimiter(items, delimiter, num_wanted): 
    def remove_delimiter(objs): 
     return [obj for obj in objs if obj != delimiter] 

    ranges = [(index, index+num_wanted) for index in xrange(len(items)) 
       if items[index:index+num_wanted] == [delimiter] * num_wanted] 

    last_end = 0 
    for range_start, range_end in ranges: 
     yield remove_delimiter(items[last_end:range_start]) 
     last_end = range_end 

    yield remove_delimiter(items[last_end:]) 

items = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'] 
print list(split_multi_delimiter(items, "X", 2)) 
2

muy feo, pero quería ver si podía sacar esto adelante como una sola línea y pensé que iba a compartir. Sin embargo, le ruego que no use esta solución para nada de importancia. El ('X', 3) al final es el delimitador y el número de veces que debe repetirse.

(lambda delim, count: map(lambda x:filter(lambda y:y != delim, x), reduce(lambda x, y: (x[-1].append(y) if y != delim or x[-1][-count+1:] != [y]*(count-1) else x.append([])) or x, ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'], [[]])))('X', 2) 

EDITAR

He aquí un desglose. También eliminé un código redundante que era mucho más obvio cuando se escribía así.(Cambió anterior también)

# Wrap everything in a lambda form to avoid repeating values 
(lambda delim, count: 
    # Filter all sublists after construction 
    map(lambda x: filter(lambda y: y != delim, x), reduce(
     lambda x, y: (
      # Add the value to the current sub-list 
      x[-1].append(y) if 
       # but only if we have accumulated the 
       # specified number of delimiters 
       y != delim or x[-1][-count+1:] != [y]*(count-1) else 

       # Start a new sublist 
       x.append([]) or x, 
     ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'], [[]]) 
    ) 
)('X', 2) 
+0

¡Impresionante! ¡Horrible! ¡Increíble! ¡Horrible! +1, pero no estoy seguro si esta respuesta realmente cuenta como "útil". – PaulMcG

+0

@PaulMcGuire de acuerdo. Yo también entendería un -1. Esta respuesta definitivamente no debe ser aceptada de todos modos. –

+0

@PaulMcGuire Limpiar las cosas un poco hasta el punto donde la respuesta es casi viable. –

4

utilizar una función de generador para mantener el estado de su iterador a través de la lista, y el recuento del número de caracteres separadores visto hasta ahora:

l = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'] 

def splitOn(ll, x, n): 
    cur = [] 
    splitcount = 0 
    for c in ll: 
     if c == x: 
      splitcount += 1 
      if splitcount == n: 
       yield cur 
       cur = [] 
       splitcount = 0 
     else: 
      cur.append(c) 
      splitcount = 0 
    yield cur 

print list(splitOn(l, 'X', 2)) 
print list(splitOn(l, 'X', 1)) 
print list(splitOn(l, 'X', 3)) 

l += ['X','X'] 
print list(splitOn(l, 'X', 2)) 
print list(splitOn(l, 'X', 1)) 
print list(splitOn(l, 'X', 3)) 

impresiones:

[['a', 'b'], ['c', 'd'], ['f', 'g']] 
[['a', 'b'], [], ['c', 'd'], [], ['f'], ['g']] 
[['a', 'b', 'c', 'd', 'f', 'g']] 
[['a', 'b'], ['c', 'd'], ['f', 'g'], []] 
[['a', 'b'], [], ['c', 'd'], [], ['f'], ['g'], [], []] 
[['a', 'b', 'c', 'd', 'f', 'g']] 

EDIT: yo soy también un ventilador grande de GroupBy, aquí está mi ir en él:

from itertools import groupby 
def splitOn(ll, x, n): 
    cur = [] 
    for isdelim,grp in groupby(ll, key=lambda c:c==x): 
     if isdelim: 
      nn = sum(1 for c in grp) 
      while nn >= n: 
       yield cur 
       cur = [] 
       nn -= n 
     else: 
      cur.extend(grp) 
    yield cur 

No muy diferente de mi respuesta anterior, solo permite que groupby se encargue de iterar sobre la lista de entrada, creando grupos de coincidencia de delimitadores y caracteres que no coincidan con delimitadores. Los caracteres que no coinciden solo se agregan al elemento actual, los grupos de caracteres coincidentes hacen el trabajo de dividir elementos nuevos. Para listas largas, esto es probablemente un poco más eficiente, ya que groupby hace todo su trabajo en C, y aún solo itera sobre la lista una vez.

+0

Me gusta esto, como una forma de rubí para hacerlo. – Matt

+0

Agradable. Muy similar a la respuesta aceptada pero con generadores. – speedplane

0
In [6]: input = ['a', 'b', 'X', 'X', 'cc', 'XX', 'd', 'X', 'ee', 'X', 'X', 'f'] 

In [7]: [s.strip('_').split('_') for s in '_'.join(input).split('X_X')] 
Out[7]: [['a', 'b'], ['cc', 'XX', 'd', 'X', 'ee'], ['f']] 

Esto supone que usted puede utilizar un carácter reservado, como _ que no se encuentra en la entrada.

+0

También asume que los elementos se pueden convertir a caracteres. –

+0

cierto. Hice esa suposición después de ver solo caracteres en el ejemplo. –

0

demasiado listo, y sólo ofreció porque la manera obvia que hay que hacer que parece tan de fuerza bruta y feo:

class joiner(object): 
    def __init__(self, N, data =(), gluing = False): 
    self.data = data 
    self.N = N 
    self.gluing = gluing 
    def __add__(self, to_glue): 
    # Process an item from itertools.groupby, by either 
    # appending the data to the last item, starting a new item, 
    # or changing the 'gluing' state according to the number of 
    # consecutive delimiters that were found. 
    N = self.N 
    data = self.data 
    item = list(to_glue[1]) 
    # A chunk of delimiters; 
    # return a copy of self with the appropriate gluing state. 
    if to_glue[0]: return joiner(N, data, len(item) < N) 
    # Otherwise, handle the gluing appropriately, and reset gluing state. 
    a, b = (data[:-1], data[-1] if data else []) if self.gluing else (data, []) 
    return joiner(N, a + (b + item,)) 

def split_on_multiple(data, delimiter, N): 
    # Split the list into alternating groups of delimiters and non-delimiters, 
    # then use the joiner to join non-delimiter groups when the intervening 
    # delimiter group is short. 
    return sum(itertools.groupby(data, delimiter.__eq__), joiner(N)).data 
0

expresión regular, que optan por usted!

import re 

def split_multiple(delimiter, input): 
    pattern = ''.join(map(lambda x: ',' if x == delimiter else ' ', input)) 
    filtered = filter(lambda x: x != delimiter, input) 
    result = [] 
    for k in map(len, re.split(';', ''.join(re.split(',', 
     ';'.join(re.split(',{2,}', pattern)))))): 
     result.append([]) 
     for n in range(k): 
      result[-1].append(filtered.__next__()) 
    return result 

print(split_multiple('X', 
    ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'])) 

Oh, dijiste Python, no Perl.

1

Aquí es una buena solución limpia usando zip y generadores

#1 define traditional sequence split function 
#if you only want it for lists, you can use indexing to make it shorter 
def split(it, x): 
    to_yield = [] 
    for y in it: 
     if x == y: 
      yield to_yield 
      to_yield = [] 
     else: 
      to_yield.append(y) 
    if to_yield: 
     yield to_yield 

#2 zip the sequence with its tail 
#you could use itertools.chain to avoid creating unnecessary lists 
zipped = zip(l, l[1:] + ['']) 

#3. remove ('X',not 'X')'s from the resulting sequence, and leave only the first position of each 
# you can use list comprehension instead of generator expression 
filtered = (x for x,y in zipped if not (x == 'X' and y != 'X')) 

#4. split the result using traditional split 
result = [x for x in split(filtered, 'X')] 

este modo split() es más reutilizable.

Es sorprendente pitón no tiene uno construido en

edición:.

Se puede ajustar fácilmente para secuencias más largas de división, repitiendo los pasos 2-3 y pasar con velocidad filtrada con l [i:] para 0 < i < = n.

+0

y sí, sé que es lento. pero una solución sin embargo – soulcheck

1
import re  
map(list, re.sub('(?<=[a-z])X(?=[a-z])', '', ''.join(lst)).split('XX')) 

esto una lista -> string -> conversión de la lista y asume que los caracteres no delimitadores son todas las letras en minúscula.

+0

¿No podría deshacerse de la suposición si cambiara la expresión regular a '(? <= [^ X]) X (? = [^ X])'? – speedplane

+0

Claro, eso también funciona. Más conciso y generalizado. – mohit6up

Cuestiones relacionadas