Using Dictionaries in Python in place of Case/Swit

2019-04-24 05:54发布

问题:

I want to randomize a rubik's cube that is initialized as complete (all colors on the correct sides). I have move functions that rotate the cube. I want to randomly pick 50 functions in a row to properly randomize it.

I am doing this project to learn a little bit more about Python, since I mostly do C++ and I see there is no case/switch for Python, so I am trying a dictionary. When I make the dictionary, the code seems to execute for some reason:

def random_cube(self):
    scramble = {    0 : self.up_turn(),
                    1 : self.down_turn(),
                    2 : self.left_turn(),
                    3 : self.right_turn(),
                    4 : self.front_turn(),
                    5 : self.back_turn(),
                    6 : self.up_turn("inverted"),
                    7 : self.down_turn("inverted"),
                    8 : self.left_turn("inverted"),
                    9 : self.right_turn("inverted"),
                    10: self.front_turn("inverted"),
                    11: self.back_turn("inverted")
                }
    for x in range(50):
        i = random.randint(0,11)
        scramble[i]

So when I make this dictionary, it seems to run through and execute all 11 entries for some reason (I think). I can't seem to find a better way, at least more elegant than a long if/elif string of statements.

!EDIT: Implementing both suggestions, the ("inverted") flag for the functions are not being set by either suggestion. For example, calling 1 and 7 both give me down_turn, but the output shows that the flag was not set when it should have been on number 7.

Any ideas?

回答1:

When you define the dict, it's actually calling the functions, and storing the return value in the dictionary. To just have the dictionary store a reference to the functions, you need to drop the trailing parentheses. So something like:

scramble = {  0: self.up_turn,
              1: self.down_turn,
              etc.

Then, at the bottom, call scramble[i]().

This will call the function with no arguments. To handle the case where you pass "inverted" as an argument, you could either define separate functions like up_turn_inverted(), etc., or you could have the dictionary store a 2-ple consisting of the function, and the argument, then call something liks scramble[i][0](scramble[i][1])

Update from suggestion in comments: You could also use lambda expressions to define the functions, particularly the ones that require an argument. This is basically equivalent to defining an up_turn_inverted function, but done in-place as an anonymous function. It would look like this:

6: lambda: self.up_turn("inverted")
7: lambda: self.down_turn("inverted")

etc.



回答2:

I believe this is called "functions as first-class values", implying most importantly that you can pass identifiers referring to functions as parameters to other functions.

When you define your dictionary, the Python interpreter evaluates the functions and stores the values in the dictionary. To put this off until you generate your random numbers, try instead storing references to the functions themselves in your dictionary, by leaving off the parentheses:

def random_cube(self):
    scramble = {    0 : self.up_turn,
                    1 : self.down_turn,
                    2 : self.left_turn,
                    3 : self.right_turn,
                    4 : self.front_turn,
                    5 : self.back_turn,
                    6 : self.up_turn,
                    7 : self.down_turn,
                    8 : self.left_turn,
                    9 : self.right_turn,
                    10: self.front_turn,
                    11: self.back_turn
                }

Then when calling your functions in the for loop you'll have to distinguish between your normal and inverted cases by which parameters you pass:

    for x in range(50):
        i = random.randint(0,11)
        if i <= 5:
            scramble[i]()
        else:
            scramble[i]("inverted")

or more simply:

    for x in range(50):
        i = random.randint(0,11)
        scramble[i]( () if i < 6 else ("inverted"))