assign list values to another list

Viewed 125

I have two lists of the same length, one containing integers and one floats. I want to assign every float number to an integer number - relatively.

By relative, I mean:

  • higher integer numbers are supposed to have a higher chance to receive a low float number
  • lower integer numbers are supposed to have a lower chance to receive a low float number

For assigning it's enough to append the float numbers to a result array, so that integers[index] matches the assigned result[index].

Since I'm talking about chances, I cannot simply order one list descending and one list ascending and match them.

I'm not quite sure how to approach my problem. I'm looking for some functions/modules I haven't heard of - or as always preferred: pure python build in solutions :)

#INDICES:   0    1    2    3    4    5    6    7    8    9  

integers = [5,   5,   2,   4,   3,   1,   4,   5,   2,   3  ]
floats =   [0.1, 0.2, 0.3, 0.3, 0.3, 0.7, 0.6, 0.8, 0.5, 0.5]


#possible result (could be different):
result =   [0.2, 0.3, 0.3, 0.5, 0.7, 0.8, 0.6, 0.1, 0.3, 0.5]

#the result values are assigned to the integers at the same index

As you can see, in the result example, the result[9]==0.5 is assigned to integers[9]==3, while result[6]==0.6 is assigned to integers[6]==4. Which is an exception to the rest of the list, since in general higher floats were assigned to lower integers.

2 Answers

Something like this should do the trick:

import random

def shuffle(ints, floats):

    new_ints = list(sorted(ints))
    new_floats = list(sorted(floats))

    def weight(x, n):
        # implement your own weight system here
        # maybe `n+x` - just make sure that as
        # x increases the weight increases
        return x

    def gen_weights(n):
        return [weight(x,n) for x in range(1,n+1)]

    result = []
    while new_ints:
        i = random.choices(range(len(new_ints)), gen_weights(len(new_ints)))[0]
        result.append((new_ints.pop(0), new_floats.pop(i)))

    new_result = []
    for x in ints:
        for i, (j, f) in enumerate(result):
            if j == x:
                new_result.append(f)
                del result[i]
                break
    return new_result

random.seed(1)

in1 = [5,   5,   2,   4,   3,   1,   4,   5,   2,   3  ]
in2 = [0.1, 0.2, 0.3, 0.3, 0.3, 0.7, 0.6, 0.8, 0.5, 0.5]

result = shuffle(in1, in2)
print(result)

Output:

[0.3, 0.1, 0.8, 0.5, 0.3, 0.3, 0.7, 0.2, 0.6, 0.5]

Essentially, it makes a sorted copy of both lists, then creates a set of weights for each float to determine how likely they are to be chosen. It removes that index from the set of floats, and chooses the first value of the set of ints, and stores it in the results list, deleting the items from the sets of ints and floats.

Finally, the function goes through each item in the original ints list, and finds a corresponding integer value in the results list. It removes that value, and appends it to the output (new_result) list.


If you don't like the linear weights system, replace the return x value in the weight function with any other increasing function. Ensure that higher values of x have higher return values.


Hope this helps :)

Here is a relatively simple approach:

import random

STD_DEV = 2

integers = sorted([5, 5, 2, 4, 3, 1, 4, 5, 2, 3], reverse=True)
floats = sorted([0.1, 0.2, 0.3, 0.3, 0.3, 0.7, 0.6, 0.8, 0.5, 0.5])

results = []
for i in range(0, len(integers)): 
    noisy_index = min(max(int(random.gauss(i, STD_DEV)), 0), len(floats)-1)
    results.append(floats[noisy_index])

print(results)
# [0.1, 0.2, 0.3, 0.2, 0.5, 0.3, 0.5, 0.5, 0.8, 0.7]

It first sorts the integers descending and then the floats ascending, then loops over the integer indices and determines the index of the float value to choose by sampling from a gaussian distribution around that index value. The STD_DEV value will give you a control on how often further indexes are chosen...I chose STD_DEV=2 because it seemed to perform reasonably (i.e. it wasn't likely index=0 would jump to index=9).

Note that this is really a relative weighting approach. The sorted integer index value is what establishes the most likely float to be chosen, but it would behave the same if the first two numbers were 5, 5... or if they were 500, 5....

EDIT: if you want the integer values to have some weight beyond their relative ordering, one approach would be to scale the standard deviation of the guassian distribution based on the value. Here is one approach (of many ways you could do this) that just scales the standard deviation based on the distance of the integer value from the mean:

import random

BASE_STD_DEV = 2

integers = sorted([5, 5, 2, 4, 3, 1, 4, 5, 2, 3], reverse=True)
floats = sorted([0.1, 0.2, 0.3, 0.3, 0.3, 0.7, 0.6, 0.8, 0.5, 0.5])

avg_int = sum(integers)/len(integers)
max_dist = max([abs(i - avg_int) for i in integers])

results = []
for i, integer in enumerate(integers):
    factor = 1 - abs(integer - avg_int) / max_dist
    std_dev = BASE_STD_DEV * factor
    noisy_index = min(max(int(random.gauss(i, std_dev)), 0), len(floats)-1)
    results.append(floats[noisy_index])

print(results)
# [0.1, 0.2, 0.3, 0.2, 0.5, 0.3, 0.5, 0.5, 0.8, 0.7]

You could easily adjust this to perhaps have a minimum standard deviation or minimum/maximum factor, as currently the values on the extremes would have no standard deviation.

Related