Python List ordering - python

I have a list a = [2, 2, 1, 0, 3, 2, 1, 1, 0, 0].
I want the output to be order = [5, 1, 2, 6, 3, 7, 8].
The output should be a list of index+1 of the elements arranged in non-increasing order of a and wherever there is same value of a it should be arranged in increasing order of index. Here index of max element is 4 so 5 comes first and then there are indexes 0, 1, 5. So 1, 2, 6 comes.
Is there any short pythonic way to achieve this without using loops?
I have tried this with reverse = True
order = [i+1 for (v,i) in sorted(((v, i) for (i, v) in enumerate(a)),reverse=True)].
But this does not keep the indexes as desired wherever there is same value.

I'm guessing that you don't want zeros indices in your result.
The key parameter of sorted() is what you're looking for.
Try this :
a = [2, 2, 1, 0, 3, 2, 1, 1, 0, 0]
order = [ i + 1 for (i, n) in sorted(enumerate(a), key=lambda t: t[1], reverse=True) if n > 0 ]
See the sorted() documentation for more details.

But this does not keep the indexes as desired wherever there is same value.
It does, but you are sorting in reverse! So the indices for 1 - 1, 2 and 6 - are coming ordered 6, 2, 1. Flip the index to sort correctly:
[-i+1 for (v,i) in sorted(((v, -i) for (i, v) in enumerate(a)),reverse=True)]
# => [5, 1, 2, 6, 3, 7, 8, 4, 9, 10]

Related

list of unique elements formed by concatenating permutations of the initial lists

I would like to combine several lists, each lists should be preserved up to a permutation.
Here is an example:
I would like to combine these lists
[[0, 7], [2, 4], [0, 1, 7], [0, 1, 4, 7]]
The output I would like to obtain is e.g. this list
[2, 4, 0, 7, 1]
Or as Sembei Norimaki phrased the task:
the result must be a list of unique elements formed by concatenating permutations of the initial lists.
The solution is not unique, and it could be that there is not always a solution possible
Third time lucky. This is a bit cheesy - it checks every permutation of the source list elements to see which ones are valid:
from itertools import permutations
def check_sublist(sublist, candidate):
# a permutation of sublist must exist within the candidate list
sublist = set(sublist)
# check each len(sublist) portion of candidate
for i in range(1 + len(candidate) - len(sublist)):
if sublist == set(candidate[i : i + len(sublist)]):
return True
return False
def check_list(input_list, candidate):
for sublist in input_list:
if not check_sublist(sublist, candidate):
return False
return True
def find_candidate(input_list):
# flatten input_list and make set of unique values
values = {x for sublist in input_list for x in sublist}
for per in permutations(values):
if check_list(input_list, per):
print(per)
find_candidate([[0, 7], [2, 4], [0, 1, 7], [0, 1, 4, 7]])
# (0, 7, 1, 4, 2)
# (1, 0, 7, 4, 2)
# (1, 7, 0, 4, 2)
# (2, 4, 0, 7, 1)
# (2, 4, 1, 0, 7)
# (2, 4, 1, 7, 0)
# (2, 4, 7, 0, 1)
# (7, 0, 1, 4, 2)
You'd definitely do better applying a knowledge of graph theory and using a graphing library, but that's beyond my wheelhouse at present!

What does arr.sort(key=lambda x: (-d[x], x)) mean?

