Цифровой корень. Неожиданное продолжение.

В связи с внезапно вспыхнувшим интересом к DGui, а также в связи с желанием поставить себе знание этого тулкита на уровень рефлексов (ну, например,усвоить его также как графику в Icon, которая настолько хорошо усвоена, что о ней я даже и не думаю, когда пишу очередную программку на нем), возникло желание повторить некоторые вещи, которые были написаны в Icon, на новой так сказать базе: D в связке с DGui.

Построение графиков и системы итерируемых функций (IFS) довольно стандартные для меня задачи, на которых я осваиваю работу с примитивами в самых разных языках программирования (да и математические отображения на плоскости не оставляют меня равнодушным), но некоторые из подобных задач запоминаются надолго, а иногда и проходят новое чтение и вызывают пересмотр способа решения.

Одной из таких задач стала задача построения графиков функций так или иначе связанных с цифровым корнем.

И вот тут наступил, так сказать, феерический абзац…

Дело все в том, что та интерпретация цифрового корня, что была в предыдущей статье о нем, не годится для такого языка со статистической типизацией, как D — и следовательно, буквальная калька реализации на Icon в этом случае никуда не годится.

Очевидно, у нас небольшая проблема: Icon динамически обращается с типами, а потому, такие вольности, как определение длины числа не прокатят — и их придется делать своими руками.

Но, несмотря на все эти проблемы, порт цифрового корня с Icon на D возможен, и я его сделал за несколько минут.

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

Например, реализацию можно сделать так:

// длина числа
int lengthOfNumber(int number)
{
int counter = 1;
while (number >= 10)
{
number /= 10;
counter++;
}
return counter;
}

Работает это весьма просто: количество цифр в записи числа напрямую следует из позиционной десятичной системы счисления, в которой каждая цифра стоит на своей позиции. Несложно доказать да и это напрямую вытекает из десятичной записи числа, что увеличенная на единицу наибольшая степень десятки в записи десятичного разложения равна количеству цифр (разрядов) в этом числе. Соответственно, устанавливаем переменную счетчик в единицу (логично, что чисел с нулевым количеством разрядов не бывает) и делим последовательно наше число на 10 (и увеличивая с каждым делением счетчик на единицу) до тех пор, пока полученное число не будет меньше или равно 10.

Вот и все с этой вспомогательной функцией.

А вот сама реализация цифрового корня на D даже со вспомогательной функцией довольно необычная:

// цифровой корень
int ciphRoot(int number)
{
    int s = 0;
    if (lengthOfNumber(number) == 1) return number;
    else {
        while (number >= 10)
        {
            s += number % 10;
            number /= 10;
        }
        s += number;
        if (lengthOfNumber(s) == 1) return s;
        else return ciphRoot(s);
    }
}

Это практически буквальный порт с Icon, который избавлен от динамической типизации и других трюкачеств с типами последнего. Если число цифр в аргументе этой функции равно единице (т.е если перед нами однозначное число), то тогда цифровой корень от такого аргумента равен самому этому аргументу.

А что, если иначе?

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

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

Как видно, не так уж и тяжело было сделать подобный порт (а при желании, подобный финт ушами можно и на Icon провернуть).

Теперь настало время совместить это с DGui, для чего быстренько создаем новый пустой проект для сборки dub’ом и наскоро переписываем с Icon функцию Вейерштрасса:

// функция Вейерштрасса
float weerstrass(float a, float b, float x)
{
   float s = 0.0;
   for (float i = 0; i <= 10; i++)
   {
       s += cos(3.1415926535 * x * (a ^^ i)) * (b ^^ i);
   }
   return s;
}

после чего в файл app.d вносим необходимые изменения:

import dgui.all, std.math;

// отрисовка отдельных точек
void drawPoint(Canvas c, Pen p, int x, int y) {
  // Pen p = new Pen(SystemColors.darkGreen, 1, PenStyle.solid);
  c.drawLine(p, x, y, x + 1, y + 1);
};

// длина числа
int lengthOfNumber(int number)
{
    int counter = 1;
    while (number >= 10)
    {
       number /= 10;
       counter++;
    }
    return counter;
}

// цифровой корень
int ciphRoot(int number)
{
    int s = 0;
    if (lengthOfNumber(number) == 1) return number;
    else {
        while (number >= 10)
        {
            s += number % 10;
            number /= 10;
        }
        s += number;
        if (lengthOfNumber(s) == 1) return s;
        else return ciphRoot(s);
    }
}

// функция Вейерштрасса
float weerstrass(float a, float b, float x)
{
   float s = 0.0;
   for (float i = 0; i <= 10; i++)
   {
       s += cos(3.1415926535 * x * (a ^^ i)) * (b ^^ i);
   }
   return s;
}

// отрисовка функции
void drawFunc(Canvas c)
{
Pen[] p = [
           new Pen(SystemColors.red, 1, PenStyle.solid),
           new Pen(Color(255, 61, 139), 1, PenStyle.solid),
           new Pen(Color(255, 153, 0), 1, PenStyle.solid),
           new Pen(SystemColors.yellow, 1, PenStyle.solid),
           new Pen(SystemColors.white, 1, PenStyle.solid),
           new Pen(SystemColors.green, 1, PenStyle.solid),
           new Pen(SystemColors.cyan, 1, PenStyle.solid),
           new Pen(SystemColors.blue, 1, PenStyle.solid),
           new Pen(SystemColors.magenta, 1, PenStyle.solid),
           new Pen(SystemColors.lightGray, 1, PenStyle.solid)
    ];

    for (float i = -250; i < 250; i += 0.01)
    {
        int coeff = ciphRoot(cast(int) (abs(1000 * i)));
        float f = weerstrass(2.0, 0.5, i / 100.0) * coeff;
        int X = cast(int) (250 + i);
        int Y = cast(int) (250 - 20 * f);
        drawPoint(c, p[coeff], X, Y);
    }
}



class MainForm : Form
{
    public this()
    {
        this.text = "Тест";
        this.size = Size(500, 550);
        this.startPosition = FormStartPosition.centerScreen;
    };

    protected override void onPaint(PaintEventArgs e)
    {

        Canvas c = e.canvas;
        drawFunc(c);

        super.onPaint(e);
    }
};


int main(string[] args)
{
    return Application.run(new MainForm());
}

собираем и запускаем (в моем случае, это делается под Wine):

Получается эпичнее и быстрее.

В отличие от программы для построения IFS, здесь использовано несколько видов «ручек», которые отличаются друг от друга лишь своими цветами. Нетрудно заметить, что задание цвета производится или с помощью выбора системных цветов (объект SystemColors) или с помощью прямого задания цвета в RGB (объект Color), а выбор производится согласно значению цифрового корня: значение этой числовой функции является индексом для массива объектов Pen, соответственно, по сути дела таким образом выбирается только цвет, во всем остальном объекты внутри массива идентичны.

 

Добавить комментарий