Python adding a list to a slice of another list - python

Here's basic problem:
>>> listb = [ 1, 2, 3, 4, 5, 6, 7 ]
>>> slicea = slice(2,5)
>>> listb[slicea]
[3, 4, 5]
>>> lista = listb[slicea]
>>> lista
[3, 4, 5]
>>> listb[slicea] += lista
>>> listb
[1, 2, 3, 4, 5, 3, 4, 5, 6, 7]
listb should be
[1, 2, 6, 8, 10, 6, 7]
But 3, 4, 5 was inserted after 3, 4, 5 not added to it.
tl;dr
I have this code that's not working:
self.lib_tree.item(song)['values'][select_values] = adj_list
self.lib_tree.item(album)['values'][select_values] += adj_list
self.lib_tree.item(artist)['values'][select_values] += adj_list
The full code is this:
def toggle_select(self, song, album, artist):
# 'values' 0=Access, 1=Size, 2=Selected Size, 3=StatTime, 4=StatSize,
# 5=Count, 6=Seconds, 7=SelSize, 8=SelCount, 9=SelSeconds
# Set slice to StatSize, Count, Seconds
total_values = slice(4, 7) # start at index, stop before index
select_values = slice(7, 10) # start at index, stop before index
tags = self.lib_tree.item(song)['tags']
if "songsel" in tags:
# We will toggle off and subtract from selected parent totals
tags.remove("songsel")
self.lib_tree.item(song, tags=(tags))
# Get StatSize, Count and Seconds
adj_list = [element * -1 for element in \
self.lib_tree.item(song)['values'][total_values]]
else:
tags.append("songsel")
self.lib_tree.item(song, tags=(tags))
# Get StatSize, Count and Seconds
adj_list = self.lib_tree.item(song)['values'][total_values] # 1 past
self.lib_tree.item(song)['values'][select_values] = adj_list
self.lib_tree.item(album)['values'][select_values] += adj_list
self.lib_tree.item(artist)['values'][select_values] += adj_list
if self.debug_toggle < 10:
self.debug_toggle += 1
print('artist,album,song:',self.lib_tree.item(artist, 'text'), \
self.lib_tree.item(album, 'text'), \
self.lib_tree.item(song, 'text'))
print('adj_list:',adj_list)
The adj_list has the correct values showing up in debug.
How do I add a list of values to the slice of a list?

The behavior you want is not a feature of any Python built-in type; + with built-in sequences means concatenation, not element-wise addition. But numpy arrays will do what you want, so I'd suggest looking into numpy. Simple example:
>>> import numpy as np
>>> a = np.array([2,3,4], dtype=np.int64)
>>> b = np.array([5,6,7], dtype=np.int64)
>>> a += b
>>> a
array([ 7, 9, 11])
>>> print(a)
[ 7 9 11]
>>> print(a.tolist())
[7, 9, 11]
Note that the output (both repr and str forms) looks a little different from Python lists, but you can convert back to a plain Python list if needed.

Related

Various list concatenation method and their performance

