Reputation: 1093
The concept of my problem is to identify a letter for a random number.
ra_list
holds the random float numbers.
e_list
holds the letter and the range.
The current code, identifies string matching and randomises between B and C letters as they have the same value.
ra_list = [6, 7, 7]
e_list = [(6, 'A'), (7, 'B'), (7, 'C'), (8, 'E')]
test_dict = {}
for key,val in e_list:
test_dict.setdefault(key,[]).append(val)
import random
for i in ra_list:
cate = random.choice(test_dict.get(i,[0]))
if cate != 0:
print i,cate
However, my problem is that I have float numbers in ra_list
and would like to implement ranges - I have very little python experience. How would one manipulate the current code and solve the issue of in range? e_list
will always be in ascending order. For example:
ra_list = [6.25, 7.5, 7.6]
e_list = [(6, 'A'), (7.4, 'B'), (7.4, 'C'), (7.7, 'E')]
output = (6.25, A), (7.5, B or C), (7.6, B or C)
Values from ra_list
less than the first value of e_list
should get A
and values greater than the last value should be E
.
Upvotes: 1
Views: 304
Reputation: 47866
You can do the following:
In [1]: e_list = [(6, 'A'), (7, 'B'), (7, 'C'), (8, 'E')]
In [2]: import random
In [3]: from collections import OrderedDict
In [4]: choices_dict = OrderedDict()
In [5]: for x in e_list:
ra = x[0]
e = x[1]
if ra in choices_dict:
choices_dict[ra].append(e)
else:
choices_dict[ra] = [e]
In [6]: choices_dict
Out [6]: OrderedDict([(6, ['A']), (7, ['B', 'C']), (8, ['E'])])
choices_dict
is a dictionary containing the limit and the letter.
We create a function get_e_value
which will give us the value of e
for a given ra
.
If the value lies inside a range, then a random letter out of letters list from the lower limit is returned from the choices_dict
. Else, the letter for highest range is returned.
In [7]: def get_e_value(my_number):
limits = choices_dict.keys()
limits_count = len(limits)
for x in range(limits_count):
if (my_number <= limits[x]) or (x!= limits_count-1 and my_number<limits[x+1]): # check if number lies between a range
choices = choices_dict[limits[x]]
return random.choice(choices)
last_key = limits[-1] # number is beyond range
return random.choice(choices_dict[last_key]) # return largest range letter
In [8]: ra_list = [1.3, 2.5, 5, 6.3, 7.5, 8.5]
In [9]: final_output = [(x, get_e_value(x)) for x in ra_list]
In [10]: final_output
Out [10]: [(1.3, 'A'), (2.5, 'A'), (5, 'A'), (6.3, 'A'), (7.5, 'C'), (8.5, 'E')]
Upvotes: 0
Reputation: 180391
You could use the bisect module using the first element from each sub tuple as the key to bisect which will give you a running time that is O(N log N)
as opposed to the quadratic:
from bisect import bisect
from random import choice
def pair(l, l2):
# use first element from each tuple as the key
keys = [r[0] for r in l2]
for i in l:
# find the index i would go in keys to keep order
ind = bisect(keys, i)
# make sure we don't wrap araound i.e 0 to -1
# and don't fall of the end
ind = ind - 1 if ind > 0 else ind
yield (i, e_list[ind][1])
output:
In [32]: ra_list = [5.5, 6.25, 7.5, 7.6, 7.7,9.0]
In [33]: e_list = [(6, 'A'), (7.4, 'B'), (7.4, 'C'), (7.7, 'E')]
In [34]: list(pair(ra_list, e_list))
Out[34]: [(5.5, 'A'), (6.25, 'A'), (7.5, 'C'), (7.6, 'C'), (7.7, 'E'), (9.0, 'E')]
If you really want a random choice for repeated values, the logic is exactly the same, you just need to group them in a dict again and check if the length of the value/list for each corresponding key contains more than a single element or not, if it does randomly choose one:
def pair(l, l2):
dct = {}
for a, b in l2:
dct.setdefault(a, []).append(b)
keys = [r[0] for r in l2]
for i in l:
ind = bisect(keys, i)
print(ind,i)
ind = ind - 1 if 0 < ind else ind
val = dct[e_list[ind][0]]
yield ((i, val[0]) if len(val) == 1 else (i, choice(val)))
Output:
In [63]: ra_list = [5.5, 6.25, 7.5, 7.6, 7.7, 7.8, 9.0]
In [64]: e_list = [(6, 'A'), (7.4, 'B'), (7.4, 'C'), (7.7, 'E'), (7.7, "F")]
In [65]: print(list(pair(ra_list, e_list)))
[(5.5, 'A'), (6.25, 'A'), (7.5, 'C'), (7.6, 'C'), (7.7, 'F'), (7.8, 'F'), (9.0, 'E')]
In [66]: print(list(pair(ra_list, e_list)))
[(5.5, 'A'), (6.25, 'A'), (7.5, 'B'), (7.6, 'C'), (7.7, 'F'), (7.8, 'F'), (9.0, 'E')]
In [67]: print(list(pair(ra_list, e_list)))
[(5.5, 'A'), (6.25, 'A'), (7.5, 'B'), (7.6, 'B'), (7.7, 'F'), (7.8, 'F'), (9.0, 'F')]
In [68]: print(list(pair(ra_list, e_list)))
[(5.5, 'A'), (6.25, 'A'), (7.5, 'C'), (7.6, 'B'), (7.7, 'F'), (7.8, 'F'), (9.0, 'E')]
In [69]: print(list(pair(ra_list, e_list)))
[(5.5, 'A'), (6.25, 'A'), (7.5, 'C'), (7.6, 'C'), (7.7, 'E'), (7.8, 'F'), (9.0, 'E')]
Not sure if an exact match appears what should happen, if it does like 7.7
above it will use the corresponding value, if it should be something else then it will still only take constant work so the runtime will stay at N log N
Upvotes: 0
Reputation: 1052
One naive approach would be to create a sorted list of the dictionary keys and find the one that one with the maximum value while being smaller than the input float.
from collections import OrderedDict
import random
ra_list = [5, 6.25, 7.5, 7.6]
e_list = [(6, 'A'), (7.4, 'B'), (7.4, 'C'), (7.7, 'E')]
test_dict = OrderedDict()
for key,val in e_list:
test_dict.setdefault(key,[]).append(val)
key_list = list(test_dict.keys())
min_key = key_list[0]
for i in ra_list:
max_key = min_key
for key in key_list:
if i >= key:
max_key = key
else:
break
cate = random.choice(test_dict.get(max_key))
print( i,cate)
This is more computational complex than converting the input float to an integer, but has the benefit that you can add floats to your (number, letter) pairs.
EDIT 2: Updated original answer according to comments.
Upvotes: 1