I was surprised to receive a nice review when I wrote Python to CheckIO and its explanation

When I looked back at the code written in Python more than two years ago on a programming site called CheckIO, a refactoring proposal was made and I was a little impressed, so I shared it as an explanation. To do.

The answer to the problem and the [thread] extending from it (https://py.checkio.org/mission/open-labyrinth/publications/kumagi/python-3/first/) are only visible to the person who solved the problem. It seems that I will copy it here. All of the following citations are sourced from this issue and thread.

problem

Since the maze is passed in a two-dimensional array of 0 and 1, the path that reaches (10, 10) for the first time from the coordinates of(1, 1)of the maze is N, S. Return as a string consisting of, ʻE, W. 0 represents the floor and 1represents the wall.N stands for top, S stands for bottom, ʻE stands for right, and W stands for left. This figure is all about. There can be multiple routes, but you only have to return one route that will eventually reach the goal.

problem.png

My (not good) answer

I didn't want to type anyway, so I kept the answer short.

python:answer.py:


def checkio(data):
    result = []
    dirs = [[-1, 0, "W"], [+1, 0, "E"], [0, -1, "N"], [0, +1, "S"]]
    def move(path, x, y, field):
        field[y][x] = 1
        if x == 10 and y == 10:
            result.append(path)
        for d in dirs:
            if field[y + d[1]][x + d[0]] == 0:
                move(path + d[2], x + d[0], y + d[1], field)
    move("", 1, 1, data)
    return result[0]

Commentary

A typical recursive fill search. The move function takes the paths that have been walked so far, the search origin, and the field as arguments, and recursively calls the move function to all ** floors ** up, down, left, and right. The move function repaints the path you walked into a "wall" (field [x] [y] = 1) so that you don't turn back the path you came from. When you reach the goal, add it to the result array as a goal candidate. This will put all the directions that can reach the goal into the array, so at the end, return the beginning and finish. Normally, it is customary to search without waste by Dijkstra or A *, but since I wrote it short anyway, it fits in 12 lines.

Review received

Although this answer itself is a bit of a mess, it seems that some people were able to solve the problem that seemed to be annoying at first, and I received the most vote among the answers to this answer.

voted.png

Partly because of the excitement of the thread, veky provided a polite review and refactoring. The main subject is from here.

veky.png

Since the answer is rather long in English, I will explain only the important part of the answer.

The answer he provided is:

python:answer.py:


def checkio(data):
    def move(path="", x=1, y=1):
        data[y][x] = 1
        if x == y == 10:
            yield path
        for x, y, way in (x-1,y,"W"), (x+1,y,"E"), (x,y-1,"N"), (x,y+1,"S"):
            if not data[y][x]:
                yield from move(path + way, x, y)
    return next(move())

It has been reduced to 9 lines. The points that have been improved are as follows.

Use default arguments

Python can specify the argument value when the argument is omitted by adding = and the right side to the formal argument list of the function.

Use of multinomial comparison

In the case of Python, you can compare multiple terms at once. The sentence x == 10 and y == 10 can be mechanically rewritten as x == y == 10.

Use of generator

A generator statement in Python is a variant of a function that uses yield instead of return, roughly speaking. Writing yield in a function interprets the function as a ** generator-generated function ** that returns the arguments given to yield. If you give a value to the function and call it, a ** generator ** will be generated. Each time the next () function is called, the generator will continue to execute inside the function until the next yield. It's common to call the next () function and stop when the StopIteration exception is thrown, so in Python you can easily lick all the elements by giving a generator to the for statement.

python:generator.py:


def fruit_generator(num):
    yield "apple: " + str(num)
    yield "banana: " + str(num)
    yield "orange: " + str(num)

for fruit in fruit_generator(3):
    print(fruit)

:Execution result:


apple: 3
banana: 3
orange: 3

Often, when you repeat something, you want the function to have its own state, but you don't want to pass it from the outside. Typically, it is honest to define a class and have it as a member, but there are many cases where the same thing can be done by expressing it with a generator and it can be written even more neatly and shortly.

Use of tuples

In Python, there are list and tuple that behave like arrays. Both can be accessed by subscripts, but list is represented by [] and tuple is represented by (). list can add / remove elements and rewrite values for subscript references using ʻappend` etc. This is what programmers expect as an array.

python:list.py:


a = []
a.append(2)  # a == [2]
a.append(3)  # a == [2, 3]
a[1] = 10    # a == [10, 3]

tuple cannot be changed such as ʻappend` or rewritten to a subscript reference.

python:tuple.py:


a = (2,3,4)
a[2]  # 4
a.append(5)  # AttributeError: 'tuple' object has no attribute 'append'
a[1] = 3  #TypeError: 'tuple' object does not support item assignment

So when programming, you can express as a clear message, "This is a value you don't expect to be rewritten during execution."

Tuple unpacking

It is common to reassign tuple values to multiple variables. You can assign at once by writing multiple variables with commas in the lvalue of the assignment statement.

python


tup = (1,2,3)
a, b, c = tup
print(a)  # 1
print(b)  # 2
print(c)  # 3

This can also be used at the beginning of a for statement.

python


x = 0
y = 0
for x, y, way in (x-1,y,"W"), (x+1,y,"E"), (x,y-1,"N"), (x,y+1,"S"):
    print("x={x} y={y} way={w}".format(x=x, y=y, w=way))

:Execution result:


x=-1 y=0 way=W
x=1 y=0 way=E
x=0 y=-1 way=N
x=0 y=1 way=S

In my first code, there was an implicit specification that "of the array of length 3, the first is the x coordinate, the second is the y coordinate, and the third is the direction", and the subscript access was originally done according to it. Non-trivial numbers and meaning associations (commonly known as ** magic numbers **) should not be used. It is easier to convey the meaning if you unpack and name each variable.

Use of yield from statement

By using yield from added from Python 3.3, the generator with recursion is expressed concisely. I think that many people are unfamiliar with yield from, so to explain it a little properly, it is a sentence for successfully transferring generators.

python:yield_from.py:


def foo():
    yield 1
    yield 2

def gen():
    for x in range(5):
        yield from foo()  #Use yield from here

d = gen()
for x in d:
    print(x)

:Execution result:


1
2
1
2
1
2
1
2
1
2

Even if you say transfer, it doesn't come to mind, but the same result is returned even if you rewrite the gen () function as follows.

python:without_yield_from.py:


def gen():
    for x in range(5):
        for f in foo():
            yield f

This is a useful notation when you want the results of other (or recursively yourself) generators to be returned instead.

Summary

――If you expose even a delicate code, you may get useful knowledge, so let's expose it more and more. ――CheckIO is a good place to get knowledge from the forum

Recommended Posts

I was surprised to receive a nice review when I wrote Python to CheckIO and its explanation
I wrote AWS Lambda, and I was a little addicted to the default value of Python arguments
A story I was addicted to when inserting from Python to a PostgreSQL table
I wrote a class in Python3 and Java
A note I was addicted to when running Python with Visual Studio Code
A story that I was addicted to when I made SFTP communication with python
What I was addicted to when using Python tornado
I wrote a program quickly to study DI with Python ①
What I was addicted to when migrating Processing users to Python
I want to make input () a nice complement in python
Just try to receive a webhook in ngrok and python
When I tried to scrape using requests in python, I was addicted to SSLError, so a workaround memo
I was surprised at how to save objects with python, which is lean and very energy-saving.
What I was addicted to when introducing ALE to Vim for Python
A note I was addicted to when making a beep on Linux
I wrote a function to load a Git extension script in Python
I tried to make a periodical process with Selenium and Python
A note I was addicted to when creating a table with SQLAlchemy
I wrote a script to extract a web page link in Python
I was addicted to confusing class variables and instance variables in Python
[Python] I wrote a REST API using AWS API Gateway and Lambda.
I wrote a script to help goodnotes5 and Anki work together
I thought a Python class variable was an instance variable and died
[Python] When I tried to make a decompression tool with a zip file I just knew, I was addicted to sys.exit ()
I was surprised when I sent Facerank from iPad via udp and relayed, received, and displayed it in Python!
I wrote a code to convert quaternions to z-y-x Euler angles in Python
When I tried to run Python, it was skipped to the Microsoft Store
I got an error when trying to install Xgboost and its solution
I was addicted to creating a Python venv environment with VS Code
When I screenfetch with xonsh, python x.x comes out and it's sad
I get a UnicodeDecodeError when trying to connect to oracle with python sqlalchemy
The file name was bad in Python and I was addicted to import
I tried to create a sample to access Salesforce using Python and Bottle
I want to make a web application using React and Python flask
I want to build a Python environment
What I was addicted to Python autorun
I tried to make a periodical process with CentOS7, Selenium, Python and Chrome
What I was addicted to when creating a web application in a windows environment
I just want to add scipy, but it's a messy note. Ubuntu, Python 3.
I want to drop a file on tkinter and get its path [Tkinter DnD2]
When I tried to create a virtual environment with Python, it didn't work
[ES Lab] I tried to develop a WEB application with Python and Flask ②
When I connect to a remote Jupyter Server with VScode, it's remote but local
I want to exe and distribute a program that resizes images Python3 + pyinstaller
I set up TensowFlow and was addicted to it, so make a note
A story that I was very convinced when I wrote the code for the Monty Hall problem and calculated the winning percentage
It's been a year since I was self-taught and moved to the web application development department, so I look back
A memo that I wrote a quicksort in Python
I was able to recurse in Python: lambda
A nice nimporter that connects nim and python
I want to create a window in Python
I want to make a game with Python
I wrote "Introduction to Effect Verification" in Python
Python Note: When assigning a value to a string
I wrote a script to upload a WordPress plugin
python memo-"if not A and B" was "if (not A) and B"
What I did when updating from Python 2.6 to 2.7
I want to write to a file with Python
I was soberly addicted to calling awscli from a Python 2.7 script registered in crontab
A story that was convenient when I tried using the python ip address module
[Python] I want to use only index when looping a list with a for statement