Iterating over only a part of list in Python - python

I have a list in python, that consists of both alphabetic and numeric elements, say something like list = ["a", 1, 2, 3, "b", 4, 5, 6] and I want to slice it into 2 lists, containing numbers that follow the alphabetic characters, so list1 = [1, 2, 3] and list2 = [4, 5, 6]. a and b elements could be in reversed order, but generally, I want to store numeric elements that follow a and b elements in separate lists. The easiest solution that I came up with was creating a loop with condition:
#Generating a list for numeric elements following "a":
for e in list[list.index("a")+1:]:
if not str.isdigit(e):
break
else:
list1.append(e)
I'd do it similarly for list2 and numeric elements after "b".
But could there be more elegant solutions? I'm new to Python, but I've seen beautiful one-liner constructions, could there be something like that in my case? Thanks in advance.

Something like this, maybe?
>>> import itertools
>>> import numbers
>>> lst = ["a", 1, 2, 3, "b", 4, 5, 6]
>>> groups = itertools.groupby(lst, key=lambda x: isinstance(x, numbers.Number))
>>> result = [[x for x in group_iter] for is_number, group_iter in groups if is_number]
>>> result
[[1, 2, 3], [4, 5, 6]]
And here is a less “sexy” version that outputs a list of tuple pairs (group_key, group_numbers):
>>> import itertools
>>> import numbers
>>> lst = ["a", 1, 2, 3, "b", 4, 5, 6]
>>> groups = itertools.groupby(lst, key=lambda x: isinstance(x, numbers.Number))
>>> group_key = None
>>> result = []
>>> for is_number, group_iter in groups:
... if not is_number:
... for x in group_iter:
... group_key = x
... else:
... result.append((group_key, [x for x in group_iter]))
>>> result
[('a', [1, 2, 3]), ('b', [4, 5, 6])]
Note that it is a quick and dirty version which expects the input data to be well-formed.

Here you have a functional aproach:
>>> l = ["a", 1, 2, 3, "b", 4, 5, 6]
>>> dig = [x for (x, y) in enumerate(l) if type(y) is str] + [len(l)]
>>> dig
[0, 4, 8]
>>> slices = zip(map(lambda x:x+1, dig), dig[1:])
>>> slices
[(1, 4), (5, 8)]
>>> lists = map(lambda (i, e): l[i:e], slices)
>>> lists
[[1, 2, 3], [4, 5, 6]]
First we get the index of the letters with, notice that we need the size of the list to know the end of it:
[x for (x, y) in enumerate(l) if type(y) is str] + [len(l)]
Then we get the pair of slices where the lists are:
zip(map(lambda x:x+1, dig), dig[1:])
Finally, we get each slice from the original list:
map(lambda (i, e): l[i:e], slices)

You can use slices:
list = ["a", 1, 2, 3, "b", 4, 5, 6]
lista = list[list.index('a')+1:list.index('b')]
listb = list[list.index('b')+1:]

Another approach (Python 3 only):
def chunks(values, idx=0):
''' Yield successive integer values delimited by a character. '''
tmp = []
for idx, val in enumerate(values[1:], idx):
if not isinstance(val, int):
yield from chunks(values[idx + 1:], idx)
break
tmp.append(val)
yield tmp
>>> values = ['a', 1, 2, 3, 'b', 4, 5, 6]
>>> list(chunks(values))
[[4, 5, 6], [1, 2, 3]]

Related

python get first x elements of a list and remove them

