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.
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.
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]
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.
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.
Partly because of the excitement of the thread, veky provided a polite review and refactoring. The main subject is from here.
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.
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.
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
.
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.
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."
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.
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.
――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