Pergunta

Eu gostaria de saber a melhor maneira (mais compacto e forma "pythônico") para fazer um tratamento especial para o último elemento em um loop. Há um pedaço de código que só deve ser chamado entre elementos, sendo suprimida na última.

Aqui está como eu atualmente fazê-lo:

for i, data in enumerate(data_list):
    code_that_is_done_for_every_element
    if i != len(data_list) - 1:
        code_that_is_done_between_elements

Existe alguma maneira melhor?

Nota: eu não quero fazê-lo com hacks como o uso de reduce;)

Foi útil?

Solução

Na maioria das vezes é mais fácil (e barato) para fazer o início iteração o caso especial em vez da última:

first = True
for data in data_list:
    if first:
        first = False
    else:
        between_items()

    item()

Isto irá funcionar para qualquer iterable, mesmo para aqueles que não têm len():

file = open('/path/to/file')
for line in file:
    process_line(line)

    # No way of telling if this is the last line!

Além disso, eu não acho que existe uma solução geralmente superior, uma vez que depende do que você está tentando fazer. Por exemplo, se você está construindo uma seqüência de uma lista, é naturalmente melhor para uso str.join() do que usar um loop for “com caso especial”.


Usando o mesmo princípio, mas mais compacto:

for i, line in enumerate(data_list):
    if i > 0:
        between_items()
    item()

Parece familiar, não é? :)


Para @ofko, e outros que realmente precisam para descobrir se o valor atual de um iterable sem len() é o último, você vai precisar de olhar em frente:

def lookahead(iterable):
    """Pass through all values from the given iterable, augmented by the
    information if there are more values to come after the current one
    (True), or if it is the last value (False).
    """
    # Get an iterator and pull the first value.
    it = iter(iterable)
    last = next(it)
    # Run the iterator to exhaustion (starting from the second value).
    for val in it:
        # Report the *previous* value (more to come).
        yield last, True
        last = val
    # Report the last value.
    yield last, False

Em seguida, você pode usá-lo como este:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False

Outras dicas

O 'código entre' é um exemplo do Cabeça-Cauda padrão.

tem um item, o que é seguido por uma sequência de (entre, item) pares. Pode também visualizar este como uma sequência de (item, entre) os pares seguido de um item. Em geral, é mais simples para dar o primeiro elemento como especial e todos os outros conforme o caso "padrão".

Além disso, para evitar código repetindo, você tem que fornecer uma função ou outro objeto para conter o código que você não deseja repetir. Incorporando um se declaração em um loop que é sempre falsa, exceto um tempo é meio bobo.

def item_processing( item ):
    # *the common processing*

head_tail_iter = iter( someSequence )
head = head_tail_iter.next()
item_processing( head )
for item in head_tail_iter:
    # *the between processing*
    item_processing( item )

Este é mais confiável porque é um pouco mais fácil de provar, Ele não cria uma estrutura de dados adicionais (ou seja, uma cópia de uma lista) e não requer um monte de execução desperdiçado de um se condição que é sempre falsa, exceto uma vez.

Se você está procurando simplesmente para modificar o último elemento data_list então você pode simplesmente usar a notação:

L[-1]

No entanto, parece que você está fazendo mais do que isso. Não há nada realmente errado com o seu caminho. Eu mesmo tomou uma rápida olhada em alguns Django código para suas marcas de modelo e eles fazem basicamente o que você está fazendo.

Embora essa pergunta é muito velho, eu vim aqui via google e eu encontrei uma maneira bastante simples: Lista corte. Vamos dizer que você quer colocar um '&' entre todas as entradas da lista.

s = ""
l = [1, 2, 3]
for i in l[:-1]:
    s = s + str(i) + ' & '
s = s + str(l[-1])

Este retorna '1 & 2 & 3'.

Este é semelhante à abordagem de Formigas Aasma mas sem usar o módulo itertools. É também um iterador atraso que parece à frente um único elemento no fluxo iterator:

