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
7.7is in r_list?