I was working on an algorithm and in that, we are trying to write every line in the code such that it adds up a good performance to the final code.
In one situation we have to add lists (more than two specifically). I know some of the ways to join more than two lists also I have looked upon StackOverflow but none of the answers are giving account on the performance of the method.
Can anyone show, what are the ways we can join more than two lists and their respective performance?
Edit : The size of the list is varying from 2 to 13 (to be specific).
Edit Duplicate : I have been specifically asking for the ways we can add and their respected questions and in duplicate question its limited to only 4 methods
There are multiples ways using which you can join more than two list.
Assuming that we have three list,
a = ['1']
b = ['2']
c = ['3']
Then, for joining two or more lists in python,
1)
You can simply concatenate them,
output = a + b + c
2)
You can do it using list comprehension as well,
res_list = [y for x in [a,b,c] for y in x]
3)
You can do it using extend() as well,
a.extend(b)
a.extend(c)
print(a)
4)
You can do it by using * operator as well,
res = [*a,*b,*c]
For calculating performance, I have used timeit module present in python.
The performance of the following methods are;
4th method < 1st method < 3rd method < 2nd [method on the basis of
time]
That means If you are going to use " * operator " for concatenation of more than two lists then you will get the best performance.
Hope you got what you were looking for.
Edit:: Image showing performance of all the methods (Calculated using timeit)
I did some simple measurements, here are my results:
import timeit
from itertools import chain
a = [*range(1, 10)]
b = [*range(1, 10)]
c = [*range(1, 10)]
tests = ("""output = list(chain(a, b, c))""",
"""output = a + b + c""",
"""output = [*chain(a, b, c)]""",
"""output = a.copy();output.extend(b);output.extend(c);""",
"""output = [*a, *b, *c]""",
"""output = a.copy();output+=b;output+=c;""",
"""output = a.copy();output+=[*b, *c]""",
"""output = a.copy();output += b + c""")
results = sorted((timeit.timeit(stmt=test, number=1, globals=globals()), test) for test in tests)
for i, (t, stmt) in enumerate(results, 1):
print(f'{i}.\t{t}\t{stmt}')
Prints on my machine (AMD 2400G, Python 3.6.7):
1. 6.010000106471125e-07 output = [*a, *b, *c]
2. 7.109999842214165e-07 output = a.copy();output += b + c
3. 7.720000212430023e-07 output = a.copy();output+=b;output+=c;
4. 7.820001428626711e-07 output = a + b + c
5. 1.0520000159885967e-06 output = a.copy();output+=[*b, *c]
6. 1.4030001693754457e-06 output = a.copy();output.extend(b);output.extend(c);
7. 1.4820000160398195e-06 output = [*chain(a, b, c)]
8. 2.525000127207022e-06 output = list(chain(a, b, c))
If you are going to concatenate a variable number of lists together, your input is going to be a list of lists (or some equivalent collection). The performance tests need to take this into account because you are not going to be able to do things like list1+list2+list3.
Here are some test results (1000 repetitions):
option1 += loop 0.00097 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
option2 itertools.chain 0.00138 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
option3 functools.reduce 0.00174 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
option4 comprehension 0.00188 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
option5 extend loop 0.00127 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
option6 deque 0.00180 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
This would indicate that a += loop through the list of lists is the fastest approach
And the source to produce them:
allLists = [ list(range(10)) for _ in range(5) ]
def option1():
result = allLists[0].copy()
for lst in allLists[1:]:
result += lst
return result
from itertools import chain
def option2(): return list(chain(*allLists))
from functools import reduce
def option3():
return list(reduce(lambda a,b:a+b,allLists))
def option4(): return [ e for l in allLists for e in l ]
def option5():
result = allLists[0].copy()
for lst in allLists[1:]:
result.extend(lst)
return result
from collections import deque
def option6():
result = deque()
for lst in allLists:
result.extend(lst)
return list(result)
from timeit import timeit
count = 1000
t = timeit(lambda:option1(), number = count)
print(f"option1 += loop {t:.5f}",option1()[:15])
t = timeit(lambda:option2(), number = count)
print(f"option2 itertools.chain {t:.5f}",option2()[:15])
t = timeit(lambda:option3(), number = count)
print(f"option3 functools.reduce {t:.5f}",option3()[:15])
t = timeit(lambda:option4(), number = count)
print(f"option4 comprehension {t:.5f}",option4()[:15])
t = timeit(lambda:option5(), number = count)
print(f"option5 extend loop {t:.5f}",option5()[:15])
t = timeit(lambda:option6(), number = count)
print(f"option6 deque {t:.5f}",option6()[:15])

Efficient way to iterate python loop until previous position

