A better solution than nested if else statements

Diconica

Well-Known Member
Apr 25, 2020
1,135
1,189
Below is a simple example of something I am using at present to get rid of nest if else statements in story flow.
Basically any sort of comparison you can do that has a depth to it you can put into a tuple and do a single comparison
In my case I also have some such as
location and occupancy list
Various MC attributes and values
story name and positions(integer) and so on. This means I can control several sets of stories at the same time all cleanly without a bunch of ugly ass code.
You don't have permission to view the spoiler content. Log in or register now.
The nice parts about the method is you can write your functions/ scripts in other files for each event you want.
Then you just need to add them in when you start the game
You can even add in events in the middle of the game remove events you only want to run once or a select number of times.
You can run all the checks in an update function.

Using multiple dictionaries for each event type allows you to keep easier track of the what is going on.

Another example of removing nested if else
This is a leap year test. Have a look online at the examples they give.
Python:
def leapTest(year):
    p = not year%4
    q = not year%100
    r = not year%400
    if not(q!=r) and p:
        return 1
    return 0
Understanding the underlying aspect or nature of what you are coding can allow you to write better code.

I'm not saying all if else statements are bad. They should be used sparingly.
There is almost always a better solution to a nest if else statement.
If the code doesn't run much then its not big deal.
How many times do I test if the year is a leapyear not that often. Even when updating time and date I only check if the year changes.
Most of my date time functions used the mod (%) to handle updating. Math functions are far faster than doing if checks.

I was planning on finishing up something other stuff before posting this on here.
But I was helping someone else out today. It got me to thinking that maybe I should go ahead and post it.
There are a number of games on this site that could easily make use of it and seriously clean stuff up and boost performance.
I won't list any names. I'm not hear to embarrass anyone.
I figured this solution was relatively simple and easy to follow.
It sure as hell makes for easier code to look at and read. Thus easier to trouble shoot and catch mistakes.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,229
Below is a simple example of something I am using at present to get rid of nest if else statements in story flow.
Or you can use Ren'Py, and see a whole class become four totally basics lines...

Code:
init python:
    config.label_overrides["Home_1_6:00"] = "hello"
    config.label_overrides["Home_1_6:01"] = "bye"
    config.label_overrides["Home_1_6:02"] = "dance"

label hello:
    "Hello"
    return

label bye:
    "Bye"
    return

label dance:
    "Dance"
    return

label hasEvent:
    $ l = "{}_{}_{}".format( Location, Day, gTime )
    if renpy.has_label( l ):
        call expression l
    return

label start:
    $ Location = "Home"
    $ Day = 1
    $ gTime = "5:00"

    call hasEvent
    $ gTime = "6:00"
    call hasEvent
    $ gTime = "6:01"
    call hasEvent
    $ gTime = "6:02"
    call hasEvent
    "Finished"
Four lines that can become three:
Code:
label hasEvent:
    $ l = "{}_{}_{}".format( Location, Day, gTime )
    $ if renpy.has_label( l ): renpy.call( l )
    return
Or even two:
Code:
label hasEvent:
    $ if renpy.has_label( "{}_{}_{}".format( Location, Day, gTime ) ): renpy.call( "{}_{}_{}".format( Location, Day, gTime ) )
    return
 
  • Like
Reactions: Diconica

shark_inna_hat

Active Member
Game Developer
Dec 25, 2018
705
2,765
Asking out of morbid curiosity - why not just make a dict with the time, place, whatever as a key and the function as the value?
Python:
def func1():
    print("Hello")
    return 1
    
def func2():
    print("Bye")
    return 1

def dance():
    print("Dance")
    return 1
    
events = { (6, 'park')  : func1,
           (7, 'work')  : func2,
           (8, 'shower'): dance }

time = 8
place = 'shower'

if (time, place) in events:
    events[(time, place)]()
 
  • Like
Reactions: Diconica

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,229
Asking out of morbid curiosity - why not just make a dict with the time, place, whatever as a key and the function as the value?
It's what he's doing. It's just that he used an object as abstraction for this.

It present an advantage, the code of the game itself is independent of the way he'll treat events. If he decide to finally the event in a dict of dicts (dict[location][day][time]), he would just have to rewrite his event_System class. Everything else, and especially the many event declaration and event test, will stay the same.
It also present the advantage to limits the risk of bugs due to a typo.