arr=[2, 5, 2, 6, -1, 9999999, 5, 8, 8, 8]
from collections import defaultdict
def sortfreq(arr,n):
d=defaultdict(lambda:0)
for i in range(n):
d[arr[i]]+=1
print(d)
arr.sort(key=lambda x:(-d[x],x)) #unable to understand this line
print(arr)
return arr
Output: [8, 8, 8, 2, 2, 5, 5, -1, 6, 9999999]
arr.sort(key=lambda x:(-d[x],x))
Here lambda x:(-d[x],x) is a comparator, so first it will check the count of key(x) in the dictionary and -ve sign tells that sort in decreasing order of number of occurrence of a value ( that's why 8 is at starting of the sorted list as it occurs 3 times ).
The 2nd arg in lambda x:(-d[x],x) is x tells if occurrence of two elements is same then place lesser value first and that's why you're seeing 2 before 5.
So in comparison you can provide list of params, but the params will only be in effect when the previous params are equal for two values.
This is quite a weird code.
This sorts the array by decreasing frequency of the first n elements, and increasing value otherwise.
This part creates a counter limited to the first n values:
d=defaultdict(lambda:0)
for i in range(n):
d[arr[i]]+=1
And thus
arr.sort(key=lambda x:(-d[x],x))
sort by decreasing count in the counter, then breaks the ties by sorting on increasing value.
A more compact version of the code would be:
arr=[2, 5, 2, 6, -1, 9999999, 5, 8, 8, 8]
def sortfreq(arr, n):
from collections import Counter
c = Counter(arr[:n])
return sorted(arr, key=lambda x: (-c[x], x))
sortfreq(arr, 5)
output:
[2, 2, -1, 5, 5, 6, 8, 8, 8, 9999999]
# counts among first 5 values
#2, 2, 1, 1, 1, 0, 0, 0, 0, 0
value of the counter:
{2: 2, 5: 1, 6: 1, -1: 1}

How to isolate values from a list in Python?

I have a few lists I'm trying to investigate.I'm stuck at some point:
D = [1, 1, 1, 2, 5, 1, 1, 1, 1, 1, 3, 2, 1, 1]
Let's say this is the list. I need to isolate a new list from the list D.We can say I want to filter those "1"'s; but I couldn't manage it.
If I use "index" method like:
D = [1, 1, 1, 2, 5, 1, 1, 1, 1, 1, 3, 2, 1, 1]
E = []
for i in D:
if not i == 1:
E.append(D.index(i))
print(E)
The output is: [3, 4, 10, 3].What I need is [3, 4, 10, 11].Since values are at D[3] and D[11] are the same, python does not allow me to get the second one.How can I solve this?
Your help is highly appreciated.
Thanks.
You can write this program in many ways. I will try to adapt your example with minimal changes first:
D = [1, 1, 1, 2, 5, 1, 1, 1, 1, 1, 3, 2, 1, 1]
E = []
for i in range(len(D)):
if D[i] != 1:
E.append(i)
print(E)
However, there is also a shorter/simpler one-line solution:
D = [1, 1, 1, 2, 5, 1, 1, 1, 1, 1, 3, 2, 1, 1]
E = [i for i in range(len(D)) if D[i]!=1]
print(E)
You can use a conditional list comprehension to enumerate the values in D and get the index locations of those values that match your condition.
>>> [idx for idx, val in enumerate(D) if val != 1]
[3, 4, 10, 11]
Note that you can also use filter to create a generator of the values at those index locations.
>>> list(filter(lambda val: val != 1, D))
[2, 5, 3, 2]
Using the index function indeed always returns the first occurence. From the documentation:
list.index(x[, start[, end]])
Return zero-based index in the list of the first item whose value is equal to x. Raises a ValueError if there is no such item.
...
We can use list comprehensions and combine this with enumerate to get what you want:
[index for index, value in enumerate(D) if value != 1]
# [3, 4, 10, 11]

Python: choosing indices from array that correspond to elements of specific value

I have an array that looks like this:
x = [1, 1, 2, 3, 3, 2, 2, 1, 2, 3, 2, 3, 2, 1, 2, 1, 1, 2, 1]
I want to write a function that will randomly return some specified number of indices that correspond to a specified number. In other words, if I pass the function the array x, the desired number of indices such as 3, and the target value 1, I would want it to return an array such as:
[0, 7, 13]
Since 0, 7, and 13 are the indices that correspond to 1 in x.
Does anyone know how I might do this efficiently?
You want to use random.sample for this:
import random
def f(arr, target, num):
return random.sample([i for i, x in enumerate(arr) if x == target], k=num)
x = [1, 1, 2, 3, 3, 2, 2, 1, 2, 3, 2, 3, 2, 1, 2, 1, 1, 2, 1]
print(f(x, 1, 3))
Output:
[0, 1, 15]
You can use the sample function from the random module and pass it the list of indices that match the specified value:
x = [1, 1, 2, 3, 3, 2, 2, 1, 2, 3, 2, 3, 2, 1, 2, 1, 1, 2, 1]
from random import sample
def randomIndices(a,count,v):
return sample([i for i,n in enumerate(a) if n==v],count)
print(randomIndices(x,3,1)) # [1,18,15]
Your question asks how to do this efficiently, which depends on how you plan on using this code. As myself and others have pointed out, one way is to use enumerate to filter the list for the indices that correspond to the target value. The downside here is that each time you pick a new target value or request a new sample, you have to once again enumerate the list which is an O(n) operation.
If you plan on taking multiple samples, you may be better off building a dictionary mapping the target value to the indices upfront. Then you can subsequently use this dictionary to draw random samples more efficiently than enumerating. (The magnitude of the savings would grow as x becomes very large).
First build the dictionary using collections.defaultdict:
from collections import defaultdict
d = defaultdict(list)
for i, val in enumerate(x):
d[val].append(i)
print(dict(d))
#{1: [0, 1, 7, 13, 15, 16, 18], 2: [2, 5, 6, 8, 10, 12, 14, 17], 3: [3, 4, 9, 11]}
Now you can use d to draw your samples:
from random import sample
def get_random_sample(d, target_value, size):
return sample(d[target_value], size)
print(get_random_sample(d, target_value=1, size=3))
#[16, 7, 18]
You can do the next:
Get the indices of the items with value equal to 1
Use random.sample to select randomly only a few indices (without repetitions) extracted from the previous step.
Here is one way to do it (n indicates the number of indices to pick):
from random import sample
x = [1, 1, 2, 3, 3, 2, 2, 1, 2, 3, 2, 3, 2, 1, 2, 1, 1, 2, 1]
n = 3
target = 1
indices = frozenset(filter(lambda k: x[k] == target, range(len(x))))
out = sample(indices, min(len(indices), n))
print(out)
Note that the number of returned indices could be lower than n (if the number of 1s in the list is less than n)

Seperate lists based on indices

I have 2 lists:
data = [0, 1, 2, 3, 7, 8, 9, 10]
indices = [1, 1, 0, 0, 0, 2, 1, 0]
I want to append the data to a 2-D array given the indices which correspond to the 2-D array. meaning:
new_list = [[]]*len(set(indices))
Where new_list will results as follows:
new_list = [[2,3,7,10],[0,1,9],[8]]
I am using this code:
for i in range(len(set(indices)):
for j in range(len(indices)):
if indices[j] == i:
new_list[i].append(data[j])
else:
pass
However, I get this:
new_list = [[2, 3, 7, 10, 0, 1, 9, 8], [2, 3, 7, 10, 0, 1, 9, 8], [2, 3, 7, 10, 0, 1, 9, 8]]
I am not sure what mistake I am doing, any help is appreciated!
You can use a dict to map the values to their respective indices, and then use a range to output them in order, so that this will only cost O(n) in time complexity:
d = {}
for i, n in zip(indices, data):
d.setdefault(i, []).append(n)
newlist = [d[i] for i in range(len(d))]
newlist becomes:
[[2, 3, 7, 10], [0, 1, 9], [8]]
You're iterating your indices completely for every value, which is wasteful. You're also multiplying a list of lists, which doesn't do what you expect (it makes a list of many references to the same underlying list). You want to pair up indices and values instead (so you do O(n) work, not O(n**2)), which is what zip was made for, and make your list of empty lists safely (a list of several independent lists):
data = [0, 1, 2, 3, 7, 8, 9, 10]
indices = [1, 1, 0, 0, 0, 2, 1, 0]
# Use max because you really care about the biggest index, not the number of unique indices
# A list comprehension producing [] each time produces a *new* list each time
new_list = [[] for _ in range(max(indices)+1)]
# Iterate each datum and matching index in parallel exactly once
for datum, idx in zip(data, indices):
new_list[idx].append(datum)
To get at this, i zipped the data with its index:
>>>data = [0, 1, 2, 3, 7, 8, 9, 10]
>>>indices = [1, 1, 0, 0, 0, 2, 1, 0]
>>>buff = sorted(list(zip(indices,data)))
>>>print(buff)
[(0, 2), (0, 3), (0, 7), (0, 10), (1, 0), (1, 1), (1, 9), (2, 8)]
Then I used the set of unique indices as a way to determine if the data gets included in a new list. This is done with nested list comprehensions.
>>>new_list = list(list((b[1] for b in buff if b[0]==x)) for x in set(indices))
>>>print(new_list)
[[2, 3, 7, 10], [0, 1, 9], [8]]
I hope this helps.

Categories