Note: I know there is probably an answer for this on StackOverflow already, I just can't find it.
I need to do this:
>>> lst = [1, 2, 3, 4, 5, 6]
>>> first_two = lst.magic_pop(2)
>>> first_two
[1, 2]
>>> lst
[3, 4, 5, 6]
Now magic_pop doesn't exist, I used it just to show an example of what I need. Is there a method like magic_pop that would help me to do everything in a pythonic way?
Do it in two steps. Use a slice to get the first two elements, then remove that slice from the list.
first_list = lst[:2]
del lst[:2]
If you want a one-liner, you can wrap it in a function.
def splice(lst, start = 0, end = None):
if end is None:
end = len(lst)
partial = lst[start:end]
del lst[start:end]
return partial
first_list = splice(lst, end = 2)
One option would be using slices
def func(lst: list, n: int) -> tuple:
return lst[:n], lst[n:]
lst = [1, 2, 3, 4, 5, 6]
first_two, lst = func(lst, 2)
print(lst)
# [3, 4, 5, 6]
Alternative, here is a kafkaesque approach using builtins:
def func(lst: list, i: int) -> list:
return list(map(lambda _: lst.pop(0), range(i)))
lst = list(range(10))
first_two = func(lst, 2)
print(first_two)
# [0, 1]
print(lst)
# [2, 3, 4, 5, 6, 7, 8, 9]
Try:
lst = [1, 2, 3, 4, 5, 6]
first_two, lst = lst[:2], lst[2:]
print(first_two)
print(lst)
Prints:
[1, 2]
[3, 4, 5, 6]
You can just create a lambda function to do this.
magic_pop = lambda x,y:(x[:y],x[y:]) if y<len(lst) else x
magic_pop(lst,3)
Out[8]: ([1, 2, 3], [4, 5, 6])

Remove duplicate values from the ordered list without `in` or `set` keywords

i am trying to remove duplicate values from ordered list without using IN or SET keywords
I have the following code, it removes the first occurrence of duplicates value, but not the second duplicate value
def remove_duplicates(list1):
new_list = list(list1)
indx = 0
while indx+1 < len(new_list):
if new_list[indx] == new_list[indx+1] :
new_list.pop(indx)
indx += 1
return new_list
with one duplication it works:
>>> remove_duplicates([1,2,3,3,4])
[1,2,3,4]
but not with three duplications:
>>> remove_duplicates([1,2,3,3,3,4])
[1,2,3,3,4]
as i know when you pop the value from the list it also reduces the size of by 1
any suggestions.
You can use filter function:
>>>uniq = {}
>>>filter(lambda x:uniq.update({x:1}),[1,2,3,3,3,4])
>>> print uniq.keys()
[1, 2, 3, 4]
Using dictionary keys
>>> dict.fromkeys(lst).keys()
[1, 2, 3, 5]
The trick is, that a dictionary allows only unique keys, so creating a dictionary from names with duplicated key names results in having only unique set of them.
Having list of values:
>>> lst = [1, 1, 1, 2, 3, 3, 5]
we create a dictioary using list values as keys:
>>> dct = dict.fromkeys(lst)
>>> dct
{1: None, 2: None, 3: None, 5: None}
As all the key names can be present only ones:
>>> dct.keys()
[1, 2, 3, 5]
We have what is needed.
>>> dict.fromkeys(lst).keys()
[1, 2, 3, 5]
I have to admit, that even though it is not using set, using dict keys is very similar approach.
Using generator
>>> def remove_duplicates(iterable):
... last_val = iterable.next()
... yield last_val
... for itm in iterable:
... if itm != last_val:
... last_val = itm
... yield last_val
...
>>> lst = [1, 1, 1, 2, 3, 3, 5]
>>> list(remove_duplicates(iter(lst)))
[1, 2, 3, 5]
Generator yields values one by one.
The initial value is yielded before the loop starts.
The remove_duplicates requires an iterable, so the call needs to call iter(lst) if passing a
list. Another option would be to do it inside of the generator, but my decision was to do it
outside.
The list in:
list(remove_duplicates(iter(lst)))
is to force the generator yielding all the values out.
Using groupby
In short:
>>> from itertools import groupby
>>> lst = [1, 1, 1, 2, 3, 3, 5]
>>> map(lambda grpitm: grpitm[0], groupby(lst))
[1, 2, 3, 5]
Step by step:
>>> from itertools import groupby
>>> lst = [1, 1, 1, 2, 3, 3, 5]
>>> list(groupby, lst)
[(1, <itertools._grouper at 0x7f759f976a90>),
(2, <itertools._grouper at 0x7f759f976ad0>),
(3, <itertools._grouper at 0x7f759f976b10>),
(5, <itertools._grouper at 0x7f759f976b50>)]
groupby returns an iterator, which yields tuple (groupname, grouitemiterator).
For our task, we care only about groupname:
>>> map(lambda grpitm: grpitm[0], groupby(lst))
This takes each tuple returned from groupby and pick only the first element from it.
Note, that in Pyhton 3.x you have to put map into list to see the values:
>>> list(map(lambda grpitm: grpitm[0], groupby(lst)))
My turn to try to solve that interesting "Sunday Python puzzle":
>>> def remove_duplicates(lst):
... result = [x for x,n in zip(lst,lst[1:]+[lst[0:1]]) if x != n]
... return result if result or not lst else lst[0:1]
...
>>> lst = [1, 1, 1, 2, 3, 3, 5]
>>> print remove_duplicates(lst)
[1, 2, 3, 5]
>>>
>>> lst = [5, 5]
>>> print remove_duplicates(lst)
[5]
>>>
>>> lst = [5]
>>> print remove_duplicates(lst)
[5]
>>>
>>> lst = []
>>> print remove_duplicates(lst)
[]
This answer has the property of conserving the original list order.
One might say I should have used itertools.izip. And she/he would be probably right. But, hey, its Sunday .... so let's pretend I'm using Python 3.
Try this:
def remove_duplicates(list1):
new_list = list(list1)
indx = 0
while indx+1 < len(new_list):
if new_list[indx] == new_list[indx+1] :
new_list.pop(indx)
else:
indx += 1
return new_list
The way you have it now, if you detect a duplicate as part of a triple, it will remove the middle element, then advance the index to so it's at the last element. Then, the first and the last never get compared. This way only increments the index if it doesn't detect a duplicate.
As I think there is no limit on the number of answers for this "Sunday Python Puzzle", here is my second attempt. As a matter of fact, it should have been the first, since this puzzle is a perfect candidate for reduce. I don't know how I could have missed that for so long. But, hey, it is still Sunday...
>>> def remove_duplicates(lst):
... return reduce(lambda x,n: (x + [n]) if [n] != x[-1:] else x, lst, [])
...
>>> lst = [1, 1, 1, 2, 3, 3, 5]
>>> print remove_duplicates(lst)
[1, 2, 3, 5]
>>>
>>> lst = [5, 5]
>>> print remove_duplicates(lst)
[5]
>>>
>>> lst = [5]
>>> print remove_duplicates(lst)
[5]
>>>
>>> lst = []
>>> print remove_duplicates(lst)
[]