But in the same time, his code is broke.

Firstly, and unlike you, he don't test if the key is present in the dictionary. And he'll discover the issue once he'll be effectively using his class in production. Unless he have an event for every single minute of every single day and for every single location, what I really doubt, his class will inevitably lead to a crash.

Secondly, as designed, his class can not be used by more than an unique object. There's people who could decide, by example, to speed up the processing by splitting the events by location:
Code:
init python:
    class Event_System():
        [...]

    setattr( store, "bedroom" ) = Event_System()
    setattr( store, "living room" ) = Even_System()

label whatever:
    $ location = "bedroom"
   
    $ getattr( store, location ).Check_Location_time(Day,gTime)
But doing this they will discover that his class is broke. He defined the dictionary at class level, what mean that its value will be common to every single objects from this class. If one object update Location_Time, this update will apply to every single objects.
You don't have permission to view the spoiler content. Log in or register now.

And obviously, but at this level it's a detail more than anything else, there's his inversion of the convention shared by every languages: Class names start with an uppercase letter, methods/properties/attributes starts with a lowercase, and variables hosting an object should also starts with a lowercase.
It break nothing, but it's really confusing for (and from) someone with experience.
 
  • Like
Reactions: shark_inna_hat

Diconica

Well-Known Member
Apr 25, 2020
1,135
1,189
Asking out of morbid curiosity - why not just make a dict with the time, place, whatever as a key and the function as the value?
Python:
def func1():
    print("Hello")
    return 1
  
def func2():
    print("Bye")
    return 1

def dance():
    print("Dance")
    return 1
  
events = { (6, 'park')  : func1,
           (7, 'work')  : func2,
           (8, 'shower'): dance }

time = 8
place = 'shower'

if (time, place) in events:
    events[(time, place)]()
Truthfully you could.
It allows me to provide a layer of abstraction. As I said this is a simplified version.
Assume you have some events you only want to play one time.
Can you have it remove itself. If so then that code would need to be put in every single one time event. If that's how you do it.
Alternatively, you come up with a flag to indicate it is a one time event and then after the run sequence it deletes it.
Alternatively, you separate it into a separate dictionary and it just deletes any of them after they ran.
Now add on limited time events. One's you want to turn on at some point and turn off again at another point.
We are going to make this a bit more complex though. If it was that simple we could use >< and check two times in the tuple.
What we want is a different set of events to load depending on the day of the week. So we use another dictionary or list to control what is loaded and unloaded each day on the update cycle.

There is a bit of a performance difference also.
It has more to do with the way you are accessing the entry.
The method you used to access the entry search through the entries till it finds a match.
The get function uses a hashtable to locate it.
This would be even slower with a more complex tuple especially if they were similar from the right side.

Basically what you did amounts to an if check for each element starting at the right side. In this case it would stop rather fast if the right side of the tuple was different. If you reversed the entries and time was first or something else followed and there were several similar ones. You would loose even more time.

You don't have permission to view the spoiler content. Log in or register now.
In short you basically ran a for loop and if check across the data were I pulled from a hashtable.
You could do the same thing though.
It doesn't matter much if you only have a few entries. But lets say you have 100 characters and use it for their daily routines, stats and other stuff. It quickly grows.

So I guess as a final answer to your question Abstraction, Flexibility, Expandability, and Performance
I'm lazy writing something once is better than writing it many times, and easier to track and keep clean.
 

shark_inna_hat

Active Member
Game Developer
Dec 25, 2018
705
2,765
The 'in' operator is O(1) for Python dicts, it's as fast as it gets. I just think it's a very complicated way to do something very simple, but you do you.
 
  • Like
Reactions: anne O'nymous

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,229
The 'in' operator is O(1) for Python dicts, it's as fast as it gets. I just think it's a very complicated way to do something very simple, but you do you.
Another difference is that, as you said, in is an operator, while get() is a method. This mean that using the second imply an intermediary step to find the method ; it's atomic, but depending of the circumstance it can make a difference.