def last_iter(it):
    # Ensure it's an iterator and get the first field
    it = iter(it)
    prev = next(it)
    for item in it:
        # Lag by one item so I know I'm not at the end
        yield 0, prev
        prev = item
    # Last item
    yield 1, prev

def test(data):
    result = list(last_iter(data))
    if not result:
        return
    if len(result) > 1:
        assert set(x[0] for x in result[:-1]) == set([0]), result
    assert result[-1][0] == 1

test([])
test([1])
test([1, 2])
test(range(5))
test(xrange(4))

for is_last, item in last_iter("Hi!"):
    print is_last, item

Se os itens são originais:

for x in list:
    #code
    if x == list[-1]:
        #code

outras opções:

pos = -1
for x in list:
    pos += 1
    #code
    if pos == len(list) - 1:
        #code


for x in list:
    #code
#code - e.g. print x


if len(list) > 0:
    for x in list[:-1]
        #code
    for x in list[-1]:
        #code

Você pode usar uma janela deslizante sobre os dados de entrada para obter uma espiada no próximo valor e usar uma sentinela para detectar o último valor. Isso funciona em qualquer iterable, assim você não precisa saber o comprimento de antemão. A implementação pares é de itertools receitas .

from itertools import tee, izip, chain

def pairwise(seq):
    a,b = tee(seq)
    next(b, None)
    return izip(a,b)

def annotated_last(seq):
    """Returns an iterable of pairs of input item and a boolean that show if
    the current item is the last item in the sequence."""
    MISSING = object()
    for current_item, next_item in pairwise(chain(seq, [MISSING])):
        yield current_item, next_item is MISSING:

for item, is_last_item in annotated_last(data_list):
    if is_last_item:
        # current item is the last item

Não há nenhuma possibilidade de interagir sobre tudo, mas o último elemento, e tratar o último fora do loop? Afinal, um loop é criado para fazer algo semelhante a todos os elementos que você varrer; se um elemento precisa de algo especial, não deve estar no loop.

(ver também esta pergunta: faz -o-última-elemento-em-um-ciclo merecem-a-separar-tratamento )

EDIT: uma vez que a questão é mais sobre o "entre", ou o início elemento é o especial na medida em que não tem predecessor, ou o última elemento é especial na medida em que não tem sucessor.

Não há nada de errado com a sua maneira, a menos que você terá 100 000 loops e desejos poupar 100 000 declarações "if". Nesse caso, você pode ir por esse caminho:

iterable = [1,2,3] # Your date
iterator = iter(iterable) # get the data iterator

try :   # wrap all in a try / except
    while 1 : 
        item = iterator.next() 
        print item # put the "for loop" code here
except StopIteration, e : # make the process on the last element here
    print item

Saídas:

1
2
3
3

Mas, realmente, no seu caso eu sinto que é um exagero.

Em qualquer caso, você provavelmente vai ter mais sorte com corte:

for item in iterable[:-1] :
    print item
print "last :", iterable[-1]

#outputs
1
2
last : 3

ou apenas:

for item in iterable :
    print item
print iterable[-1]

#outputs
1
2
3
last : 3

Por fim, uma forma BEIJO fazer-lhe coisas, e que iria trabalhar com qualquer iterable, incluindo os sem __len__:

item = ''
for item in iterable :
    print item
print item

Ouputs:

1
2
3
3

Se sinto que eu iria fazê-lo dessa maneira, parece simples para mim.

Use corte e is para verificar se o último elemento:

for data in data_list:
    <code_that_is_done_for_every_element>
    if not data is data_list[-1]:
        <code_that_is_done_between_elements>

caveat.emptor : Isso só funciona se todos os elementos da lista são realmente diferentes (têm diferentes posições na memória). Sob o capô, pitão pode detectar elementos iguais e reutilizar os mesmos objectos para eles. Por exemplo, para cordas do mesmo valor e inteiros comuns.