Python: Append values to list without for-loop

How can I append values to a list without using the for-loop?
I want to avoid using the loop in this fragment of code:
count = []
for i in range(0, 6):
print "Adding %d to the list." % i
count.append(i)
The result must be:
count = [0, 1, 2, 3, 4, 5]
I tried different ways, but I can't manage to do it.
Range:
since range returns a list you can simply do
>>> count = range(0,6)
>>> count
[0, 1, 2, 3, 4, 5]
Other ways to avoid loops (docs):
Extend:
>>> count = [1,2,3]
>>> count.extend([4,5,6])
>>> count
[1, 2, 3, 4, 5, 6]
Which is equivalent to count[len(count):len(count)] = [4,5,6],
and functionally the same as count += [4,5,6].
Slice:
>>> count = [1,2,3,4,5,6]
>>> count[2:3] = [7,8,9,10,11,12]
>>> count
[1, 2, 7, 8, 9, 10, 11, 12, 4, 5, 6]
(slice of count from 2 to 3 is replaced by the contents of the iterable to the right)
Use list.extend:
>>> count = [4,5,6]
>>> count.extend([1,2,3])
>>> count
[4, 5, 6, 1, 2, 3]
You could just use the range function:
>>> range(0, 6)
[0, 1, 2, 3, 4, 5]
For an answer without extend...
>>> lst = [1, 2, 3]
>>> lst
[1, 2, 3]
>>> lst += [4, 5, 6]
>>> lst
[1, 2, 3, 4, 5, 6]
List comprehension
>>> g = ['a', 'b', 'c']
>>> h = []
>>> h
[]
>>> [h.append(value) for value in g]
[None, None, None]
>>> h
['a', 'b', 'c']
You can always replace a loop with recursion:
def add_to_list(_list, _items):
if not _items:
return _list
_list.append(_items[0])
return add_to_list(_list, _items[1:])
>>> add_to_list([], range(6))
[0, 1, 2, 3, 4, 5]

