user47467
user47467

Reputation: 1093

Python in range within list

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

Answers (3)

Rahul Gupta
Rahul Gupta

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

Padraic Cunningham
Padraic Cunningham

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

Hendrik Makait
Hendrik Makait

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

Related Questions