Что такое Код принятия желаемого за действительное?
Желаемый код – это код, который вызывает функции, которые еще не существуют.
Кодирование желаемого – это стратегия/философия рекурсивного решения больших проблем. Все происходит примерно так:
- Определите функцию верхнего уровня, которая при вызове решит вашу проблему. Пока не пишите тело функции! Только имя и входные параметры.
- Напишите тесты, которые демонстрируют обязанности этой функции.
- Напишите “желаемый” код, чтобы заполнить тело этой функции.
- Теперь у вас должно быть несколько функций, которых еще не существует. Каждая из них-это новая проблема, которую можно решить, приняв желаемое за действительное.
Решение проблемы Желаемое за действительное
На днях я хотел написать программу, чтобы проверить, является ли судоку действительным. Это первые 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. Принудительная фокусировка
Когда вы начинаете с кода самого высокого уровня, вы сразу же накладываете на себя некоторую дисциплину. Цель состоит в том, чтобы пройти тест. Вот и все.