O Google trouxe-me a esta pergunta de idade e eu acho que eu poderia acrescentar uma abordagem diferente para este problema.

A maioria das respostas aqui iria lidar com um tratamento adequado de um controle de malha, uma vez que foi perguntado, mas se o data_list é destrutível, gostaria de sugerir que você estourar os itens da lista até que você acabar com uma lista vazia :

while True:
    element = element_list.pop(0)
    do_this_for_all_elements()
    if not element:
        do_this_only_for_last_element()
        break
    do_this_for_all_elements_but_last()

Você poderia até usar enquanto len (element_list) se você não precisa fazer nada com o último elemento. Acho isso solução mais elegante, em seguida, lidar com next ().

Eu gosto da abordagem de @ Ethan-t, mas while True é perigoso, do meu ponto de vista.

while L:
    e = L.pop(0)
    # process element
    if not L:
        print('Last element has been detected.')

atrasar o tratamento especial do último item até depois do loop.

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3

Assumindo entrada como um iterador, aqui está uma maneira usando tee e izip de itertools:

from itertools import tee, izip
items, between = tee(input_iterator, 2)  # Input must be an iterator.
first = items.next()
do_to_every_item(first)  # All "do to every" operations done to first item go here.
for i, b in izip(items, between):
    do_between_items(b)  # All "between" operations go here.
    do_to_every_item(i)  # All "do to every" operations go here.

Demonstração:

>>> def do_every(x): print "E", x
...
>>> def do_between(x): print "B", x
...
>>> test_input = iter(range(5))
>>>
>>> from itertools import tee, izip
>>>
>>> items, between = tee(test_input, 2)
>>> first = items.next()
>>> do_every(first)
E 0
>>> for i,b in izip(items, between):
...     do_between(b)
...     do_every(i)
...
B 0
E 1
B 1
E 2
B 2
E 3
B 3
E 4
>>>

Se você está indo através da lista, para mim isso funcionou muito:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()

A solução mais simples que vem à mente é:

for item in data_list:
    try:
        print(new)
    except NameError: pass
    new = item
print('The last item: ' + str(new))

Então, nós sempre olhar para frente um item, atrasando a do processamento de uma iteração. Para pular fazendo algo durante a primeira iteração eu simplesmente pegar o erro.

É claro que você precisa pensar um pouco, para que o NameError a ser levantada quando quiser.

Além disso, mantenha o `counstruct

try:
    new
except NameError: pass
else:
    # continue here if no error was raised

Esta confia que o novo nome não foi previamente definido. Se você é paranóico você pode garantir que new não existe usando:

try:
    del new
except NameError:
    pass

Como alternativa, você pode, naturalmente, também usar um if (if notfirst: print(new) else: notfirst = True). Mas, tanto quanto eu sei a sobrecarga é maior.


Using `timeit` yields:

    ...: try: new = 'test' 
    ...: except NameError: pass
    ...: 
100000000 loops, best of 3: 16.2 ns per loop

por isso espero que a sobrecarga de ser inelegível.

Conte os itens de uma vez manter-se com o número de itens restantes:

remaining = len(data_list)
for data in data_list:
    code_that_is_done_for_every_element

    remaining -= 1
    if remaining:
        code_that_is_done_between_elements

Desta forma você só avaliar o comprimento da lista uma vez. Muitas das soluções nesta página parecem assumir o comprimento está disponível com antecedência, mas que não faz parte da sua pergunta. Se você tem o comprimento, usá-lo.

Para mim, o mais simples e maneira Python para lidar com um caso especial no final de uma lista é a seguinte:

for data in data_list[:-1]:
    handle_element(data)
handle_special_element(data_list[-1])

Claro que isso também pode ser usado para tratar o primeiro elemento de uma maneira especial.

Pode haver várias maneiras. corte será mais rápido. Adicionando mais um que usos .Index () método:

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top