Related
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'm having a problem with 'symmetries' and I hope you can help.
I have a list which looks like
list = [[1,2,3,4,5,6],[2,4,6,8,10,12],[1,4,5,2,3,6],[1,2,3,4,9,3]]
and I want to identify symmetries of each element in this list. In this example if I look at the first element (= i) in the list and switch i[1:3] with i[3:5], then I want to search the rest of the list for matches. We can see that the list[3] matches i after the switch.
Can you help me write a fast solution for this? For example:
I have a function to switch elements which looks like:
def switch(i):
i[1:3], i[3:5] = i[3:5],i[1:3]
return i
and then I have a loop which looks like
no_sym = []
for i in list:
sym = [x for x in list if x not in no_sym and not no_sym.append(switch(i))]
continue
but this is not working for me.
Help!
You should not have append in this line:
sym = [x for x in list if x not in no_sym and not no_sym.append(switch(i))]
foo.append(bar) returns None, and so the code is equivalent to [x for x in list if x not in no_sym and not None], which allows every item not in no_sym to make it into sym.
Also, your swtich function actually modifies its arguments, which is probably no what you want. Check:
>>> ls = [[1,2,3,4,5,6], [2,4,6,8,10,12], [1,4,5,2,3,6], [1,2,3,4,9,3]]
>>> switch(ls[0]) == ls[2] # seems harmless
True
>>> switch(ls[0]) == ls[2] # but ls[0] has been modified, so the next test fails
False
To avoid this, switch should make a copy of the argument list:
def switch(ls):
copy_ls = ls[:]
copy_ls[1:3], copy_ls[3:5] = copy_ls[3:5], copy_ls[1:3]
return copy_ls
Now if you want every item and its "symmetrical" brother as a tuple in sym, this will work:
>>> ls = [[1,2,3,4,5,6], [2,4,6,8,10,12], [1,4,5,2,3,6], [1,2,3,4,9,3]]
>>> sym = [(x, switch(x)) for x in ls if switch(x) in ls[ls.index(x)+1:]]
>>> sym
[([1, 2, 3, 4, 5, 6], [1, 4, 5, 2, 3, 6])]
Also you should probably not use list as a variable name because it would shadow a builtin.
I think you not write the switch right.And I exactly do not understand why
you use (not no_sym.append(switch(i))).
In your example, it seems that you assume the index starts with 1. Actually, the index starts with 0. Therefore, your switch function should look like
def switch(i):
i[1:3], i[3:5] = i[3:5], i[1:3]
return i
Also, you need to provide more concrete definition of your 'symmetries'. Is your definition is something like 'tuples a and b are symmetry if len(a) = len(b) and either a[i] == b[i] && a[-(i+1)] == b[-(i+1)] or a[i] == b[-(i+1)] && a[-(i+1)]==b[i] for i >=0 and i
Here is what you want. I'm not sure if I understood what did you mean by "symmetry"
_list = [[1,2,3,4,5,6],[2,4,6,8,10,12],[1,4,5,2,3,6],[1,2,3,4,9,3]]
def switch(i):
result = i[:]
result[1:3], result[3:5] = result[3:5], result[1:3]
return result
sym = [(x, switch(x)) for x in _list if switch(x) in _list]
print sym
[([1, 2, 3, 4, 5, 6], [1, 4, 5, 2, 3, 6]), ([1, 4, 5, 2, 3, 6], [1, 2, 3, 4, 5, 6])]
The following Python code appears to be very long winded when coming from a Matlab background
>>> a = [1, 2, 3, 1, 2, 3]
>>> [index for index,value in enumerate(a) if value > 2]
[2, 5]
When in Matlab I can write:
>> a = [1, 2, 3, 1, 2, 3];
>> find(a>2)
ans =
3 6
Is there a short hand method of writing this in Python, or do I just stick with the long version?
Thank you for all the suggestions and explanation of the rationale for Python's syntax.
After finding the following on the numpy website, I think I have found a solution I like:
http://docs.scipy.org/doc/numpy/user/basics.indexing.html#boolean-or-mask-index-arrays
Applying the information from that website to my problem above, would give the following:
>>> from numpy import array
>>> a = array([1, 2, 3, 1, 2, 3])
>>> b = a>2
array([False, False, True, False, False, True], dtype=bool)
>>> r = array(range(len(b)))
>>> r(b)
[2, 5]
The following should then work (but I haven't got a Python interpreter on hand to test it):
class my_array(numpy.array):
def find(self, b):
r = array(range(len(b)))
return r(b)
>>> a = my_array([1, 2, 3, 1, 2, 3])
>>> a.find(a>2)
[2, 5]
Another way:
>>> [i for i in range(len(a)) if a[i] > 2]
[2, 5]
In general, remember that while find is a ready-cooked function, list comprehensions are a general, and thus very powerful solution. Nothing prevents you from writing a find function in Python and use it later as you wish. I.e.:
>>> def find_indices(lst, condition):
... return [i for i, elem in enumerate(lst) if condition(elem)]
...
>>> find_indices(a, lambda e: e > 2)
[2, 5]
Note that I'm using lists here to mimic Matlab. It would be more Pythonic to use generators and iterators.
In Python, you wouldn't use indexes for this at all, but just deal with the values—[value for value in a if value > 2]. Usually dealing with indexes means you're not doing something the best way.
If you do need an API similar to Matlab's, you would use numpy, a package for multidimensional arrays and numerical math in Python which is heavily inspired by Matlab. You would be using a numpy array instead of a list.
>>> import numpy
>>> a = numpy.array([1, 2, 3, 1, 2, 3])
>>> a
array([1, 2, 3, 1, 2, 3])
>>> numpy.where(a > 2)
(array([2, 5]),)
>>> a > 2
array([False, False, True, False, False, True], dtype=bool)
>>> a[numpy.where(a > 2)]
array([3, 3])
>>> a[a > 2]
array([3, 3])
For me it works well:
>>> import numpy as np
>>> a = np.array([1, 2, 3, 1, 2, 3])
>>> np.where(a > 2)[0]
[2 5]
Maybe another question is, "what are you going to do with those indices once you get them?" If you are going to use them to create another list, then in Python, they are an unnecessary middle step. If you want all the values that match a given condition, just use the builtin filter:
matchingVals = filter(lambda x : x>2, a)
Or write your own list comprhension:
matchingVals = [x for x in a if x > 2]
If you want to remove them from the list, then the Pythonic way is not to necessarily remove from the list, but write a list comprehension as if you were creating a new list, and assigning back in-place using the listvar[:] on the left-hand-side:
a[:] = [x for x in a if x <= 2]
Matlab supplies find because its array-centric model works by selecting items using their array indices. You can do this in Python, certainly, but the more Pythonic way is using iterators and generators, as already mentioned by #EliBendersky.
Even if it's a late answer: I think this is still a very good question and IMHO Python (without additional libraries or toolkits like numpy) is still lacking a convenient method to access the indices of list elements according to a manually defined filter.
You could manually define a function, which provides that functionality:
def indices(list, filtr=lambda x: bool(x)):
return [i for i,x in enumerate(list) if filtr(x)]
print(indices([1,0,3,5,1], lambda x: x==1))
Yields: [0, 4]
In my imagination the perfect way would be making a child class of list and adding the indices function as class method. In this way only the filter method would be needed:
class MyList(list):
def __init__(self, *args):
list.__init__(self, *args)
def indices(self, filtr=lambda x: bool(x)):
return [i for i,x in enumerate(self) if filtr(x)]
my_list = MyList([1,0,3,5,1])
my_list.indices(lambda x: x==1)
I elaborated a bit more on that topic here:
http://tinyurl.com/jajrr87
The following should then work (but I haven't got a Python interpreter
on hand to test it):
class my_array(numpy.array):
def find(self, b):
r = array(range(len(b)))
return r(b)
>>> a = my_array([1, 2, 3, 1, 2, 3])
>>> a.find(a>2)
[2, 5]
That's a good solution. But built-in types are not meant to be subclassed. You can use composition instead of inheritance. This should work:
import numpy
class my_array:
def __init__(self, data):
self.data = numpy.array(data)
def find(self, b):
r = numpy.array(list(range(len(self.data))))
return list(r[b])
>>> a = my_array([1, 2, 3, 1, 2, 3])
>>> a.find(a.data>2)
[2,5]
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.
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, [])