How can I refactor this code snippet to make it more efficient?

Question:

I’m trying to make a matrix of actions based off game theory. So if two people meet, they can either both share, or one steal, or both steal, etc.

The outline of what I have looks like this (not the whole matrix, just so you get an idea):

if first_agent.type == "AlwaysShare" and second_agent.type == "AlwaysShare":
    pass
elif first_agent.type == "AlwaysSteal" and second_agent.type == "AlwaysShare":
    pass
elif first_agent.type == "AlwaysShare" and second_agent.type == "AlwaysSteal":
    pass

Clearly this is very inefficient and prone to mistakes. How can I optimise this so I can efficiently manage interactions for the matrix?

Asked By: Cypher236

||

Answers:

You can build a lookup matrix.

def action1():
    print("Doing action 1")

def action2():
    print("Doing action 2")

def action3():
    print("Doing action 3")

def action4():
    print("Doing action 4")


lookup = {
    "AlwaysShare": {
        "AlwaysShare": action1,
        "AlwaysSteal": action2,
    },
    "AlwaysSteal": {
        "AlwaysShare": action3,
        "AlwaysSteal": action4,
    },
}

action = lookup[first_agent.type][second_agent.type]
action()
Answered By: Ted Klein Bergman

Depending on your use case, it might make sense to check for matches vs. mismatches first:

if first_agent.type == second_agent.type:
    action = first_agent.type
    if action == "AlwaysShare":
        do_something()
    elif action == "AlwaysSteal":
        do_something_else()

else:
    whatever_happens_if_mismatch()
Answered By: will-hedges

When I have a situation with multiple options, I like to make a dictionary of functions that handle the different possibilities. The keys are the encoded and normalized inputs. In your case, you can generate a key like this:

def key(first_agent, second_agent):
    key = [first_agent.type, second_agent.type]
    key.sort()
    return tuple(key)

Then your dictionary would look something like this:

def handle_share():
    pass

def handle_one_steal():
    pass

def handle_both_steal():
    pass

# Etc.

action_map = {
    ('AlwaysShare', 'AlwaysShare'): handle_share,
    ('AlwaysShare', 'AlwaysSteal'): handle_one_steal,
    ('AlwaysSteal', 'AlwaysSteal'): handle_both_steal,
}

If you scope the functions carefully (e.g., make them methods or nested functions as necessary), you can fine-tune any side effects you may need them to have.

Now you can replace your if-block with something like:

action_map[key(first_agent, second_agent)]()

Use action_map.get instead if you have a suitable default or no-op in mind, and don’t want to get KeyError for potentially unknown interactions.

The nice thing about doing it this way is that you can easily add new interactions: just implement a function and register it in the dictionary. Want to have a third agent? Make key accept *args instead of a fixed number of agents. Need to define another type of agent? No problem. And so on.

Answered By: Mad Physicist

I’m late to the game, and my approach is similar to @Mad Physicist. The key here is to have a look-up table (matrix). The trick is to always put sharer first in order for look up to succeed:

def share_share(agent1, agent2):
    print(f"Both share: {agent1} and {agent2}")
    
def steal_steal(agent1, agent2):
    print(f"Both steal: {agent1} and {agent2}")

    
def share_steal(sharer, stealer):
    print(f"{sharer=} and {stealer=}")
    

def interact(agent1, agent2):
    matrix = {
        ("AlwaysShare", "AlwaysShare"): share_share,
        ("AlwaysShare", "AlwaysSteal"): share_steal,
        ("AlwaysSteal", "AlwaysSteal"): steal_steal,
    }
    if agent2.type == "AlwaysShare":
        agent1, agent2 = agent2, agent1
    func = matrix[agent1.type, agent2.type]
    func(agent1, agent2)
Answered By: Hai Vu
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.