So, lets see what method is the best, but by doing it seriously. This mean:
  • Using a code that is equal in structure, to avoid any optimization that could come for the said structure difference ;
  • Using an optimized code, to focus on the effective difference between in and get() ;
  • Relying on "profile", in order to get significant values.
Be noted that I kept his "numbers as key" test, but added it a variation that use "alphanumerical + punctuation" values as keys. Every serious programmer know that comparing numbers, even when they are QuadWords, is way faster than an alphanumerical comparison. Therefore as interesting as it can be, his test wasn't significant enough in regard of the problem it was addressing.
You don't have permission to view the spoiler content. Log in or register now.

The result of the profiling show a slight preference for the use of in.
For 10000 iterations, with numbers as keys, we get a total time of 0.004 second past in "find2", against 0.003 to which you've to add the 0.002 dedicated to get for "find". But this difference being of only 0.001 second, it can perfectly come from the rounding effect (0.0014 second would lead to 0.001, while 0.0016 would lead to 0.002).
It become a little more significant with alphanumerical keys. We get a total time of 0.010 second past in "find2", against 0.009 + 0.003 for "find". What make the use of in a 0.002 second faster than the use of get().
You don't have permission to view the spoiler content. Log in or register now.


What mean that you are doubly right.
Firstly, the use of get() uselessly complicate the code. And I don't just mean its structure/syntax, it also complicate its grasping. Where in imply to get the value and do the test at the same time, his use of get() made him use a superfluous intermediary variable.
Secondly, even if at the scale of a single iteration it's atomic, in is faster than get().

This being said, the difference is so small that it become insignificant at the scale of an adult game. You'll never reach a point where you need to save those 20 nanoseconds by iteration. Even added to other small optimization, you'll never save more than a millisecond every second. And if really what you want to do is so critical that it need this kind of optimization, perhaps start by not using Python, the second slowest script language.

But it doesn't mean that you shouldn't prefer in. Python is a human optimized language. Its goal is to produce code that is easily understandable and feel natural, both when reading it and when writing it. Since what you want is to find "if something is in some other thing", write it that way.
 

Diconica

Well-Known Member
Apr 25, 2020
1,135
1,189
The 'in' operator is O(1) for Python dicts, it's as fast as it gets. I just think it's a very complicated way to do something very simple, but you do you.
I actually tested it before posting that. I even included the performance check you can run it yourself.
The dictionary get is up 2 times faster and depending on what changes you make can be 3 times faster in some cases.
It's usually just on the small example give 50% faster to 100% faster. 1.5 to 2 times the speed.

If you add more variables in the tuple that will cause a greater change.
If you increase the amount of data they scale pretty proportional.
If you increase the number of lookups it scale fairly proportional.

Also not sure why you are all up in arms over me pointing out a performance increase.
As for me doing me; yep always.

You seem like you pretty much made up your mind about classes. Not going to waste my time trying to change your mind.
 

shark_inna_hat

Active Member
Game Developer
Dec 25, 2018
705
2,765
I think the timing difference is from the fact python is dynamic, and the in operator is converted to a dict.__contains__ function call and that extra bit is what makes dict.get() a wee bit faster in this one exotic scenario.

O(1) means that it takes the same(-ish) amount of time to look up a value in a dict of 1 elements and a dict of 100000 elements, tuples are hashable (so you can use them as dict keys), that means the interpreter is not checking individual values in the tuple, it just checks the hash - you can have as many values in the tuple as you like and the performance will not change (much - you know, hash collisions, what fits in L1/L2/L3, cache misses, etc ).

The thing is - these are operations that take virtually no time, compared to loading a image from disk. So what difference does it make if it runs in 0.000001 or 0.0001 seconds if the program wait 2.5 seconds for I/O? Like anne O'nymous said - this is not the place to look for performance improvements.

I have nothing against classes, I just prefer solutions over systems, and the best solution for me... well, might not be the best solution for anybody, hell, I'm using binary arithmetic to check for the state of nudity in my game - I've no idea what I'm doing :D
 

Diconica

Well-Known Member
Apr 25, 2020
1,135
1,189
I think the timing difference is from the fact python is dynamic, and the in operator is converted to a dict.__contains__ function call and that extra bit is what makes dict.get() a wee bit faster in this one exotic scenario.

