Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Полезное программирование

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
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 тоже непредска-
зуем. Разумеется, вначале он задан, но что делать, если реальные данные из него выходят, не отдельными выбросами, а уверенно и надолго? Само собой, количество данных не то, чтобы неизвестно, оно неизвестно в принципе. И что делать, когда мы доползём до правой границы поля вывода? Вопросы, вопросы, вопросы…
А вообще, об этом я напишу в одной из своих следующих книг — «Системы
реального времени для программистов идиотов».
Что мы видим в результате? Задача, которая казалась смешной и незатейли-
вой до неприличия, разрослась до неимоверных космических масштабов.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]