This exercise is taken from Google's Python Class:
D. Given a list of numbers, return a list where
all adjacent == elements have been reduced to a single element,
so [1, 2, 2, 3] returns [1, 2, 3]. You may create a new list or
modify the passed in list.
Here's my solution so far:
def remove_adjacent(nums):
if not nums:
return nums
list = [nums[0]]
for num in nums[1:]:
if num != list[-1]:
list.append(num)
return list
But this looks more like a C program than a Python script, and I have a feeling this can be done much more elegant.
EDIT
So [1, 2, 2, 3] should give [1, 2, 3] and [1, 2, 3, 3, 2] should give [1, 2, 3, 2]
There is function in itertools that works here:
import itertools
[key for key,seq in itertools.groupby([1,1,1,2,2,3,4,4])]
You can also write a generator:
def remove_adjacent(items):
# iterate the items
it = iter(items)
# get the first one
last = next(it)
# yield it in any case
yield last
for current in it:
# if the next item is different yield it
if current != last:
yield current
last = current
# else: its a duplicate, do nothing with it
print list(remove_adjacent([1,1,1,2,2,3,4,4]))
itertools to the rescue.
import itertools
def remove_adjacent(lst):
i = iter(lst)
yield next(i)
for x, y in itertools.izip(lst, i):
if x != y:
yield y
L = [1, 2, 2, 3]
print list(remove_adjacent(L))
Solution using list comprehensions, zipping then iterating through a twice. Inefficient, but short and sweet. It also has the problem of extending a[1:] with something.
a = [ 1,2,2,2,3,4,4,5,3,3 ]
b = [ i for i,j in zip(a,a[1:] + [None]) if not i == j ]
This works, but I'm not quite happy with it yet because of the +[None] bit to ensure that the last element is also returned...
>>> mylist=[1,2,2,3,3,3,3,4,5,5,5]
>>> [x for x, y in zip(mylist, mylist[1:]+[None]) if x != y]
[1, 2, 3, 4, 5]
The most Pythonic way is probably to go the path of least resistance and use itertools.groupby() as suggested by THC4K and be done with it.
>>> def collapse( data ):
... return list(sorted(set(data)))
...
>>> collapse([1,2,2,3])
[1, 2, 3]
Second attempt after the additional requirment was added:
>>> def remove_adjacent( data ):
... last = None
... for datum in data:
... if datum != last:
... last = datum
... yield datum
...
>>> list( remove_adjacent( [1,2,2,3,2] ) )
[1, 2, 3, 2]
You may want to look at itertools. Also, here's a tutorial on Python iterators and generators (pdf).
This is also somewhat functional; it could be written as a one-liner using lambdas but that would just make it more confusing. In Python 3 you'd need to import reduce from functools.
def remove_adjacent(nums):
def maybe_append(l, x):
return l + ([] if len(l) and l[-1] == x else [x])
return reduce(maybe_append, nums, [])
Related
I have this list:
arr = [1, 2, 3, 4, 5, 6]
What I wanted to do is make a new list with values from index 5 to index 1.
Output would be:
[6, 1, 2]
This is what I've done:
output = arr[5:] + arr[:2]
But I wanted to know if there's another way of slicing it.
Like just a normal slicing like for example:
output = arr[5:1]
But I know it's not gonna work because I've done that. Could you please help me?
As far as I'm aware, doing this without writing your own custom code doesn't seem possible. Python doesn't wrap lists around.
You can create a custom generator to do what you want, though:
>>> def cycle(l, start=0):
... l = l[start:] + l[:start]
... while True:
... x = l.pop(0)
... yield x
... l.append(x)
...
>>> k = cycle(a, 5)
>>> next(k)
6
>>> next(k)
1
>>> next(k)
2
(Example rolled back due to OP's post change.)
Here's an improved version that will take into account the number elements you want to get from the generator:
>>> def cycle(l, start=0, iters=None):
... l = l[start:] + l[:start]
... i = 0
... while True:
... if iters is not None and i == iters:
... raise StopIteration
... x = l.pop(0)
... yield x
... l.append(x)
... i += 1
...
>>> a = [1, 2, 3, 4, 5, 6]
>>> list(cycle(a, start=5, iters=3))
[6, 1, 2]
Update:
Rotate left n elements (or right for negative n) and slice number of element you want
L = L[n:] + L[:n] # rotate left n elements
In ur case n is 5:
>>> output = arr[5:] + arr[:5]
>>> output[:3]
[6, 1, 2]
Previous
>>> arr = [1, 2, 3, 4, 5, 6]
>>> output = arr[:]
>>> del output[2:5]
>>> output
[1, 2, 6]
>>>
Create a function to slice the input array for you and append the two parts together to get the desired list.
def cyc_slice(lst, start, n):
return lst[start:] + lst[:(start+n)%len(lst)]
Unlike both other answers, this doesn't make a superflous copy of all the list elements that you don't want.
>>> arr=[1,2,3,4,5,6]
>>> cyc_slice(arr, 5, 3)
[6, 1, 2]
And an improved iterator solution:
def cycle(l, start=0, n=None):
if not l:
return
idx = start-1
end = (idx + n) % len(l) if n else -1
while idx != end:
idx+=1
try:
x = l[idx]
except IndexError:
idx = 0
x = l[idx]
yield x
when provided with a count, it will provide that many elements. Otherwise, it can keep looping. This iterates through the list in place, so doesn't allocate any elements to memory (unless you create a list from it)
>>> list(cycle(arr,5,3))
[6, 1, 2]
Is there syntax to get the elements of a list not within a given slice?
Given the slice [1:4] it's easy to get those elements:
>>> l = [1,2,3,4,5]
>>> l[1:4]
[2, 3, 4]
If I want the rest of the list I can do:
>>> l[:1] + l[4:]
[1, 5]
Is there an even more succinct way to do this? I realize that I may be being too needy because this is already very concise.
EDIT: I do not think that this is a duplicate of Invert slice in python because I do not wish to modify my original list.
If you want to modify the list in-place, you can delete the slice:
>>> l = [1, 2, 3, 4, 5]
>>> del l[1:4]
>>> l
[1, 5]
Otherwise your originally suggestion would be the most succinct way. There isn't a way to get the opposite of a list slice using a single slice statement.
Clearly the best solution to create a class to encapsulate some magical behavior that occurs when you use 'c' as the step value. Clearly.
class SuperList(list):
def __getitem__(self, val):
if type(val) is slice and val.step == 'c':
copy = self[:]
copy[val.start:val.stop] = []
return copy
return super(SuperList, self).__getitem__(val)
l = SuperList([1,2,3,4,5])
print l[1:4:'c'] # [1, 5]
[x for i, x in enumerate(l) if i not in range(1, 4)]
Which is less concise. So the answer to your question is no, you can't do it more concisely.
I was looking for some solution for this problem that would allow for proper handling of the step parameter as well.
None of the proposed solution was really viable, so I ended up writing my own:
def complement_slice(items, slice_):
to_exclude = set(range(len(items))[slice_])
step = slice_.step if slice_.step else 1
result = [
item for i, item in enumerate(items) if i not in to_exclude]
if step > 0:
return result
else:
return result[::-1]
ll = [x + 1 for x in range(5)]
# [1, 2, 3, 4, 5]
sl = slice(1, 4)
ll[sl]
# [2, 3, 4]
complement_slice(ll, sl)
# [1, 5]
To the best of my knowledge, it does handle all the corner cases as well, including steps, both positive and negative, as well as repeating values.
I wanted to write it as a generator, but I got annoyed by checking all corner cases for positive/negative/None values for all parameters.
In principle, that is possible, of course.
You can use list comprehension with loop
l = [i for i in l if i not in l[1:4]]
I've been stuck on this for a while now. It sounds simple enough but here's the code I've been working with so far. Basically the function is passed a bunch of numbers in a list for example a_list = [1,2,3,3,4,3] and I should get [1,2,3,4,3]
def remove_doubles(a_list):
print()
new_list = []
for i in range(len(a_list)):
if a_list[i] != a_list[i+1]:
new_list.append(a_list[i])
return new_list
itertools.groupby makes very easy work of this:
In [28]: L = [1,2,3,3,4,3]
In [29]: answer = [k for k,_g in itertools.groupby(L)]
In [30]: answer
Out[30]: [1, 2, 3, 4, 3]
Or a simpler one liner:
In [33]: next(zip(*itertools.groupby(L)))
Out[33]: (1, 2, 3, 4, 3)
You can use generator:
def remove_doubles(a_list):
duplicate = None
for item in a_list:
if duplicate != item:
duplicate = item
yield item
a_list = [1, 2, 3, 3, 4, 3]
print(list(remove_doubles(a_list))) # [1, 2, 3, 4, 3]
It check if last item isn't the same as current, and if isn't returns it. Otherwise goes to the next item from the list and process starts from the beginning. As you can see, I used initial value for duplicate as None, so we can make a first comparison during first iteration.
>>> a_list = [1, 2, 3, 3, 4, 3]
>>>
>>> def remove_doubles(a_list):
... prev_item = a_list[0]
... # the first item in our "to_return" list will be the first item in the input array
... to_return = [prev_item]
... i = 1
... while i < len(a_list):
... # if the current element equals the previous element, do nothing (i.e. don't append the current element)
... if a_list[i] == prev_item:
... pass
... else:
... # otherwise, reassign the current element to prev_item
... # since we know it's unique, we can append it to our list to be returned
... prev_item = a_list[i]
... to_return.append(prev_item)
... i += 1
... return to_return
...
...
>>> remove_doubles(a_list)
[1, 2, 3, 4, 3]
Maybe this is what you are looking for:
def remove_doubles(a_list):
new_list = a_list[:1]
for i in range(1, len(a_list)):
if new_list[-1] != a_list[i]:
new_list.append(a_list[i])
return new_list
Edit:
If you're wanting to mutate a_list to remove adjacent duplicate values instead of simply returning a new list with them removed, you can simply change return new_list to a_list[:] = new_list.
This should work.
def remove_doubles(a_list):
new_list = [a_list[0]]
for i in range(1, len(a_list)):
if a_list[i] != a_list[i-1]:
new_list.append(a_list[i])
return new_list
I'm doing some Google Python Class exercises and I'm trying to find a pythonic solution to the following problem.
D. Given a list of numbers, return a list where all adjacent ==
elements have been reduced to a single element, so [1, 2, 2, 3]
returns [1, 2, 3]. You may create a new list or modify the passed in
list.
My try, which is working perfectly is the following:
def remove_adjacent(nums):
result = []
for num in nums:
if len(result) == 0 or num != result[-1]:
result.append(num)
return result
For example, with remove_adjacent([2, 2, 3, 3, 3]) the output is [2, 3]. Everything's ok.
I'm trying to use list comprehensions in order to archieve this in a more pythonic way, so my try is the following:
def remove_adjacent(nums):
result = []
result = [num for num in nums if (len(result)==0 or num!=result[-1])]
return result
This, with the same input [2, 2, 3, 3, 3], the output is [2, 2, 3, 3, 3] (the same). Meeeh! Wrong.
What I'm doing wrong with the list comprehensions? Am I trying to do something which is impossible to do with list comprehensions? I know it's a bit weird to initialize the list (result = []), so maybe it's not posible to do it using list comprehensions in this case.
Am I trying to do something which is impossible to do with list comprehensions?
Yep. A list comprehension can't refer to itself by name, because the variable doesn't get bound at all until the comprehension is completely done evaluating. That's why you get a NameError if you don't have result = [] in your second code block.
If it's not cheating to use standard modules, consider using groupby to group together similar values in your list:
>>> import itertools
>>> seq = [1, 2, 2, 3]
>>> [k for k,v in itertools.groupby(seq)]
[1, 2, 3]
>>> seq = [2,2,3,3,3]
>>> [k for k,v in itertools.groupby(seq)]
[2, 3]
For the sake of learning, I'd suggest using core reduce function:
def remove_adjacent(lst):
return reduce(lambda x, y: x+[y] if not x or x[-1] != y else x, lst, [])
I know this question has been asked lots of times, but I am not asking how to remove duplicate elements from a list only, I want to remove the duplicated element as well.
For example, if I have a list:
x = [1, 2, 5, 3, 4, 1, 5]
I want the list to be:
x = [2, 3, 4] # removed 1 and 5 since they were repeated
I can't use a set, since that will include 1 and 5.
Should I use a Counter? Is there a better way?
This should be done with a Counter object. It's trivial.
from collections import Counter
x = [k for k, v in Counter([1, 2, 5, 3, 4, 1, 5]).iteritems() if v == 1]
print x
Output:
[2, 3, 4]
Maybe this way:
[_ for _ in x if x.count(_) == 1]
EDIT: This is not the best way in term of time complexity as you can see in the comment above, sorry my mistake.
Something more verbose and O(n):
x = [1, 2, 2, 3, 4]
def counts_fold(acc, x):
acc[x] = acc[x]+1 if x in acc else 1
return acc
counts = reduce(counts_fold, x, {})
y = [i for i in x if counts[i] == 1]
print y
How about
duplicates = set(x)
x = [elem for elem in x if elem not in duplicates]
This has the advantage of being O(n) instead of O(n^2).
Edit. Indeed my bad, I must have been half asleep. Mahmoud's answer above is the correct one.