Задачи "на технику"

Бывают задачи, в которых вроде все понятно что делать, кажется, не надо придумывать алгоритм, нет проблем со временем работы — но тем не менее задача кажется сложной, и непонятно, с какого конца к ней подступиться (это, конечно, не строгое определение). Такие задачи принято называть задачами "на технику", часто это какие-нибудь задачи на обработку текста и т.п.

Сложно придумать какие-нибудь универсальные рекомендации к таким задачам, но есть пара полезных соображений.

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

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

Например, пусть вам надо обработать текст, в котором надо как-то особенно обрабатывать слова и предложения. Тогда напишите сначала код, который будет только разбивать входной текст на предложения; в результате у вас получится массив предложений. Потом напишите код, который будет брать одно предложение и разбивать его на слова. В результате у вас получится двумерный массив слов: каждая строка массива — это слова одного предложения. И уже потом пишите то, что надо по условию задачи.

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

Чуть более подробный пример (я уже где-то про это писал) — пусть вам дан текст, надо нормализовать заглавные буквы (после точек сделать заглавные буквы, а в остальных местах — маленькие), убрать двойные пробелы, добавить пробелы после знаков препинания, и вывести, перенося слова так, чтобы каждая строка была не длиннее 80 символов — то не надо тоже бросаться все делать сразу. Напишите программу, которая сначала нормализует заглавные буквы. Потом уберет двойные пробелы. Потом добавит пробелы где надо (может быть, это будет удобно совместить с предыдущим или со следующим пунктом, тут можно по-разному организовать код, это уже зависит от конкретных деталей задачи). Потом разобьет текст на слова. Потом уже выведет текст разбитым на строки так, как надо.

Еще пример — пусть вам надо вычислить значение многочлена, т.е. дана строка вида 2*x^2+3*x+5, и значение x, надо вычислить значение многочлена. Это не так сложно, как кажется, надо просто сначала разбить строку-многочлен на слагаемые, потом в каждом слагаемом отдельно выделить коэффициент и степень, и дальше уже все просто считается.

Вообще, общий принцип такой: старайтесь писать код так, чтобы в каждом месте программы вам надо было держать в голове как можно меньше информации. И в плане тех данных и переменных, которые вы используете, и в плане тех требований, которые вам надо выполнять. Если вы понимаете, что одна переменная вам нужна в одном месте кода, а другая — в другом — то подумайте, нельзя ли разнести код так, чтобы в первом месте вам не надо было думать про вторую переменную, и т.п.


Мелкий комментарий на эту же тему, хотя и не относящийся явно к задачам на технику. Пусть, например, вам в программе надо считать суммы элементов в строках двумерного массива:

ПаскальПитон
var a:array[1..100,1..100] of integer;
    s:integer;
...
for i:=1 to n do begin
    s:=0;
    for j:=1 to m do
        s:=s+a[i,j];
    ... что-то делаем с s
    ... делаем что-то еще
end;
...
# a -- двумерный массив
for i in range(n):
    s = 0
    for j in range(m):
        s += a[i][j]
    ... что-то делаем с s
    ... делаем что-то еще
...

Можно писать так, как написано выше. А можно так — и многие из вас именно так и пишут:

ПаскальПитон
var a:array[1..100,1..100] of integer;
    s:integer;
...
s:=0;
for i:=1 to n do begin
    for j:=1 to m do
        s:=s+a[i,j];
    ... что-то делаем с s
    ... делаем что-то еще
    s:=0;
end;
...
# a -- двумерный массив
s = 0
for i in range(n):
    for j in range(m):
        s += a[i][j]
    ... что-то делаем с s
    ... делаем что-то еще
    s = 0
...

Отличие в том, что в первом коде вы зануляете переменную s перед использованием, а во втором коде — после.

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

Элементарный пример, как может что-то пойти не так во втором варианте — пусть вы где-то внутри цикла по i решили написать continue. И все, у вас пропустилось присваивание s=0.