Рубрики
Без рубрики

Желаемое кодирование на Python: Философия решения проблем

Что такое желаемый код, как его написать и какие преимущества вы получаете? Прочитав эту статью, вы получите четкое понимание и, возможно, тоже впадете в желаемое кодирование!

Автор оригинала: Andy Brown.

Что такое Код принятия желаемого за действительное?

Желаемый код – это код, который вызывает функции, которые еще не существуют.

Кодирование желаемого – это стратегия/философия рекурсивного решения больших проблем. Все происходит примерно так:

  1. Определите функцию верхнего уровня, которая при вызове решит вашу проблему. Пока не пишите тело функции! Только имя и входные параметры.
  2. Напишите тесты, которые демонстрируют обязанности этой функции.
  3. Напишите “желаемый” код, чтобы заполнить тело этой функции.
  4. Теперь у вас должно быть несколько функций, которых еще не существует. Каждая из них-это новая проблема, которую можно решить, приняв желаемое за действительное.

Решение проблемы Желаемое за действительное

На днях я хотел написать программу, чтобы проверить, является ли судоку действительным. Это первые 2 строки, которые я написал:

def check_sudoku(grid):
    pass

И вот тут-то и появляется “философия”. На данный момент я думаю о своей проблеме в гипер-сфокусированном ключе. Единственное, что мне нужно сделать, это правильно написать эту функцию.

Но что значит “правильно”? Вот тут-то и начинаются тесты.

Следующий код, который я напишу, это:

def test():
    # rows add to 9, columns add to 9, and the 
    # nine 3 x 3 squares all contain digits 1 - 9 
    valid_grid = [[4,3,5,2,6,9,7,8,1],
                  [6,8,2,5,7,1,4,9,3],
                  [1,9,7,8,3,4,5,6,2],
                  [8,2,6,1,9,5,3,4,7],
                  [3,7,4,6,8,2,9,1,5],
                  [9,5,1,7,4,3,6,2,8],
                  [5,1,9,3,2,6,8,7,4],
                  [2,4,8,9,5,7,1,3,6],
                  [7,6,3,4,1,8,2,5,9]]

    invalid_grid = [[1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9],
                    [1,2,3,4,5,6,7,8,9]] 

    assert(check_sudoku(valid_grid))
    assert(not check_sudoku(invalid_grid))

    print "Tests pass."

Затем я нажимаю return около 20 раз и пишу свою любимую строку кода внизу…

test()

Особый Фокус

В этот момент я запускаю код. Я вижу врага, и его имя-AssertionError. Теперь у меня есть особая цель в жизни. Я должен устранить эту ошибку. Эти испытания должны пройти.

Этот психологический хак действительно полезен для меня. Я сел, чтобы написать проверку судоку. Более ранняя версия меня позволила бы этому проекту взорваться. То, что начиналось как шашка, может превратиться в игру. И какой программист напишет такую игру, как Судоку, не написав также ИИ, чтобы играть в нее? И зачем останавливаться на сетках 3 х 3? Почему не N x N? А как насчет N x M? Что бы это вообще значило?!

Но теперь уже нет. Мне нужно сдать тесты.

Прежде чем продолжить, освежите в памяти правила судоку , если вам это нужно.

Написание check_sudoku С желанием

Давайте решим всю проблему сразу. Вот как выглядит мое решение.

def check_sudoku(grid):
    return (check_rows(grid) and 
     check_columns(grid) and 
     check_squares(grid))

def check_rows(grid):
    pass

def check_columns(grid):
    pass

def check_squares(grid):
    pass

И это “кодирование желаемого”. Те функции, на которые я полагаюсь, еще не существуют. Но по сути проблема решена. Теперь мне просто нужно решить три небольшие проблемы. А как вы решаете проблемы? Задумчиво и рекурсивно, конечно!

Желаемое за действительное на всем Пути Вниз

Следующим моим шагом было проверить строки. Лучше сначала добавить тест.

# this code goes into test()
passes_check_rows = [range(1,10) for _ in range(9)]
assert(check_rows(passes_check_rows))
assert(not check_sudoku(passes_check_rows))

А теперь код…

def check_rows(grid):
    for row in grid:
        if not check_row(row):
            return False
    return True

def check_row(grid):
    pass

Я не знаю, как я собираюсь написать функцию check_row , но сейчас мне все равно. На check_columns .

