Given a list a, I can slice it by doing:
a[start:end:step]
However this is only linear slicing.
For instance, I would like to select the indexes that are powers of 2.
Unfortunately this does not work:
a[slice(2**x for x in range(len(a))]
Is there a way to avoid loops when non-linear slicing is required?
EDIT:
mainly I need this to modify the list. E.g.
a[*non-linear-slicing*] = [*list-with-new-values*]
You could use list comprehension:
is_power_of_two = lambda num: num != 0 and ((num & (num - 1)) == 0)
print [item for i, item in enumerate(a) if is_power_of_two(i)]
EDIT:
mainly I need this to modify the list. E.g.
a[*non-linear-slicing*] = [*list-with-new-values*]
Great. Then we can use that list of new values to drive this thing. Demo:
>>> a = range(17)
>>> newvals = ['foo', 'bar', 'baz', 'qux']
>>> for i, val in enumerate(newvals):
a[2**i] = val
>>> a
[0, 'foo', 'bar', 3, 'baz', 5, 6, 7, 'qux', 9, 10, 11, 12, 13, 14, 15, 16]
Or if you also have the indexes in a list already, then use zip instead of enumerate:
>>> a = range(17)
>>> indexes = [1, 2, 4, 8]
>>> newvals = ['foo', 'bar', 'baz', 'qux']
>>> for i, val in zip(indexes, newvals):
a[i] = val
>>> a
[0, 'foo', 'bar', 3, 'baz', 5, 6, 7, 'qux', 9, 10, 11, 12, 13, 14, 15, 16]
And since you just mentioned coming from R, maybe you want to use NumPy, which does support such indexing:
>>> import numpy as np
>>> a = np.arange(13)
>>> a[[1, 2, 4, 8]] = [111, 222, 444, 888]
>>> a
array([ 0, 111, 222, 3, 444, 5, 6, 7, 888, 9, 10, 11, 12])
Is this what you are trying to do?
a = [0,1,2,3,4,5,6,7,8,9]
a[1],a[2],a[4],a[8] = [11,12,14,18]
# a = [0,11,12,3,14,5,6,7,18,9]
I know no way to build the a[1],a[2],a[4],a[8] in a generic way as comprehensions or generator will not work as they do not admit the assigment operation.
I am not sure if this is what you want, but you can try this:
# Example list
l = [1,2,3,4,5,6,7,8]
result = list(map(lambda y: l[y], list(filter(lambda x: (x>0 and (x & (x-1) == 0)),list(range(0,len(l)))))))
print(result)
The result is:
[2, 3, 5]
But if I were you, I wouldn't use that because it is barely readable.
Why iterate over the whole list, when you just need the 2 ** ith elements. Which improves complexity dramatically.
a = range(100)
def two_power_slice(lst):
result = []
for i in xrange(len(lst)):
j = 2 ** i
if j > len(lst) - 1:
break
result.append(lst[j])
return result
print two_power_slice(a)
I think you can also use logarithm to avoid using if altogether.
Edit: Improved version, Fixed errors.
def two_power_slice(lst):
if not lst: return lst
result = []
for i in xrange(int(math.ceil(math.log(len(lst), 2)))):
result.append(lst[2 ** i])
return result
Related
I am trying to figure out how to iterate over fn to modify the list again with only even numbers, AFTER appending. The desire result is
[2,4,6,8,10,12,14,16,18,20]
is this possible? I do not want to add fn + sn or lst + lst2. I want to continue over this specific function.
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn,sn):
for i in sn:
if i %2 ==0:
fn.append(i)
even(lst,lst2)
print(lst) # output [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20]
I know there are more efficient and pythonic ways like this example:
example 1:
def even2(fn,sn):
fn[:] = [x for x in fn + sn if x % 2 == 0]
example 2:
def even(fn, sn):
fn.extend(sn)
fn[:] = [x for x in fn if x % 2 == 0]
def even(fn,sn):
for i in sn:
if i %2 ==0:
fn.append(i)
fn = list(filter(lambda x: x %2 == 0, fn))
the last code block is what i tried and failed with
You don't need to rebuild the entire list; just extend it with the desired extra elements.
>>> lst = [1,2,3,4,5,6,7,8,9,10]
>>> lst2 =[11,12,13,14,15,16,17,18,19,20]
>>> def even(fn, sn):
... fn.extend(i for i in sn if i % 2 == 0)
...
>>> even(lst, lst2)
>>> lst
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20]
In the above expression i for i... is a generator expression that allows extend to iterate over all of the i elements without storing them in a separate list first.
You could do it in two steps:
remove odd numbers from fn
extend fn with the even numbers in sn
This function does it without creating any temporary copies or sublists:
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn,sn):
for i,n in enumerate(reversed(fn),1-len(fn)): # delete odds backwards
if n%2: del fn[-i] # so indexes stay stable
fn.extend(n for n in sn if n%2==0) # extend with sn's evens
even(lst,lst2)
print(lst)
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Note that you could extend fn first and then remove the odd numbers, but that would be inefficient:
def even(fn,sn):
fn.extend(sn) # add all sn values
for i,n in enumerate(reversed(fn),1-len(fn)): # delete odds backwards
if n%2: del fn[-i] # so indexes stay stable
A more efficient way would be to iterate through the two lists and assign the result to fn[:]. Doing this starting with fn will ensure that there is no conflict between the iterators and the assignment process:
def even(fn,sn):
fn[:] = (n for L in (fn,sn) for n in L if n%2==0)
The nested comprehension loops allow iterating through the two lists without actually concatenating them so there is no temporary copies or sublists in that solution either.
Does this meet your requirements? Pulling lst into the even() func as a global.
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn, sn):
global lst
lst = [x for x in (fn + sn) if x % 2 == 0]
even(lst,lst2)
print(lst)
I have a list that looks something like this:
lst_A = [32,12,32,55,12,90,32,75]
I want to replace the numbers with their rank. I am using this function to do this:
def obtain_rank(lstC):
sort_data = [(x,i) for i,x in enumerate(lstC)]
sort_data = sorted(sort_data,reverse=True)
result = [0]*len(lstC)
for i,(_,idx) in enumerate(sort_data,1):
result[idx] = i
return result
I am getting the following output while I use this:
[6, 8, 5, 3, 7, 1, 4, 2]
But what I want from this is:
[4, 7, 5, 3, 8, 1, 6, 2]
How can I go about this?
Try this:
import pandas as pd
def obtain_rank(a):
s = pd.Series(a)
return [int(x) for x in s.rank(method='first', ascending=False)]
#[4, 7, 5, 3, 8, 1, 6, 2]
You could use 2 loops:
l = [32,12,32,55,12,90,32,75]
d = list(enumerate(sorted(l, reverse = True), start = 1))
res = []
for i in range(len(l)):
for j in range(len(d)):
if d[j][1] == l[i]:
res.append(d[j][0])
del d[j]
break
print(res)
#[4, 7, 5, 3, 8, 1, 6, 2]
Here you go. In case, you are not already aware, please read https://docs.python.org/3.7/library/collections.html to understand defaultdict and deque
from collections import defaultdict, deque
def obtain_rank(listC):
sorted_list = sorted(listC, reverse=True)
d = defaultdict(deque) # deque are efficient at appending/popping elements at both sides of the sequence.
for i, ele in enumerate(sorted_list):
d[ele].append(i+1)
result = []
for ele in listC:
result.append(d[ele].popleft()) # repeating numbers with lower rank will be the start of the list, therefore popleft
return result
Update: Without using defaultdict and deque
def obtain_rank(listC):
sorted_list = sorted(listC, reverse=True)
d = {}
for i, ele in enumerate(sorted_list):
d[ele] = d.get(ele, []) + [i + 1] # As suggested by Joshua Nixon
result = []
for ele in listC:
result.append(d[ele][0])
del d[ele][0]
return result
I'm trying to write a python program that drops 25% of the lowest values from a list and (return the original unsorted list). For example;
Input : [1,5,6,72,3,4,9,11,3,8]
Output : [5,6,72,4,9,11,8]
I tried to do:
l = [1,5,6,72,3,4,9,11,3,8]
def drop(k):
while len(l)!=0 and k > 0:
k = k - 1
l.sort(reverse = True)
l.pop()
return l
k = math.ceil(len(l) * 0.25)
drop (k)
it returns [72, 11, 9, 8, 6, 5, 4] but is there a way to do it without sorting?.
You don't require to reverse sort and find the smallest element. Use min on list l which returns the smallest value from l and remove using l.remove conveniently.
import math
l = [1,5,6,72,3,4,9,11,3,8]
def drop(k):
while len(l)!=0 and k > 0:
k = k - 1
l.remove(min(l))
return l
k = math.ceil(len(l) * 0.25)
print(drop (k))
# [5, 6, 72, 4, 9, 11, 8]
You could use a heapq and keep popping elements until 25% of the container has been removed. Then, filter the contents of the original list
import heapq, copy
s = [1,5,6,72,3,4,9,11,3,8]
new_s = copy.deepcopy(s)
heapq.heapify(s)
count = 0
last_items = set()
while count/float(len(new_s)) <= 0.25:
last_items.add(heapq.heappop(s))
count += 1
final_s = [i for i in new_s if i not in last_items]
Output:
[5, 6, 72, 4, 9, 11, 8]
There are O(n) solutions to this problem. One of those, introselect, is implemented in numpy in the partition and argpartition functions:
>>> data = [1,5,6,72,3,4,9,11,3,8]
>>>
>>> k = int(round(len(data) / 4))
>>>
>>> import numpy as np
>>> dnp = np.array(data)
>>> drop_them = np.argpartition(dnp, k)[:k]
>>> keep_them = np.ones(dnp.shape, dtype=bool)
>>> keep_them[drop_them] = False
>>> result = dnp[keep_them].tolist()
>>>
>>> result
[5, 6, 72, 4, 9, 11, 3, 8]
Note that this method keeps one of the 3s and drops the other one in order to get the split at exactly k elements.
If instead you want to treat all 3s the same, you could do
>>> boundary = np.argpartition(dnp, k)[k]
>>> result = dnp[dnp > dnp[boundary]]
>>>
>>> result
array([ 5, 6, 72, 4, 9, 11, 8])
One way of doing this is this is very slow especially for longer lists!:
quart_len = int(0.25*len(l))
for i in range(quart_len):
l.remove(min(l))
A much faster way of doing this:
import numpy as np
from math import ceil
l = [1,5,6,72,3,4,9,11,3,8]
sorted_values = np.array(l).argsort()
l_new = [l[i] for i in range(len(l)) if i in sorted_values[int(ceil(len(l)/4.)):]]
Another approach:
l = np.array(l)
l = list(l[l > sorted(l)[len(l)/4]])
l1=[1,5,6,72,3,4,9,11,3,8]
l2=sorted(l1)
ln=round(len(l1)*0.25)
[i for i in l1 if i not in l2[ln+1:]]
Output:
[5, 6, 72, 4, 9, 11, 8]
Is it possible to unpack a list of numbers in to list indices? For example I have a lists with in a list containing numbers like this:
a = [[25,26,1,2,23], [15,16,11,12,10]]
I need to place them in a pattern so i did something like this
newA = []
for lst in a:
new_nums = [lst[4],lst[2],lst[3],lst[0],lst[1]]
newA.append(new_nums)
print (newA) # prints -->[[23, 1, 2, 25, 26], [10, 11, 12, 15, 16]]
so instead of writing new_nums = [lst[4],lst[2],lst[3],lst[0],lst[1]] , i thought of defining a pattern as list called pattern = [4,2,3,0,1] and then unpack these in to those indices of lst to create new order of lst.
Is there a fine way to do this.
Given a list of indices called pattern, you can use a list comprehension like so:
new_lst = [[lst[i] for i in pattern] for lst in a]
operator.itemgetter provides a useful mapping function:
from operator import itemgetter
a = [[25,26,1,2,23], [15,16,11,12,10]]
f = itemgetter(4,2,3,0,1)
print [f(x) for x in a]
[(23, 1, 2, 25, 26), (10, 11, 12, 15, 16)]
Use list(f(x)) if you want list-of-lists instead of list-of-tuples.
If you're not opposed to using numpy, then try something like:
import numpy as np
pattern = [4, 2, 3, 0, 1]
newA = [list(np.array(lst)[pattern]) for lst in a]
Hope it helps.
In pure Python, you can use a list comprehension:
pattern = [4,2,3,0,1]
newA = []
for lst in a:
new_nums = [lst[i] for i in pattern]
newA.append(new_nums)
In numpy, you may use the fancy indexing feature:
>>> [np.array(lst)[pattern].tolist() for lst in a]
[[23, 1, 2, 25, 26], [10, 11, 12, 15, 16]]
it is slower than other, but it is another option. you can sort the list based on your pattern
a = [[25,26,1,2,23], [15,16,11,12,10]]
pattern = [4,2,3,0,1]
[sorted(line,key=lambda x:pattern.index(line.index(x))) for line in a]
[[23, 1, 2, 25, 26], [10, 11, 12, 15, 16]]
consider x = [10,10,20,20,20,30]
How do i form another list_x1 which contains only same values example: list_x1 = [10,10]
and list_x2 =[20,20] and list_x3 =[30] ?
You can use counter.
from collections import Counter
x = [10, 10, 20, 20, 20, 30]
my_counter = Counter(x)
d = {'list_x{0}'.format(key): [key] * my_counter[key] for key in my_counter}
>>> d
{'list_x10': [10, 10], 'list_x20': [20, 20, 20], 'list_x30': [30]}
One of the issues with your request is that you would need to pre-assign variables, which aren't initially know. I've used a dictionary as a container to hold them.
For a list, [10] * 3 results in [10, 10, 10]. So, [k] * my_counter multiplies the unique key value by the number of occurrences.
With itertools.groupby
>>> from itertools import groupby
>>> x = [10,10,20,20,20,30]
>>> [list(g) for k, g in groupby(x)]
[[10, 10], [20, 20, 20], [30]]
Perhaps the best way is #Alexander's idea with collections, but I always find it helpful to look at more 'native' python code to see what's going on. So here's a way to do it:
x = [10,10,20,20,20,30]
def foo(iterable):
for val in iterable:
cnt = iterable.count(val)
iterable = list(filter(lambda x: x != val, iterable))
if cnt:
yield [val]*cnt
for _ in foo(x):
print(_)
Note that the complexity factor is going to be fairly high. Certainly not O(n) because you have to:
Iterate through each of the values in our main for val in iterable
Iterate through each of the values every time we call iterable.count
Iterate through each of the values when we filter() them to prevent duplicates.
Using collections.Counter:
>>> def list_gen(in_list, elem):
... count = collections.Counter(in_list)
... return [elem] * count[elem]
...
>>> a
[1, 2, 3, 2, 3]
>>> list_gen(a, 2)
[2, 2]
This isn't exactly what you're looking for, but this code will generate a list of lists separating the values.
x = [10, 10, 20, 20, 20, 30]
uniques = set(x)
output = []
for unique in uniques:
unique_count = x.count(unique)
temp = []
for i in range(0, unique_count):
temp.append(unique)
output.append(temp)
You can then use list comprehensions on output