I have an if-else statement with over 100 "elses". Any ideas on how to condense this?

545 views Asked by At

I've coded a game in oTree for research purposes and the code below does exactly what I intend. The problem is that some games have over 100 rounds so you can see how the code could get ridiculously long. For people who are going to use this code in the future, it is practically unreadable in its current form. Any ideas on how I could condense this into a simpler if statement?

If you are unfamiliar with oTree, the "participant" signifier is essentially a way to pass data across rounds. The "player" reflects the participant specific to a round number. So each round I need to save their current round return (player.r#) and call the returns of all past rounds (participant.r#-1).

if subsession.round_number == 1:
    participant = player.participant
    participant.r1 = round(100 * asset, 3) + 100
    player.r1 = participant.r1
elif subsession.round_number == 2:
    participant = player.participant
    participant.r2 = round(participant.r1 * asset, 3) + participant.r1
    player.r1 = participant.r1
    player.r2 = participant.r2
elif subsession.round_number == 3:
    participant = player.participant
    participant.r3 = round(participant.r2 * asset, 3) + participant.r2
    player.r1 = participant.r1
    player.r2 = participant.r2
    player.r3 = participant.r3
elif subsession.round_number == 4:
    participant = player.participant
    participant.r4 = round(participant.r3 * asset, 3) + participant.r3
    player.r1 = participant.r1
    player.r2 = participant.r2
    player.r3 = participant.r3
    player.r4 = participant.r4
elif subsession.round_number == 5:
    participant = player.participant
    participant.r5 = round(participant.r4 * asset, 3) + participant.r4
    player.r1 = participant.r1
    player.r2 = participant.r2
    player.r3 = participant.r3
    player.r4 = participant.r4
    player.r5 = participant.r5
3

There are 3 answers

0
martineau On

Here's a function that does the same thing as the code in your if statements. It refers to the last two rounds using Python negative indexing (with a check to handle the first round as a special case). It would only need to be called once for a given round.

def update(player, asset, round_number):
    attrs = tuple(f'r{i}' for i in range(1, round_number+1))  # ('r1', 'r2', 'r3', ...)

    if len(attrs) < 2:
        participant = player.participant
        participant.r1 = round(100 * asset, 3) + 100
        player.r1 = participant.r1
    else:
        participant = player.participant
        prev_particpant = getattr(participant, attrs[-2])
        setattr(participant, attrs[-1], round(prev_particpant*asset, 3) + prev_particpant
        for attr in attrs:
            setattr(player, attr, getattr(participant, attr)

I think it would be better to keep a single list of round information in each player instead of having a separately named instance attribute (i.e. .r1, .r2, .r3, … for each of them. If nothing else it would make copying them all much easier — I also think that is the "X problem" @Dmitry was referring to in his comment.

2
ChalkEating Addict On

I’m know that dictionaries are often used switch statements so that’s some to look into. If you don’t know what switch statements are; they’re similar to if statements that they’re conditional blocks of code. But instead of checking a Boolean they check if a certain variable is a certain case. Python doesn’t have switch statements but dictionaries can be used as a replacement.

1
Dmitry Barsukoff On

You can use getattr and setattr for this and avoid ifs at all.
getattr(self, name) # equal to: self.name
setattr(self, name, value) # equal to: self.name = value

The final code would be something like:

r_n = subsession.round_number
participant = player.participant

setattr(participant, 'r' + str(r_n), round(getattr(participant, 'r' + str(r_n - 1)) * asset, 3) + getattr(participant, 'r' + str(r_n - 1))

for i in range(1, r_n+1):
    player_id = 'r' + str(i) 
    setattr(player, player_id, getattr(participant, 'r' + str(player_id)))

UPD: Don't forget to add condition for r1. Suddenly there will be if that we tried to avoid :D