ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.10.2023
Просмотров: 434
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
48 Глава 2
результатом выполнения которого была бы числовая последователь- ность (з), у нас не было бы сложностей с воспроизведением требуе- мого узора (д).
Таким образом, большая часть умственной работы, требую- щейся для решения задачи «Равнобедренный треугольник», уже выполнена. Более того, мы знаем точно, какую умственную работу осталось сделать: поиск выражения, которое выдало бы числовую последовательность (з). Таким образом, именно сюда мы должны направить свое внимание. Мы могли бы либо взять завершенный код задачи «Половина квадрата» и экспериментировать до тех пор, пока не получится произвести требуемую числовую после- довательность, либо сделать предположение и составить таблицу наподобие табл. 2.1, чтобы посмотреть, подстегнет ли это наше воображение.
В этот раз давайте поэкспериментируем. В задаче «Половина квадрата» очень хорошо работало вычитание от ряда с большим числом. Поэтому давайте посмотрим, что у нас получится, если мы пропустим row через цикл от 1 до 7 и вычтем row из 8. Результат показан на рис. 2.2 (б). Это не то, что нам нужно. Какой мы можем сделать следующий шаг? В предыдущей задаче было необходимо, чтобы числа шли по убыванию, а не по возрастанию, поэтому мы вычитали переменную цикла из большего числа. В этой же задаче нам нужно сначала идти по увеличению, а потом по уменьшению.
Будет ли правильно вычитать из числа где-то посередине? Если мы заменим выражение 8 — row из предыдущего кода на 4 — row, то также не получим правильной последовательности (рис. 2.2
(в)). Последовательность похожа на правильную, если не смо- треть на знаки минус у последних трех чисел. Что если мы вос- пользуемся функцией выдачи модуля значения, чтобы убрать знак минус? Выражение abs(4 — row) выводит значения на рис. 2.2 (г).
Мы так близки! Я практически уже чувствую вкус победы! Пробле- ма лишь в том, что мы сначала идем по уменьшению, а потом по увеличению, хотя нам нужно идти в обратном направлении. Но как же нам прийти от имеющейся числовой последовательности к требуемой?
Давайте попробуем взглянуть на числа на рис. 2.2 (г) под дру- гим углом. Что если бы мы считали количество пробелов вместо количества символов #, как показано на рис. 2.2 (д)? В случае если мы считаем количество пробелов, колонка (г) представляет собой
правильную числовую последовательность. Чтобы получить пра- вильное количество символов #, представьте, что в каждой строке четыре клетки, а затем вычтите количество пробелов. Если в каж- дом ряду четыре клетки, из которых abs(4 — row) пустые, значит количество клеток с символом # можно вычислить с помощью вы- ражения 4 — abs(4 — row). Это выражение работает, подключите его и проверьте!
Истинные головоломки 49
Рис. 2.2. Различные компоненты, необходимые для решения задачи «Равнобедренный тре-
угольник»
Мы избежали большей части работы через проведение аналогий и решили оставшуюся часть задачи через эксперименты. Это отлич- ный подход, когда новая задача очень похожа на ту, что мы уже ре- шили.
Обработка ввода
Предыдущие программы только производили вывод. Давайте теперь сменим тему и попробуем программы, полностью посвященные об- работке ввода. У всех этих программ будет одно общее ограничение: ввод должен считываться символ за символом, а программа должна обрабатывать каждый символ перед считыванием следующего. Ины- ми словами, программы не будут хранить символы в структурах дан- ных для последующей обработки, но будут обрабатывать символы по мере их ввода.
В первой задаче мы произведем проверку идентификационно- го номера. В современном мире почти у всего есть идентификаци- онный номер, будь то ISBN или номер клиента. Иногда требуется ручной ввод этих номеров, что создает возможность возникнове- ния ошибки. Если ошибочно введенный номер не соответствует ни одному действительному идентификационному номеру, то сис- тема может просто его отбраковать. А что если номер неверен, но при этом действителен? Что если кассир пытается увеличить кре- дит вашего счета за возвращенный товар и вводит при этом номер лицевого счета другого клиента? Ваш кредит будет передан дру- гому клиенту. Чтобы избежать подобной ситуации, были созданы системы обнаружения ошибок в идентификационных номерах.
Эти системы пропускают идентификационный номер через не- кую формулу, что приводит к генерации одной или двух дополни- тельных цифр, которые, в свою очередь, становятся частью рас- ширенного идентификационного номера. При изменении любой цифры исходная часть номера больше не будет соответствовать добавочным цифрам — и система может отбраковать введенный номер.
50 Глава 2
: $ " " # %
Формула Луна — это широко используемая система проверки идентификационных номеров. Используя исходное число, увеличить вдвое значение каждой цифры. Затем сложить значения отдельных цифр (если удвоенное значение имеет две цифры, то сло- жить эти цифры по отдельности). Идентификационный номер проходит проверку, если получившаяся сумма делится на 10.
Напишите программу, которая принимала бы идентификационный номер произволь- ной длины и определяла бы по формуле Луна, действителен ли номер. Программа должна обрабатывать каждый символ перед чтением следующего.
Процесс звучит немного сложно, но пример позволит вам все прояснить. Наша программа будет только проверять идентификаци- онный номер, без генерации проверочной цифры. Давайте пройдем через обе части процесса: вычисление проверочного номера и про- верка результата. Данный процесс представлен на рис. 2.3. В части (а) мы вычисляем проверочную цифру. Исходный идентификационный номер 176248 показан в поле, обведенном пунктирной линией. Каж- дая вторая цифра, начиная с самого правого числа исходного номера
(которая после добавления контрольной цифры становится второй справа), умножается на два. После этого все цифры складываются.
Обратите внимание, что если при умножении на два результат — двуз- начное число, то каждая их цифр этого числа рассматривается по от- дельности. Например, когда при умножении 7 на 2 в результате мы получаем 14, то к контрольной сумме прибавляется 14, но по отдель- ности 1 и 4. В данном случае контрольная сумма 27, значит контроль- ная цифра 3, потому как именно это значение приведет к общей сум- ме 30. Помните, что контрольная сумма последнего числа должна делиться на 10, а следовательно, заканчиваться на 0.
Рис. 2.3. Формула контрольной суммы Луна
Истинные головоломки 51
В части (б) мы проверяем число 1762483, которое теперь содер- жит проверочную цифру. Такой процесс мы будем пользоваться для решения этой задачи. Как и прежде, мы удваиваем каждую вторую цифру, начиная с цифры слева от проверочной, и суммируем значе- ния всех цифр для определения контрольной суммы. Так как кон- трольная сумма делится на 10, число проходит проверку.
Разбиение проблемы на части
Программа, которая решит эту задачу, содержит несколько слож- ностей, с которыми нужно разобраться. Одна из них — умножение цифр на 2, что непросто, так как мы определяем то, какие из цифр нужно умножать, начиная справа. Помните, что мы не будем считы- вать и сохранять все цифры для последующей обработки. Мы будем обрабатывать их по мере ввода. Проблема в том, что мы будем по- лучать цифры слева направо, но чтобы знать, какие из цифр умно- жать на 2, нужно было бы получать их справа налево. Мы знали бы, которые из цифр умножать, если бы знали количество цифр в иден- тификационном номере, но мы этого не знаем, так как, по условию задачи, идентификационный номер имеет произвольную длину. Еще одна сложность в том, что если умноженное на 2 число больше 10, то мы должны обрабатывать цифры, составляющее получившееся число, по отдельности. Нам также нужно будет определить, когда идентификационный номер считан полностью. Наконец, нам нуж- но будет придумать, как считывать номер по одной цифре. Иными словами, пользователь будет вводить одно длинное число, но мы бу- дем считывать его так, будто бы пользователь вводил цифры, как от- дельные числа.
Так как нам всегда необходим план, следует создать список этих подзадач и разрешить их по отдельности: yзнать, какие цифры умножить на 2;
y
обрабатывать число 10 и большие числа по отдельным циф- рам;
yзнать, что мы достигли конца номера;
yсчитывать каждую цифру по отдельности.
Для решения задач мы будем работать над отдельными частями прежде, чем писать окончательное решение. Таким образом, нет ни- какой необходимости работать над этими подзадачами в какой-то конкретной последовательности. Начните с той, которая выглядит проще остальных, или, если вы хотите побороться, начните с той, что кажется самой сложной. Или же просто выберите ту, что кажет- ся самой интересной.
Давайте начнем с решения проблемы с числом 10 и больше. Это как раз та ситуация, когда ограничения задачи упрощают, а не услож- няют ее решение. Вычисление суммы цифр целого числа произволь- ной длины может быть сложной задачей самой по себе. Но каков здесь диапазон возможных значений? Если мы начнем с отдельных
1 2 3 4 5 6 7 8 9 ... 34
52 Глава 2
цифр 0–9 и умножим их на 2, то максимальное значение, которое мы можем получить — 18. Таким образом, возможности только две. Если удвоенное значение — однозначное число, то нам не нужно ничего больше делать. Если же удвоенное значение равно 10 и больше, то оно должно быть в диапазоне 10–18, таким образом, первая цифра всегда 1. Давайте поставим небольшой программный эксперимент, чтобы подтвердить этот подход:
int digit;
cout << "Enter a single digit number, 0-9: ";
cin >> digit;
X int doubledDigit = digit * 2;
int sum;
o if (doubledDigit >= 10) sum = Z1 + doubledDigit % 10;
else sum = doubledDigit;
[ cout << "Sum of digits in doubled number: " << sum << "\n";
ПРИМЕЧАНИЕ.
Оператор % называется оператором деления с остат-
ком. Для положительных целых чисел он вернет остаток от целочисленного
деления. Например, результатом операции 12 % 10 будет 2, так как оста-
ток деления 12 на 10 как раз 2.
Это прямолинейный код: программа считывает цифру, умножа- ет ее на 2
X
, суммирует цифры удвоенного числа o
и выводит сум- му
[
. Однако вся суть эксперимента заключается в подсчете сум- мы удвоенного числа, больше, чем 10
Z
. Как и в случае с подсчетом количества символов #, необходимых для конкретной строки в за- дачах с фигурами, выделение этого вычисления в короткую про- грамму упростит экспериментирование. Даже если мы не получим правильную формулу с первого раза, мы все равно уверены, что вскоре ее найдем.
Прежде чем мы вычеркнем эту подзадачу из нашего списка, да- вайте преобразуем код в короткую функцию, которую сможем ис- пользовать для упрощения последующих листингов:
int doubleDigitValue(int digit) {
int doubledDigit = digit * 2;
int sum;
if (doubledDigit >= 10) sum = 1 + doubledDigit % 10;
else sum = doubledDigit;
return sum;
}
Теперь давайте поработаем над считыванием отдельных цифр идентификационного номера. Мы могли бы попробовать разрешить любую другую подзадачу при желании, но я думаю, что именно эта– хороший выбор, так как она позволит нам естественным образом ввести идентификационный номер во время тестирования других частей нашей задачи.
Если мы считываем идентификационный номер в виде число- вых данных (например, int), то получим лишь одно длинное чис-
Истинные головоломки 53
ло, а впереди будет много работы. Кроме того, существует огра- ничение на длину целочисленного значения, которое мы можем считать, а по условию задачи идентификационный номер имеет произвольную длину. Таким образом, нам придется считывать сим- вол за символом. Это значит, мы должны убедиться, что знаем, как считать символ, представляющий цифру, и привести его целочис- ленному типу, над которым мы сможем совершать математические действия. Чтобы увидеть, что произойдет, если мы возьмем сим- вольное значение и используем его в целочисленном выражении напрямую, взгляните на следующий листинг, который включает также образец программного вывода.
char digit;
cout << "Enter a one-digit number: ";
X digit = cin.get();
int sum = digit;
cout << "Is the sum of digits " << sum << "? \n";
Y Enter a one-digit number: 7
Is the sum of digits 55?
Обратите внимание, что мы используем метод get
X
, так как базовый оператор извлечения (как в cin >> digit) пропускает про- белы. В данном случае это не проблема, но, как вы увидите далее, это вызовет трудности впоследствии. Вы также видите проблему в примере ввода и вывода
Y
. Компьютеры работают с числовыми данными, поэтому отдельные символы представлены в виде цело- численных кодов символов. В разных операционных системах мо- гут использоваться разные системы кодировки символов, но в этой книге мы сосредоточим внимание на общеупотребительной систе- ме ASCII. В этой системе символу 7 присвоено значение кода сим- вола 55, поэтому, когда мы обрабатываем этот символ как целое число, то получаем 55. Нам нужен механизм для преобразования
символа 7 в целое число 7.
: " & ' # &
Напишите программу, которая считывает символ, введенный пользователем и представ- ляющий цифру от 0 до 9, а затем выводит целое число для демонстрации результата.
В задачах с геометрическими фигурами из предыдущего разде- ла у нас уже были переменные одного диапазона значений, которые мы хотели преобразовать в переменные с другим диапазоном. Мы составляли таблицу с колонками для текущих и требуемых значений, а затем выясняли разницу между значениями этих двух групп. Это аналогичная задача, а значит, мы вновь можем воспользоваться иде- ей с таблицей, как показано в табл. 2.2.