Remove consecutive duplicates from a list using yield generator? [closed]
Asked Answered
S

6

5

i'm trying to compress a list using generator:

examples

[1, 1, 1, 1, 2, 2, 2, 1, 1, 1] == [1, 2, 1]

[5, 5, 5, 4, 5, 6, 6, 5, 5, 7, 8, 0, 0])) == [5, 4, 5, 6, 5, 7, 8, 0]

I tried to use a generator that checks if the 1st and 2nd element are equal then check 2nd and 3rd and so on until it is no longer equal "when it reaches 4" and then yield "5" then it would repeat the process starting with "4"

code

test = [5, 5, 5, 4, 5, 6, 6, 5, 5, 7, 8, 0, 0] # sample list
from typing import Iterable
def compress(items: list) -> Iterable:

    x = 0
    while items[x] == items[x + 1]:
        x += 1
    yield items[x]


ans = compress(test)
for x in ans:
    print(ans)

but i keep getting

generator object compress at 0x00000254D383C820. why won't it loop?

if i try and use next() it only goes up to 5 and wont check the other numbers.

any assistance is greatly appreciated.

Superinduce answered 31/5, 2020 at 21:7 Comment(4)
Your generator encounters the yield statement exactly once. That's why it can only produce one value.Bickford
Once the end of the function is reached, the function exits and the generator ends. There is no implicit loop behavior.Pyromancy
you print ans and not x ... plus the comment from timgeb - it must be in a loop where you test for x to reach len(items)Madness
See Removing elements that have consecutive duplicates.Averil
M
4

So there are several flaws, all of them described as comments to the question post.

  • there is a loop missing that would yield more than one value
  • you print ans and not x, which logically is the generator object.

Is this code working for you?

test = [5, 5, 5, 4, 5, 6, 6, 5, 5, 7, 8, 0, 0]

def compress(items):
    for i, d in enumerate(items[:-1]):
        if d == items[i+1]:
            continue
        yield d
    yield items[-1]

for x in compress(test):
    print(x)
Madness answered 31/5, 2020 at 21:19 Comment(0)
J
12

As others have explained, your structure is incorrect - you're only encountering the yield once outside the loop. The ideal way would be to iterate over pairs of consecutive numbers and yield the first one in the loop if they are different.

However, here's a canonical method via itertools.groupby which removes consecutive duplicates:

from itertools import groupby 
from operator import itemgetter

list(map(itemgetter(0), groupby(l)))
# [1, 2, 1]
Jonquil answered 31/5, 2020 at 21:18 Comment(0)
D
4

If you're looking for the same effect as that, look into the itertools.groupby function. It's smarter to use built-in tools.

If you're interested in learning generators, write it. You didn't make any mistakes outside of having "yield" at the wrong level, but you wrote un-Pythonic code. Keeping track of x and x+1 and keeping the whole thing in memory are all bad.

class NeverInList: pass


from typing import Iterable
def compress(items: list) -> Iterable:
    previous = NeverInList()  # Something never in a list, duh. 
    for item in items:
        if item != previous:
            yield item
            previous = item


ans = compress(test)
for x in ans:
    print(x)   # "x", not "ans"
Downwards answered 31/5, 2020 at 21:19 Comment(1)
As a replacement to NeverInList, previous = object() should suffice. I've used object for sentinel values before, and it worked well.Pyromancy
M
4

So there are several flaws, all of them described as comments to the question post.

  • there is a loop missing that would yield more than one value
  • you print ans and not x, which logically is the generator object.

Is this code working for you?

test = [5, 5, 5, 4, 5, 6, 6, 5, 5, 7, 8, 0, 0]

def compress(items):
    for i, d in enumerate(items[:-1]):
        if d == items[i+1]:
            continue
        yield d
    yield items[-1]

for x in compress(test):
    print(x)
Madness answered 31/5, 2020 at 21:19 Comment(0)
D
2

you need to implement stack, and see if last element of list is same as value or not

test = [5, 5, 5, 4, 5, 6, 6, 5, 5, 7, 8, 0, 0]

def funct(lis):
    l = []
    for val in lis:
        if len(l) ==0:
            l.append(val)
            yield val
        if l[-1] != val:
            l.append(val)
            yield val


for result in funct(test):
    print(result)

output

5
4
5
6
5
7
8
0

way two, using comparison variable in O(N) time complexity and O(1) space complexity

def funct(lis):
    checker = None
    for val in lis:
        if checker != val:
            checker = val 
            yield val
Damselfish answered 31/5, 2020 at 21:20 Comment(1)
There are O(1) solutions to this problem but this is not one of them.Jonquil
A
2

Heh, I recognize this question from somewhere. ;)

I went about it a different way than you but I figured you might find it useful to peep at:

def compress(i:list):
    return[i[x]for x in range(len(i))if x==0 or i[x-1]!=i[x]]

The other answers fit the specifics of your question better, I just happened to recognize the setup and figured I'd dig up and offer the solution I gave to this very same problem. Cheers!

Analisaanalise answered 31/5, 2020 at 21:26 Comment(0)
P
2

One more solution: go through the combination of the list and the same list shifted by one (in other words, by the pairs of consecutive items) and select the second item from each pair where the first (previous) item differs from the second (next) item:

l = [5, 5, 5, 4, 5, 6, 6, 5, 5, 7, 8, 0, 0]
[l[0]] + [y for x,y in zip(l, l[1:]) if x!=y]
#[5, 4, 5, 6, 5, 7, 8, 0]

Incidentally, this is one of the fastest solutions presented so far.

Pacificia answered 31/5, 2020 at 22:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.