I have a list a and I need to iterate from position 2 until its previous position 1.
# old index - 0 1 2 3 4
a = [1,2,3,4,5]
# new index - 2,3,4,0,1
# new value - 3,4,5,1,2
cnt = 0
while True:
for i in range(2,len(a)):
print(a[i])
for i in range(len(a)-2-1):
print(a[i])
break
I'm using 2 for loops but I believe there should be a better way to do it.
Let's assume we start with a list a = [1,2,3,4,5].
You can use collections.deque and its method deque.rotate:
from collections import deque
b = deque(a)
b.rotate(-2)
print(b)
deque([3, 4, 5, 1, 2])
Or, if you are happy to use a 3rd party library, you can use NumPy and np.roll:
import numpy as np
c = np.array(a)
c = np.roll(c, -2)
print(c)
array([3, 4, 5, 1, 2])
you can create a new list combining the elements after the particular value and before the particular value, let's say 3 in your case:
a = [1, 2, 3, 4, 5]
piv = a.index(3)
print(a[piv:] + a[:piv])
which gives you [3, 4, 5, 1, 2]
a = [1,2,3,4,5]
position = 2
for item in a[position:] + a[:position]:
print(item)
A base python based solution
a[2::] + a[:2:]
Gives
[3, 4, 5, 1, 2]
A generic version of the same would be
rotate_from = 2
a[rotate_from::] + a[:rotate_from:]
Write a function for rotate list,
In [114]: def rotate(lst, n):
...: return lst[-n:] + lst[:-n]
...:
In [115]: rotate(a,-2)
Out[115]: [3, 4, 5, 1, 2]

Integers being multiplied by the wrong number

I am trying to multiply some of the values of an array by 3, but when I print the array with its new values I see that the numbers have actually been multiplied by 2187 and not 3. Very strange.
for digit in digits:
digits[0] = digits[0]*3
digits[2] = digits[2]*3
digits[4] = digits[4]*3
digits[6] = digits[6]*3
The original numbers in the array are [1,2,3,4,5,6,7]
What they should be after being multiplied is:
[3,2,9,4,15,6,21]
Instead we get [2187,2,6561,4,10935,6,15309].
You are multiplying it everytime and hence the large numbers! The loop multiplies the values each time by 3.
Thus 1 will become 2187 which is 1*3*3*3*3*3*3*3
Either do it all at once (Remove the for loop)
>>> digits = [1,2,3,4,5,6,7]
>>> digits[0] = digits[0]*3
>>> digits[2] = digits[2]*3
>>> digits[4] = digits[4]*3
>>> digits[6] = digits[6]*3
>>> digits
[3, 2, 9, 4, 15, 6, 21]
Or use a condition in the loop
>>> digits = [1,2,3,4,5,6,7]
>>> for i, v in enumerate(digits):
... if(i % 2 == 0):
... digits[i] = v * 3
...
>>> digits
[3, 2, 9, 4, 15, 6, 21]
Here I have used enumerate which will return a tuple of index and values.
Or you can even use a range function with a step of 2
>>> digits = [1,2,3,4,5,6,7]
>>> for i in range(0, len(digits), 2):
... digits[i] = digits[i]*3
...
>>> digits
[3, 2, 9, 4, 15, 6, 21]
Just to demonstrate Python slicing and list comprehensions:
>>> L = [1,2,3,4,5,6,7]
>>> L[::2] = [x*3 for x in L[::2]]
>>> L
[3, 2, 9, 4, 15, 6, 21]
Select for replacement every other character with L[::2] =, then use a list comprehension to triple each of those entries in the list.

Iterate through each value of list in order, starting at random value

Given the following code:
length = 10
numbers = [x for x in range(length)]
start_index = randint(0,length-1)
# now output each value in order from start to start-1 (or end)
# ex. if start = 3 --> output = 3,4,5,6,7,8,9,0,1,2
# ex if start = 9 ---> output = 9,0,1,2,3,4,5,6,7,8
What is the best / simplest / most pythonic / coolest way to iterate over the list and print each value sequentially, beginning at start and wrapping to start-1 or the end if the random value were 0.
Ex. start = 3 then output = 3,4,5,6,7,8,9,1,2
I can think of some ugly ways (try, except IndexError for example) but looking for something better. Thanks!
EDIT: made it clearer that start is the index value to start at
You should use the % (modulo) operator.
length = 10
numbers = [x for x in range(length)]
start = randint(0, length)
for i in range(length):
n = numbers[(i + start) % length]
print(n)
>>> start = randint(0, len(numbers))
>>> start
1
You can use list slicing then iterate over that
>>> numbers[start:] + numbers[:start]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
You can also use the modulus % operator in a list comprehension
>>> [numbers[i%len(numbers)] for i in range(start, start + len(numbers))]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
What is the best / simplest / most pythonic / coolest way ...
You can use collections.deque and its rotate function, like this
>>> from collections import deque
>>> d = deque(numbers)
>>> d.rotate(-9)
>>> d
deque([9, 0, 1, 2, 3, 4, 5, 6, 7, 8])
>>>
>>> d = deque(numbers)
>>> d.rotate(-2)
>>> d
deque([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])
You can try to iterate over the list with simple conditional loops
i = start
while(True):
print i,
if i==numbers[-1]: # If it's the last number
i=numbers[0]
else:
i += 1
if i==start: # One iteration is over
break
This will print 3 4 5 6 7 8 9 0 1 2

