Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Полезное программирование
.pdf
whY:=y0-Round((-1-sc.BegY)/MYpix);
Canvas.TextOut(x0-dx,whY-dy,FloatToStrF(-1,ffGeneral,3,1));
Canvas.MoveTo(x0-5, whY); Canvas.LineTo(x0+5, whY);
ɪɚɡɦɟɬɤɚɩɨɝɨɪɢɡɨɧɬɚɥɢ
pinadva:=Round( sc.begX/(pi/2));
Canvas.Font.Name:=’Symbol’;
Canvas.Font.Size:=14;
repeat
whX:=x0 + Round((-sc.begX+pinadva*pi/2)/MXpix);
Canvas.MoveTo( whX, y0-5);
Canvas.LineTo( whX, y0+5);
if (pinadva mod 2) = 0 then begin
if pinadva = 0 then stroka:=’0’ else
if pinadva = 2 then stroka:=#112
else stroka:=IntToStr(pinadva div 2) + #112;
end
else begin
if pinadva = 1
then stroka:=#112’/2’
else stroka:=IntToStr(pinadva) + ‘/2’#112;
end;
Canvas.TextOut( whX-5, y0+5, stroka);
pinadva:=pinadva + 1;
until (whX >= x0 + rect.Right);
end;
51Совсем всё. Финиш
А вот так выглядит результат выполнения процедуры, с кривой, осями, разметкой, подписями и прямоугольником.
Можно ли улучшить эту процедуру? Можно, конечно. Самое очевидное —
сделать её действительно универсальной. В первую очередь это касается разметки по оси ординат (оси Y). Даже при том, что мы подписали не ней всего три

52 Глава 2. Не совсем учебная. Графики
значения, программный код явно выглядит монотонным, однообразным и с
бросающимися в глаза повторениями. Их надо организовать в цикл или вынести
в отдельную процедуру. Некоторых смущают выражения типа ‚/2‘#112. Меня самого они до сих пор смущают, как ни странно, но привык. Что важнее, обратите
внимание — при рисовании графика мы движемся по геометрическим координатам, с шагом единица, и рассчитываем для них соответствующие физические
координаты. При нанесении разметки мы перебираем физические, с шагом π/2
и рассчитываем для них геометрические. Есть ли в этом какой-то скрытый смысл
и является ли это универсальным принципом?
Теперь ещё раз подумаем о том, о чём мы уже много раз думали. Зачем мы это
пишем? Для кого мы это пишем? Если только для себя, то, для начала, сойдёт и
так. Если для других, то любой пользователь немедленно захочет нарисовать
вместо синуса косинус, а то и тангенс — тангенс рисовать труднее, поверьте.
Если мы пишем для себя, то можем влезть в исходный текст и руками поменять
синус на косинус, хотя нам должно быть очень стыдно — какой из нас после этого программист? Если мы пишем для адекватного пользователя, об этом и речи
быть не может. Адекватный пользователь — такой заказчик, который знает, чего
он хочет от программы, может это объяснить словами, не прибегая к жестам рук,
и понимает, где кончается его компетенция.
// Рассказ об Адекватном пользователе
Главный вывод из моего опыта состоит в том, что адекватный пользователь вовсе
не обязан выглядеть с первого взгляда как адекватный пользователь. Обратное, само
собой, тоже верно. Однажды мы написали программу. Заказчики были из Плесецка, но
это не то, что вы подумали, если, конечно вы вообще что-то подумали. Никакого отношения к одноимённому космодрому они не имели, просто их контора находилась в
этом самом городе, а они были люди самого что ни на есть мирного труда. Но в душе
романтики, то есть им нравилось программировать. Программировали они на Бейсике, максимальный размер программы— пятнадцать строк, но им это нравилось.
Поэтому, чувствуя себя в глубине души настоящими программистами, и получив
от нас заказанную программу, они во чтобы то ни стало захотели получить и её исходный текст. Нам этого категорически не хотелось, потому что случаи разные бывают, и никогда никого добра от передачи исходных кодов ни разу не было. Или украдут,
что не страшно, потому что всё равно ворованным исходным текстом никто ещё воспользоваться не сумел. Или, что хуже, внесут изменения, скомпилируют, получат проблемы, обломаются и предъявят претензии за плохую работу. Нам же и предъявят.
Мы не давали. Они требовали. Градус повышался. Начали орать. Яприслушался.
Орали всякое, но речь шла и о файлах, которые пишет наша программа. Понятие
«файл» заказчикам было знакомо и даже воспринималось совершенно правильно.
Файлы их программы, на Бейсике, выглядели как текст. Файлы данных, которые они
читали и писали— да, они читали и писали файлы!— при просмотре показывали
числа, они писали и читали текстовые файлы. Но когда они смотрели внутрь файлов,
записанных нашей программой, то видели там «мордочки». Никакой мистики— посмотрите любой бинарный, не текстовый файл— там обязательно есть мордочки.

