Reduce iterations in a sorting algorithm

Question:

Let’s say I have a list that I want to sort based on subjective criteria, such as the user’s personal preferences. In this case, just using the input() function.

The goal is to minimize the number of iterations and maximize the certainty of the algorithm.

In the example below, I implement a simple algorithm that selects two items at random from the list and asks the user which one they prefer. It then transfers a percentage of the assigned coefficient, from the not chosen item to the item chosen. And it takes away a chosen item percentage from the not chosen item.

How can I minimize the number of iterations without disturbing the certainty of the algorithm?

# Assign a coefficient to each element initialized to 1.0
def ListToDict(list_of_items):
    food_dict = {}
    for item in list_of_items:
        food_dict[item] = 1.0
    return food_dict

# Ask the user to choose between two items
def AskUser(item_name_one, item_name_two):
    print("n [" + item_name_one + "]  or  [" + item_name_two + "] ?")
    user_choice = input("--> ")
    if user_choice == "1" or user_choice == "2":
        return int(user_choice)
    else:
        print("nONLY 1 OR 2 PLEASE!")
        return AskUser(item_name_one, item_name_two)

# The PerformSort() function update the coefficient of each item.
# For each step, the user will be asked to choose between two items.
# If the user chooses the first item, 
#   the coefficient of the first item will be increased by 0.1 times the coefficient of the second item,
#   and the coefficient of the second item will be decreased by 0.1 times the coefficient of the first item.
# The opposite happens if the user chooses the second item.
# When the number_of_iterations parameter is high,
#   the certainty of the result will be higher but the user will have to answer more questions.
def PerformSort(my_dict, number_of_iterations):
    from random import randint
    length_of_dict = len(my_dict)
    
    for i in range(number_of_iterations):
        print("n---- ITERATION " + str(i + 1) + " ----")
        remaining_items = list(my_dict.keys())
        while len(remaining_items) > 1: 
            item_one = remaining_items[randint(0, len(remaining_items) - 1)]
            item_two = remaining_items[randint(0, len(remaining_items) - 1)]
            while item_one == item_two:
                item_two = remaining_items[randint(0, len(remaining_items) - 1)]
            user_choice = AskUser(item_one, item_two)
            if user_choice == 1:
                my_dict[item_one] += 0.1 * my_dict[item_two]
                my_dict[item_two] -= 0.1 * my_dict[item_one]
            elif user_choice == 2:
                my_dict[item_one] -= 0.1 * my_dict[item_two]
                my_dict[item_two] += 0.1 * my_dict[item_one]
            remaining_items.remove(item_one)
            remaining_items.remove(item_two)
    return my_dict

# Get the list of items sorted by their coefficient
def OrderByCoeficient(food_dict):
    list_of_keys = list(food_dict.keys())
    list_of_keys.sort(key=lambda x: food_dict[x], reverse=True)
    return list_of_keys


if __name__ == "__main__":
    items_to_sort = [ "pizza", "cheeseburger", "beef", "soup", "ice cream" ]
    my_dict = ListToDict(items_to_sort)
    my_dict = PerformSort(my_dict, 3)
    print("n Here's your list from highest to lowest:")
    print(OrderByCoeficient(my_dict))
Asked By: Genaro.exe

||

Answers:

I’d suggest just doing a regular sort by turning your AskUser into a comparator function and using functools.cmp_to_key() to turn it into a sorting key function. That way you can take advantage of sort‘s built-in efficiency to minimize the number of comparisons, without having to invent and tune your own sorting algorithm.

import functools

def ask_user_cmp(item1, item2):
    while True:
        print(f"[{item1}](1) or [{item2}](2) ?")
        cmp = input("--> ")
        if cmp == "1":
            return 1
        if cmp == "2":
            return -1
        print("1 or 2, please!")

ask_user_key = functools.cmp_to_key(ask_user_cmp)

items_to_sort = ["pizza", "cheeseburger", "beef", "soup", "ice cream"]
items_to_sort.sort(key=ask_user_key, reverse=True)
print("Here's your list from highest to lowest:")
print(items_to_sort)
[soup](1) or [ice cream](2) ?
--> 2
[beef](1) or [soup](2) ?
--> 2
[cheeseburger](1) or [beef](2) ?
--> 1
[cheeseburger](1) or [soup](2) ?
--> 1
[cheeseburger](1) or [ice cream](2) ?
--> 2
[pizza](1) or [cheeseburger](2) ?
--> 1
[pizza](1) or [ice cream](2) ?
--> 2
Here's your list from highest to lowest:
['ice cream', 'pizza', 'cheeseburger', 'soup', 'beef']

Comparing this with the results of running your code and giving the same answers:

---- ITERATION 1 ----

 [cheeseburger]  or  [soup] ?
--> 1

 [pizza]  or  [beef] ?
--> 1

---- ITERATION 2 ----

 [soup]  or  [beef] ?
--> 1

 [ice cream]  or  [pizza] ?
--> 1

---- ITERATION 3 ----

 [beef]  or  [soup] ?
--> 2

 [ice cream]  or  [pizza] ?
--> 1

 Here's your list from highest to lowest:
['ice cream', 'cheeseburger', 'soup', 'pizza', 'beef']

Note that your code asked me two redundant questions (ice cream vs pizza and beef vs soup were both asked twice), and it never figured out that I like pizza better than either cheeseburgers or soup.

Answered By: Samwise
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.