Python equivalent of R "split"-function

In R, you could split a vector according to the factors of another vector:
> a <- 1:10
[1] 1 2 3 4 5 6 7 8 9 10
> b <- rep(1:2,5)
[1] 1 2 1 2 1 2 1 2 1 2
> split(a,b)
$`1`
[1] 1 3 5 7 9
$`2`
[1] 2 4 6 8 10
Thus, grouping a list (in terms of python) according to the values of another list (according to the order of the factors).
Is there anything handy in python like that, except from the itertools.groupby approach?
From your example, it looks like each element in b contains the 1-indexed list in which the node will be stored. Python lacks the automatic numeric variables that R seems to have, so we'll return a tuple of lists. If you can do zero-indexed lists, and you only need two lists (i.e., for your R use case, 1 and 2 are the only values, in python they'll be 0 and 1)
>>> a = range(1, 11)
>>> b = [0,1] * 5
>>> split(a, b)
([1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
Then you can use itertools.compress:
def split(x, f):
return list(itertools.compress(x, f)), list(itertools.compress(x, (not i for i in f)))
If you need more general input (multiple numbers), something like the following will return an n-tuple:
def split(x, f):
count = max(f) + 1
return tuple( list(itertools.compress(x, (el == i for el in f))) for i in xrange(count) )
>>> split([1,2,3,4,5,6,7,8,9,10], [0,1,1,0,2,3,4,0,1,2])
([1, 4, 8], [2, 3, 9], [5, 10], [6], [7])
Edit: warning, this a groupby solution, which is not what OP asked for, but it may be of use to someone looking for a less specific way to split the R way in Python.
Here's one way with itertools.
import itertools
# make your sample data
a = range(1,11)
b = zip(*zip(range(len(a)), itertools.cycle((1,2))))[1]
{k: zip(*g)[1] for k, g in itertools.groupby(sorted(zip(b,a)), lambda x: x[0])}
# {1: (1, 3, 5, 7, 9), 2: (2, 4, 6, 8, 10)}
This gives you a dictionary, which is analogous to the named list that you get from R's split.
As a long time R user I was wondering how to do the same thing. It's a very handy function for tabulating vectors. This is what I came up with:
a = [1,2,3,4,5,6,7,8,9,10]
b = [1,2,1,2,1,2,1,2,1,2]
from collections import defaultdict
def split(x, f):
res = defaultdict(list)
for v, k in zip(x, f):
res[k].append(v)
return res
>>> split(a, b)
defaultdict(list, {1: [1, 3, 5, 7, 9], 2: [2, 4, 6, 8, 10]})
You could try:
a = [1,2,3,4,5,6,7,8,9,10]
b = [1,2,1,2,1,2,1,2,1,2]
split_1 = [a[k] for k in (i for i,j in enumerate(b) if j == 1)]
split_2 = [a[k] for k in (i for i,j in enumerate(b) if j == 2)]
results in:
In [22]: split_1
Out[22]: [1, 3, 5, 7, 9]
In [24]: split_2
Out[24]: [2, 4, 6, 8, 10]
To make this generalise you can simply iterate over the unique elements in b:
splits = {}
for index in set(b):
splits[index] = [a[k] for k in (i for i,j in enumerate(b) if j == index)]

Categories