List slicing in python every other value

Suppose I have a list x = [1,2,3] and I want to output every other value than the indexed one, is there a list operation I can use?, ie: x[0]=> [2,3], x[1] => [1,3], x[2] =>[1,2] ?
You could use this:
def exclude(l, e):
return [v for i, v in enumerate(l) if i != e]
>>> exclude(range(10), 3)
[0, 1, 2, 4, 5, 6, 7, 8, 9]
You could do it with a slice, but I'd be tempted to try:
a = [1, 2, 3]
b = a[:]
del b[1]
edit
The above does "technically" use a slice operation, which happens to be on list objects in effect a shallow-copy.
What's more flexible and shouldn't have any downsides is to use:
a = [1, 2, 3]
b = list(a)
del b[1]
The list builtin works on any iterable (while the slice operation [:] works on lists or those that are indexable) so that you could even extend it to constructs such as:
>>> a = '123'
>>> b = list(a)
>>> del b[0]
>>> ''.join(b)
'23'
You could use x[:i] + x[i+1:]:
In [8]: x = [1, 2, 3]
In [9]: i = 0
In [10]: x[:i] + x[i+1:]
Out[10]: [2, 3]
Depending on the context, x.pop(i) might also be useful. It modifies the list in place by removing and returning the i-th element. If you don't need the element, del x[i] is also an option.
>>> x = [1, 2, 3]
>>> x[:1] + x[2:]
[1, 3]
my_list = [1, 2, 3]
my_list.remove(my_list[_index_]) #_index_ is the index you want to remove
Such that: my_list.remove(my_list[0]) would yield [2, 3],
my_list.remove(my_list[1]) would yield [0, 3], and
my_list.remove(my_list[2]) would yield [1, 2].
So you want every value except the indexed value:
Where i is the index:
>>> list = [1,2,3,4,5,6,7,8]
>>> [x for x in list if not x == list[i]]
This will give you a list with no instances of the i't element, so e.g.:
>>> i = 2
>>> list = [1,2,3,4,5,6,7,1,2,3,4,5,6,7]
>>> [x for x in list if not x == list[i]]
[1, 2, 4, 5, 6, 7, 1, 2, 4, 5, 6, 7]
note how 3 is not in that list at all.
Every other is
x[::2], start at 0
x[1::2], start at 1

Python list comprehension - simple

I have a list and I want to use a certain function only on those entries of it that fulfills a certain condition - leaving the other entries unmodified.
Example: Say I want to multiply by 2 only those elements who are even.
a_list = [1, 2, 3, 4, 5]
Wanted result:
a_list => [1, 4, 3, 8, 5]
But [elem * 2 for elem in a_list if elem %2 == 0] yields [4, 8] (it acted as a filter in addition).
What is the correct way to go about it?
Use a conditional expression:
[x * 2 if x % 2 == 0 else x
for x in a_list]
(Math geek's note: you can also solve this particular case with
[x * (2 - x % 2) for x in a_list]
but I'd prefer the first option anyway ;)
a_list = [1, 2, 3, 4, 5]
print [elem*2 if elem%2==0 else elem for elem in a_list ]
or, if you have a very long list that you want to modify in place:
a_list = [1, 2, 3, 4, 5]
for i,elem in enumerate(a_list):
if elem%2==0:
a_list[i] = elem*2
so, only the even elements are modified
You could use lambda:
>>> a_list = [1, 2, 3, 4, 5]
>>> f = lambda x: x%2 and x or x*2
>>> a_list = [f(i) for i in a_list]
>>> a_list
[1, 4, 3, 8, 5]
Edit - Thinking about agf's remark I made a 2nd version of my code:
>>> a_list = [1, 2, 3, 4, 5]
>>> f = lambda x: x if x%2 else x*2
>>> a_list = [f(i) for i in a_list]
>>> a_list
[1, 4, 3, 8, 5]

Categories