O(1) means that it takes the same(-ish) amount of time to look up a value in a dict of 1 elements and a dict of 100000 elements, tuples are hashable (so you can use them as dict keys), that means the interpreter is not checking individual values in the tuple, it just checks the hash - you can have as many values in the tuple as you like and the performance will not change (much - you know, hash collisions, what fits in L1/L2/L3, cache misses, etc ).

The thing is - these are operations that take virtually no time, compared to loading a image from disk. So what difference does it make if it runs in 0.000001 or 0.0001 seconds if the program wait 2.5 seconds for I/O? Like anne O'nymous said - this is not the place to look for performance improvements.

I have nothing against classes, I just prefer solutions over systems, and the best solution for me... well, might not be the best solution for anybody, hell, I'm using binary arithmetic to check for the state of nudity in my game - I've no idea what I'm doing :D
Actually, I ran it through about 50 different checks after that one. It averaged out 2x faster.

Tuples aren't hash tables they are an ordered set or effectively an array.
The dictionary is / uses a hashtable.



I know what the fuck O(1) means. It doesn't mean the fastest. See Lesson at bottom of this post.


The source code for get is written more efficiently than the "if in" is.
You don't have to take my word for it you can look it up yourself instead of making stupid arguments.

gets function is effectively (sudo code)
C:
get(key){
   return value[hash(Key)];
}
It won't get an error if empty it will just return null. The array is actually allocated just nothing might be in that spot.

