listpython-3.8uniform

(In Python 3.8) filling in lists to mantain them similar in length and average?


I need to allocate some values in 3 individual lists. The values are generated on the fly but all included in the 0-6 range.

The point is that these values should be put in the three lists so that the average of each list does not differ so much from the others. The lists also need to be similar in length.

So the goal would be to progressively fill these lists to maintain, as much as possible, a uniform average value and size for all of them.


Solution

  • As I didn't found any built-in function to do this, I have implemented a code which keeps track of lists length and tries to keep them as close as possible in their average value. You can play with it and improve it to better fit your case.

    class Data:
        def __init__(self):
            """Init the three lists."""
            self.a = []
            self.b = []
            self.c = []
    
        @staticmethod
        def get_average(data: list):
            """Get average value of a list."""
            try:
                return sum(data) / len(data)
            except ZeroDivisionError:
                return 0
    
        def get_shortest(self):
            """Return list with the shortest length."""
            shortest_length = min(len(self.a), len(self.b), len(self.c))
    
            if len(self.a) == shortest_length:
                return self.a
            elif len(self.b) == shortest_length:
                return self.b
            else:
                return self.c
    
        def get_smallest(self):
            """Return list with the smallest average value."""
            smallest_average = min(self.get_average(self.a), self.get_average(self.b), self.get_average(self.c))
    
            if self.get_average(self.a) == smallest_average:
                return self.a
            elif self.get_average(self.b) == smallest_average:
                return self.b
            else:
                return self.c
    
        def get_highest(self):
            """Return list with the highest average value."""
            highest_average = max(self.get_average(self.a), self.get_average(self.b), self.get_average(self.c))
    
            if self.get_average(self.a) == highest_average:
                return self.a
            elif self.get_average(self.b) == highest_average:
                return self.b
            else:
                return self.c
    
        def add_number(self, num):
            """Add number to one of the lists."""
            shortest = self.get_shortest()
            smallest = self.get_smallest()
            highest = self.get_highest()
    
            # Lists must not differ by more than two elements
            if len(smallest) - len(shortest) >= 2 or len(highest) - len(shortest) >= 2:
                shortest.append(num)
            else:
                # Test if the number uppers the smallest average
                initial_avg = self.get_average(smallest)
                smallest.append(number)
                final_avg = self.get_average(smallest)
                if final_avg > initial_avg:
                    return
                else:
                    smallest.pop()
    
                # Test if the number lowers the highest average
                initial_avg = self.get_average(highest)
                highest.append(number)
                final_avg = self.get_average(highest)
                if final_avg < initial_avg:
                    return
                else:
                    highest.pop()
    
                # Last resort
                shortest.append(num)
    
    
    d = Data()
    
    value = input("Add number: ")
    
    while value != 'e':
        try:
            number = int(value)
        except ValueError:
            break
    
        d.add_number(number)
    
        print(f"List a: {d.a}, avg. {d.get_average(d.a)}")
        print(f"List b: {d.b}, avg. {d.get_average(d.b)}")
        print(f"List c: {d.c}, avg. {d.get_average(d.c)}")
    
        value = input("Add number:")