53Совсем всё. Финиш
С этого момента я понял, что никакой опасности в отдаче исходных текстов не может быть, пошёл и распечатал всё. Заказчики, увидев примерно двести страниц текста, программа была не очень большая, вежливо сказали «спасибо, нам уже и не
надо», тем самым показав себя Адекватными Пользователями. Взаимовыгодное сотрудничество продолжалось и дальше ко всеобщему взаимоудовлетворению.
// конец рассказа об Адекватном Пользователе
Возможно, стоит всё-таки оформить всё это богатство в виде класса? Да, но
только если мы собираемся использовать запрограммированное нами долго и
всерьёз. Интерфейс класса, в первом приближении, предлагается таким, доработайте напильником, если хотите.
unit UsGrGraphic; // 01.12.2014
// 03.12.2014
{------------------------------------------------------------}
interface
uses
Types, Graphics;
type
TScale = packed record
begX : single;
MsmX : single;
begY : single;
MsmY : single;
end;
type
TLegend = packed record
///
end;
type
TGoodFunction = function( x : single) : single;
type
TGrGraphic = class
private
………………………………………………
public
property Func : TGoodFunction read fFunc write fFunc;
property sc : TScale read fSc write fSc;
property rect : TRect read fRect write fRect;
property color : TColor read fColor write fColor;
property width : integer read fWidth write fWidth;

54 Глава 2. Не совсем учебная. Графики
property axisOn : boolean read fAxisOn write fAxisOn;
property legend : TLegend read fLegend write fLegend;
constructor Create( wCanvas : TCanvas);
destructor Destroy; override;
procedure Draw;
private
// ɱɬɨɬɨ
end;
Есть ли жизнь после финиша?
Что дальше? Дальше много чего. Бывает ещё и логарифмический масштаб.
Обычно он применяется тогда, когда кривая меняется по вертикали настолько
стремительно, что в линейном масштабе уследить за ней становится невозможно. В этом случае по оси Y отображается не сама кривая, а её логарифм. Обратите
внимание, логарифмический масштаб бывает только по вертикали, по горизонтали он всегда линейный. Почему? Наверно потому. что две логарифмических
оси не смог бы вместить даже самый испорченный разум
Поясняю на примере. У нас есть кривая, принимающая значения в шести точ-
ках соответственно 1, 50, 20, 1000, 10000, 3000, неважно чего, пусть для определенности долбящих дятлов на квадратный километр леса, сокращенно ддкл.
Если мы зададим масштаб 10 ддкл/см, то должны будем нарисовать такие, в сантиметрах, значения — 0.1, 5, 2, 100, 1000, 300 Напоминаю, что один сантиметр мы
приравняли к 30 пикселям. Задача явно невыполнима, первое, второе и третье
значения мы хоть как-то нарисуем, а следующие уйдут за горизонт.
Вот что у нас получится:

