1

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.

9
  • You want the code to output 'B or C', or it doesn't matter which is it? Commented Sep 24, 2015 at 14:48
  • It doesn't matter if it's B or C as they have the same values. The code I currently have will produce either B or C, and if E was 7, it'd iterate through B,C or E too. It's the range I'm struggling with. Commented Sep 24, 2015 at 14:51
  • 1
    I am not sure what do you mean by range here, and what are you trying to accomplish, sorry. Commented Sep 24, 2015 at 14:53
  • 1
    can the values in ra_list be less than or greater than those in e_list? Also e_list seems to be ascending not descending Commented Sep 24, 2015 at 15:12
  • 1
    What should happen if 7.7 is in r_list? Commented Sep 24, 2015 at 16:52

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

12 Comments

it seems to be producing 0 for each element in the list?
That is weird, my output matches the op's requirement: 6.25 A 7.5 B 7.6 B. Which version of python do you use?
It does not print anything for 5, this is correct. However, that state is undefined based on my understanding of the op's problem. However, if the requirement is to match 5 in this case to the smallest value in the dict, this can easily be done by changing max_key = None to max_key = sorted(test_dict.keys())[0]
yep, sort one with sorted(test_dict) or better again use an OrderedDict as the data is already ordered
Well, you are using tuples in ra_list now instead of floats. You need to use the tuples first value to create the mapping. You could say ra_key = i[0] as first statement in for i in ra_list and then check for if ra_key >= key:
|
0

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

Comments

0

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')]

2 Comments

7.5 should be B or C not E
@PadraicCunningham Yep! You are right. Updated the ans.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.