def check_columns(grid):
    return check_rows(transpose(grid))

Этот код достаточно похож на функцию check_rows (для которой я уже написал тест), которую я не собираюсь тестировать check_columns напрямую. Но я проверю транспонировать .

g1_before = [[1,2],
             [3,4],
             [5,6]]
    
g1_after  = [[1,3,5],
             [2,4,6]]
    
assert(transpose(g1_before) == g1_after)

И теперь мне просто нужно пройти этот тест…

def transpose(grid):
    num_rows = len(grid)
    num_cols = len(grid[0])
    new_grid = [[0 for i in range(num_rows)] for j in range(num_cols)]
    for i in range(len(grid)):
        for j in range(len(grid[0])):
            new_grid[j][i] = grid[i][j]
    return new_grid

Теперь проверим квадраты. Чтобы создать тестовый случай, который потерпел бы неудачу, я просто изменил одну из записей в проходящем тестовом случае.

squares_fail = [[4,3,5,2,6,9,7,8,1],
                  [6,8,2,5,7,1,4,9,3],
                  [1,9,7,8,3,4,5,6,2],
                  [8,2,6,1,9,5,3,4,7],
                  [3,7,4,6,8,2,9,1,5],
                  [9,5,1,7,4,3,6,2,8],
                  [5,1,9,3,2,3,8,7,4],
                  [2,4,8,9,5,7,1,3,6],
                  [7,6,3,4,1,8,2,5,9]]
assert(not check_squares(squares_fail))

А потом код.

def check_squares(grid):
    squares = make_squares(grid)
    for square in squares:
        if not check_square(square):
            return False
    return True 

def make_squares(grid):
    pass

def check_square(square):
    pass

Это было исключительно желаемое. Я еще даже не понял, как я хочу представить “квадрат”, но мне нравится думать об этом таким образом, и если это не сработает, изменение будет простым. Теперь давайте выясним, как представить квадрат и зафиксировать это в тесте.

grid_of_zeros   = [[0 for i in range(9)] for j in range(9)]
square_of_zeros = [[0 for i in range(3)] for j in range(3)]
squares = [square_of_zeros for _ in range(9)]
assert(make_squares(grid_of_zeros) == squares)

good_square = [[1,3,5],
               [2,4,6],
               [7,8,9]]

bad_square = [[1,1,5],
              [2,4,6],
              [7,8,9]]

assert(check_square(good_square))
assert(not check_square(bad_square))

Вы знаете, как это делается…

def make_squares(grid):
    squares = []
    for n in range(9):
        i = (n / 3) * 3 # 0,0,0,3,3,3,6,6,6
        j = (n % 3) * 3 # 0,3,6,0,3,6,0,3,6
        square = [row[i : i+3] for row in grid[j : j+3]]
        squares.append(square)
    return squares

def check_square(square):
    nums = [num for row in square for num in row]
    return check_row(nums)

Я представляю квадрат в виде сетки 3×3 (список списков), но затем сглаживаю его в одну строку в check_square , что позволяет мне использовать функцию check_row.

Почти на месте. На check_row .

def check_row(row):
    return (sum(row) == sum(range(10)) and len(set(row)) == 9)

Почему Я Люблю Выдавать Желаемое За Действительное

Кодирование желаемого имеет несколько преимуществ, которые мне действительно нравятся

1. Когнитивная Чистота

Я превратил одну проблему среднего размера в три маленькие, как только написал

def check_sudoku(grid):
    return (check_rows(grid) and 
     check_columns(grid) and 
     check_squares(grid))

Это дало мне свободу думать о каждой из этих небольших проблем в отдельности. Поскольку я уже указал, как каждая из этих функций взаимодействует с более крупной программой, я могу спокойно выбросить это из головы, поскольку я посвящаю все свое внимание написанию check_rows .

2. Удобочитаемость

Самый важный код-это код самого высокого уровня. В данном случае это функция check_sudoku . Написав этот код первым, мне не пришлось ограничивать себя особенностями кода, который я уже написал. Теперь моя функция верхнего уровня чиста, ясна и полностью самодокументирована.

3. Принудительная фокусировка

Когда вы начинаете с кода самого высокого уровня, вы сразу же накладываете на себя некоторую дисциплину. Цель состоит в том, чтобы пройти тест. Вот и все.