55Есть ли жизнь после финиша?
Пойдём другим путём Можно задать масштаб 1000 ддкл/см. Значения для от-
рисовки будут такими — 0.001, 0.05, 0.02, 1, 10, 3. Результат всё равно нехорош.
Пятая точка не вписалась в диапазон и осталась неизвестной. Первые три точки
прекрасно видно, они в диапазон попали. Но вот только у нас во второй точке
имеется ярко выраженный локальный максимум, а на графике его можно увидеть только при условии, если заранее знать, где искать. Что делать? Укрупнить
масштаб? Тогда не увидим последние точки. Сделать масштаб помельче, чтоб
материализовалась пятая точка? Тогда первые три сольются в прямую.
Для таких случаев умные люди придумали логарифмический масштаб. Смысл
его в том, что по оси X откладываются не значения кривых в определенном масштабе, а значения их логарифмов, тоже в масштабе, само собой. Хотя в математике по умолчанию употребляются логарифмы натуральные, в реальной жизни
пользуются исключительно десятичными логарифмами. Таким образом, логарифмы наших значений будут равны соответственно 0, 1.7, 1.3, 3, 4, 3.47. Отобразить это не представляет никаких затруднений.
Усложняет реализауию то, что по оси ординат при этом принято откладывать
не значения логарифмов, а исходные значения данных, то есть — их значения до
логарифмирования. Интервал от логарифма нуля до логарифма десяти называется первой декадой, от логарифма десяти до логарифма ста — второй декадой и
так далее. Декады отделяются друг от друга горизонтальными линиями — это
просто, расстояния по вертикали между ними одинаковые. Сложнее то, что
крайне желательно отображать и значения всех промежуточных значения между
линиями декад.
Первая декада начинается с единицы, кончается в десяти (или там начинается
вторая декада, неважно, у нас тут не математический анализ, у нас реальная
жизнь. То есть, надо нарисовать линии для значений 2,3,4 и та далее. Во второй
декаде такими значениями будут 20,30,40… Печаль в том, что расстояния между
этими линиями совсем не будут одинаковыми. Подумайте почему.
А теперь о главном! О главном применительно к логарифмическому масшта-
бу, само собой. В логарифмическом масштабе нельзя отображать отрицательные

56 Глава 2. Не совсем учебная. Графики
значения, ну нельзя и всё! Что ещё хуже, в логарифмическом масштабе нельзя
отобразить даже такое безобидное число как ноль. Логарифмическую разметку
может начинать, к примеру, со значении, 0.000001, это пожалуйста. А с нуля —
никак.
Теперь опять о главном. В реальной жизни, на существование которой я вам
неоднократно намекал, аналитические функции, или, проще говоря, функции
заданные в виде хоть каких угодно формул, рисуют крайне редко, если вы не разработчик MathLab. И рисовать их относительно просто, даже если они не аналитические и даже если они не могут быть заданы в виде формул. Ну что самое
страшное есть в математике? Функция Дирихле?
Справка:
Функция Дирихле — функция, принимающая значение 1, если аргумент
есть рациональное число, и значение 0, если аргумент есть иррациональное
число.
Можно ли нарисовать её график? — спросил я у математика. И математик за-
ковырял в носу, а потом начал тихо качать в воздухе ботинками. А программисту — раз плюнуть. Или, выражаясь вульгарно, «как два байта переслать». Нарисуйте.
Однако, хватит о маленьких радостях жизни, пора вспомнить о сложностях.
Почему относительно легко и просто рисовать математические функции? Потому что рисовать их легко по сравнению с функциями, заданными таблично, то
есть, к примеру, в виде двух массивов плавающих чисел одинаковой длины. В одном массиве у нас значения аргументов, в другом массиве значения функции от
них. Говорить о функции в этом случае можно только в самом общем математическом смысле, потому что никакого аналитического, в виде формулы, выражения для неё нет. Более того, эта функция не определена для всех действительных
чисел (они же плавающие). Надо чётко понимать, что в промежутках между заданными значениями первого массива этой функции просто нет, мы может

57Есть ли жизнь после финиша?
предполагать там любые величины. А откуда это всё берётся? Я спрашиваю о наших двух входных массивах.
Обычно рисуются на графике данные, поступившие из жизни, говоря иначе с
како-то устройства снятия отсчётов, например уличного термометра за окном.
Каждый день мы на него смотрим и снимаем данные, то есть записываем их в
тетрадку в два столбика, в один дату, во второй градусы. Как известно, в молодости Иосиф Джугашвили, более известный как Сталин, работал наблюдателем в
Тифлисской обсерватории. В его обязанности как раз и входило снятие отсчётов
с метеорологических приборов. Несмотря на флегматический характер будущего
Сталина, это унылое занятие ему быстро надоело и он решил заняться чемнибудь другим.. Чтобы подобные случаи не повторялись, процедуру эту давно
автоматизировали.
В лучшем случае, датчик выдает данные с фиксированным шагом по оси аб-
сцисс, то есть по X. Чаще он выдаёт данные с произвольным, блуждающим шагом. Или не выдаёт вообще. Но о грустном позже.
Что касается разметки по оси абсцисс (оси X), то она всегда должна быть фик-
сированной, то есть с постоянным, заданным снаружи шагом, а не с тем плавающим, переменным, который имеют данные. Разумеется, по оси Y шаг тоже фиксированный, но это должно быть и так понятно.
А теперь об отображении на графике данных, заранее не известных. То есть не
известных перед отображением, а поступающих постепенно. Там всё то же самое, только хуже.
— Как часто вы чистили у него ботинки?
— Иногда часто, иногда редко, иногда вообще не чистил!
© Кино «Новые приключения неуловимых»
Этот диалог в точности отражает процесс поступления данных на отображе-
ние в реальном времени — поступают данные, иногда часто, иногда редко, иногда вообще не поступают.
Ещё о реальной жизни. Как я сказал, в лучшем случае данные поступают с
равномерным шагом. Но, даже в самом лучшем случае, почти наверняка от вас
потребуется отображать данные с, разумеется. тоже равномерным шагом, но другим, не совпадающим с исходным. Подходить в таком случае к задаче формально
не надо. Формально — имеется в виду подход «я это сделал, и какие ещё могут
быть вопросы?». Вопросы будут. То, что вы сделали, это хорошо. Но начальникто хотел другого! Он хотел не формального исполнения приказа. Он хотел, чтобы
вы сделали то, что он при этом подразумевал, имел в виду, о чём он мечтал, а совсем не то, что он произнёс вслух. Когда вам дают задание, об этом явно не говорят, потому что начальник уверен, что вы адекватный человек и при этом даже
программист. А когда вы приносите начальнику результат и там именно то, слово в слово, что он вам и сказал, то начальник огорчается.

58 Глава 2. Не совсем учебная. Графики
Вернёмся к нашему заданию, пусть у нас будет температура за окном. Снима-
ется она с шагом, к примеру в один час. Если данные снимаются с интервалом в
один час, то, возможно, они снимаются вручную. В этом случае интервалы между снятиями будут неизбежно плавать — 55 минут, 1 час 3 минуты. Мы исходим
из предположения, что у нас идеально честный сниматель отсчётов, потому что у
реального в тетрадке всё будет идеально, все отсчёты ровно через 60 минут.
К электронному снимателю отсчётов это замечание тоже относится. Кто его знает, когда он замерил температуру, но в файле всё будет хорошо. Но мы сейчас не
о морали, мы об отображении на графике.
Окончательно сформулируем задачу, на смеси программистского языка с ма-
тематическим. Есть массив X и массив Y плавающих чисел. В массиве Y у нас
может быть всё что угодно, это понятно и неинтересно. В массиве X содержатся
возрастающие значения, они могут быть с фиксированным приращением (шагом) или с переменным. Случай, когда данные по горизонтальной оси повторяются или даже убывают, тоже вполне возможен, но отвлекаться не будем. Наша
задача — отобразить данные из массивов на графике с фиксированным шагом,
который (шаг) не обязан совпадать с существующим шагом по X, даже если этот
шаг фиксирован.
То, чем мы сейчас будем заниматься, по научному должно называться интер-
поляция, экстраполяция и аппроксимация и, возможно, ещё как-то. «Должно» — потому что все эти прекрасные методы не вполне удобны и не вполне применимы в реальных компьютерных задачах. Если вас интересует теория, то рекомендую вот эту книгу, точнее, первые её главы:
А.Половко, П.Бутусов «Интерполяция. Методы и компьютерные технологии их реализации» БХВ-Петербург, 2004
Только первые главы потому, что в них излагается чистая математика, а дальше
идет применение этой теории к программам типа Mathlab. Разумеется, с помощью
этой теории можно теоретически решить все наши проблемы, но лучше там, где
можно, всё сделать по-простому, без высшей математики, чем мы сейчас и займёмся. Главное, что мы узнаем из этой книги — то, что бывает интерполяция и
аппроксимация. В любом случае, у нас есть, к примеру, три точки. Или, говоря
иначе, два массива из трёх элементов, в одном массиве значения по оси X, в другом массиве значения по оси Y. Это общее. Дальше нам дают значение по оси X и
просят сказать, сколько это будет по оси Y. А вот после этого происходит большая
разница. Если точка не совпадает по оси X ни с одной из трёх заранее известных
точек, то принципиальной разницы нет. А если совпадает, то принципиальная
разница есть. В случае интерполяции гарантируется, что рассчитанное значение в
любой из этих точек совпадёт с заранее известным. Поясняю. Это наши данные на
входе, те самые три точки:
X 1.0 2.0 3.0
Y 10.0 200.0 350.0

59Есть ли жизнь после финиша?
Данные условные, потому что очень простые. Если у нас работают интерполя-
ционные формулы, то задав новый X=1.5, мы получим что-то. Что именно, будет
зависеть от выбранного метода интерполяции. В случае аппроксимации будет то
же самое. Разница проявится при выборе X=2.0, например. При интерполяции
мы гарантированно получим 200.0, которые были и на входе. При аппроксимации нечто в дипазоне 199.9 — 200.1 (значения условные, само собой). Если пользователь не видит исходных данных, то и так неплохо. Если пользователь точно
знает, что в этой точке должно быть 200, то он огорчится. С теорией всё, теперь
поглядим как теория воплощается в жизнь.
Сначала рассмотрим идеальный случай — данные поступают равномерно с
шагом один час. Если на графике требуется отобразить их с шагом тоже один час,
то эта задача нами уже решена. Требование сложнее — на выходе требуется отображать данные с другим шагом, но кратным исходному.
Первый случай, это, когда исходный шаг меньше заказанного. Например, ис-
ходный шаг один час, а отображать на графике надо с шагом четыре часа. Значит,
надо четыре входных значения ужать в одно выходное. Как? А без затей. Разумеется, и в этом случае было бы разумным и необходимым, с математической точки
зрения, применить методы интерполяции. С программистской точки зрения, это
всё лишнее. Берём четыре входные значения, складываем их и делим на четыре.
Это и есть искомое нами значение. Программный код приводить не буду, мы же
программисты, всё-таки. В случае, когда требуемый шаг меньше исходного, тоже
умничать не надо. Пусть входной шаг тот же один час, а выходной заказан пятнадцать минут. Зачем, ну скажите мне зачем? Ведь информации не прибавится!
Но пользователи — они такие затейники, надо, значит надо. Стало быть, между
каждыми двумя соседними точками вставляем ещё три, полученных методом линейной интерполяции. Далее повторяя сам себя в циклическом режиме — правильнее было бы интерполировать, но никто разницы не заметит. И программу
писать не буду, для вас это должно быть просто.
В более сложном случае, когда исходный и заказанный шаги не кратны, при-
ходится, наконец, прибегнуть к интерполяции. В случае, если значения некратные, но имеют то, что называется, Наибольшее Общее Кратное, то проблема может быть решена средствами, сводимыми к ранее изложенным. Но мы ищем
простое, универсальное, хотя и неточное решение. Пусть наши входные данные
по оси X совершенно хаотичны (но строго возрастают). В этом случае рекомендуется интерполяция по четырём точкам. Две точки берём до нашей, несуществующей, две после. Далее интерполируем по Ньютону. Что делать, если наша
точка по счёту первая или вторая, она же последняя или предпоследняя? А я вас
всегда предупреждал, что краевые случаи самые сложные. Хотя, конечно, это
было известно задолго до того, как вы спросили моё мнение. Вылавливайте этот
случай и применяйте интерполяцию по меньшему числу точек, или выбирайте
другой набор точек для интерполяции. Если в этом тексте не всё понятно, то я
разъясню это всё в одной из моих будущих книг «Математика для идиотов программистов».

60 Глава 2. Не совсем учебная. Графики
Теперь мимоходом упомянем случай, когда данные неизвестны нам заранее
вообще, то есть, нет у нас двух массивов, в которых содержатся значения по X и
по Y. К примеру у нас есть класс, мы его опрашиваем и время от времени получаем от него информацию, в непредсказуемые моменты и с непредсказуемым
шагом. Только пожалуйста, не надо уточняющих вопросов, всё это очень отдельная тема.
Итак — шаг по X непредсказуем. Диапазон изменения по Y тоже непредска-
зуем. Разумеется, вначале он задан, но что делать, если реальные данные из
него выходят, не отдельными выбросами, а уверенно и надолго? Само собой,
количество данных не то, чтобы неизвестно, оно неизвестно в принципе. И что
делать, когда мы доползём до правой границы поля вывода? Вопросы, вопросы,
вопросы…
А вообще, об этом я напишу в одной из своих следующих книг — «Системы
реального времени для программистов идиотов».
Что мы видим в результате? Задача, которая казалась смешной и незатейли-
вой до неприличия, разрослась до неимоверных космических масштабов.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