"in" Again just sudo code(hopefully they used a ternary operator at the least and not an if statement.)
C:
in(key){
    if (value[hash(key])
         return 1;
    return 0;
}
get() effectively makes the assumption the programmer knows what the fuck they are doing and just tries to return it.
If it doesn't exist it returns NULL
The in() system has a conditional which is how it returns a bool instead.
You should have picked that up given they perform different functions.

You are sitting here talking like you know shit and acting like I'm the idiot. Yet, you didn't have the sense to look up how it actually works. Hell, you used the damn thing some how you didn't realize the function differently. Or what?

If you don't understand why that conditional in there is bad watch this.

The only thing O(1) means by the way is that it takes a constant time per operation. Not that those operations are as fast or faster than some other function or code.
If a function uses a 100 machine code instructions to perform an operation and that's a constant. Then that would be O(1).
If someone writes another function to do the same task in 10 machine code instruction and that's constant that also would be O(1).

I did make a false assumption myself.
The added time based on tuple size wasn't do to the if statement it was do to the hash function.
You can see for a fact it does cause a time difference by swapping the two lines commenting one out and the other in.
I did that because I was remembering how the if statement in python compares tuples sort of the way people do starting at the LSL working across.
The increase in tuple size affects both of them.
Python:
#! /usr/bin/env python
#dictionary performance test
import time
import random

LT = {} #dictionary
AR = [] #array for building dictionary and running tests
Letters = ["A","B","C","D"]
random.seed()
Time = 0
Size = 5000000
lookup = 10000
def load():
    for y in range(0,4):
        for x in range(0,Size):
            #TT = (Letters[y],x,5,2,1,4,"T")   #comment one of these two lines out then do it again with the other one.
            TT = (Letters[y],x)
            AR.append(TT)
            LT[TT] = random.randint(0,100000)


def find():
    n = 0
    s = len(AR)
    for x in range(0,lookup):
        r = random.randint(0,s)
        rs = LT.get(AR[r])
        if rs:
            n+=1

def find2():
    n = 0
    s = len(AR)
    for x in range(0,lookup):
        r = random.randint(0,s)
        if (AR[r]) in LT:
           n+=1



load()
Last = int(round(time.time() * 1000))
find()
milliseconds = int(round(time.time() * 1000))
t=milliseconds - Last
print("get " + str(t) + " millisec")

last = int(round(time.time() * 1000))
find2()
milliseconds = int(round(time.time() * 1000))
t = milliseconds - Last
print("if in " + str(t) + " millisec")
 
Last edited:

shark_inna_hat

Active Member
Game Developer
Dec 25, 2018
705
2,765
Whoa, man. Chill.
The code in the examples you gave runs faster then the other code you gave in the examples, but in Python if dict.get(key): is still objectively worse than if key in dict:, because the latter is more readable and the performance difference is negligible.

I'm not calling you an idiot, I'm not acting as if you are one - if you got that impression, I'm sorry. That was never my intention.
But I don't think the code in your first post is easy to understand, easy to use or offers any real performance benefits.

I initially asked out of curiosity. I've done a fair bit of programming in python, but I haven't touched ren'py much, and well, the ren'py flavour of python isn't what I'm used to - I wanted to know if there's a prearticular reason for all the complications with something that seemed kind of simple to me - and here we are. Wasting each others time. I'm out.
 

Paz

Active Member
Aug 9, 2016
923
1,521
This thread almost feels like you gents/ladies are planning to run RenPy on low-performance embedded systems :D

Any performance difference simply doesn't matter, because a big chuck of the processing time is taken up from the renderer anyway. You'd decide a bit faster about which image to show and still wait ages (comparatively) for said image to actually show.

(Honestly, for me .get()'s benefit over in is that you can return a default value in the case of a miss, something that would require an if/else in the case of in.)
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,229
gets function is effectively (sudo code)
C:
get(key){
   return value[hash(Key)];
}
It won't get an error if empty it will just return null. The array is actually allocated just nothing might be in that spot.

"in" Again just sudo code(hopefully they used a ternary operator at the least and not an if statement.)
C:
in(key){
    if (value[hash(key])
         return 1;
    return 0;
}
Here, it's you trying to make your truth sound real, nothing more. At no time in need the value, and therefore at to time it is asking for it. This limit in to the search of the key, without even the need to know if it effectively have a value.
The effective pseudo-code for the two look more like this:
Code:
get(key)
found hash(key) ? value[hash(key)] : None

in(key)
found hash(key) ? 1 : 0
And suddenly the result goes against your approach and saying ; as demonstrated by the profiling.

Code:
if something in someDict: someDict[something]()
will always be a bit faster than:
Code:
var = someDict[something]
if var: var()
due to:
  • in being an operator while get is a method.
  • get needing an intermediary assignation to works.
 

Diconica

Well-Known Member
Apr 25, 2020
1,135
1,189
Whoa, man. Chill.
The code in the examples you gave runs faster then the other code you gave in the examples, but in Python if dict.get(key): is still objectively worse than if key in dict:, because the latter is more readable and the performance difference is negligible.

I'm not calling you an idiot, I'm not acting as if you are one - if you got that impression, I'm sorry. That was never my intention.
But I don't think the code in your first post is easy to understand, easy to use or offers any real performance benefits.

I initially asked out of curiosity. I've done a fair bit of programming in python, but I haven't touched ren'py much, and well, the ren'py flavour of python isn't what I'm used to - I wanted to know if there's a prearticular reason for all the complications with something that seemed kind of simple to me - and here we are. Wasting each others time. I'm out.
Sorry, for my part also.

There really wasn't anything to gauge performance in the first example. It would run on the same level as the other does.
The class doesn't really effect the machine code in this case.

I had to drop my son off so you missed the performance Check I added to the end of the last post.
It proves that the tuple size and contents change the performance.
So if you make something more complex it has a more significant impact.
 

Diconica

Well-Known Member
Apr 25, 2020
1,135
1,189
about all topic: lol. just lol.

have you heard about premature optimization?
Yep, sure have it's the excuse some programmers give when they don't know the language well enough to write the best code they could the first time through.

Usually it pans out like this.
Boss: Why isn't this code optimized?
Emp: I didn't want to try optimizing it till I knew it worked.
Boss: What you couldn't optimize while you wrote the code. WTF kept you from doing so.
Emp: but but but
Boss: Never mind I'm sick of your excuses pack your shit your fired.

My personal view is I don't like wasting time rewriting shit I could just written correctly the first time around.
 

Diconica

Well-Known Member
Apr 25, 2020
1,135
1,189
if value is set but ==0 ?
I'm assuming you mean the dictionary value out of the (key, value) pair.
It won't be set to 0 unless you stored that there.
If you didn't store anything there the value is NULL.
If you stored something there then removed the key the value is set back to NULL
 

Armo99

Member
Nov 26, 2019
284
73
just lol again

i'm glad i dont mess with your code rewriting it to make something readable