Основы программирования на языке Visual Prolog

Арифметика и строки

Разбить на страницы
Показывать лекцию целиком

В настоящей главе рассматриваются примеры реализации некоторых операций над целыми числами, предикаты класса math и предикаты класса string. Строится изображение троичного острова Коха.

10.1. Математические операции

Рассмотрим встроенные операции над целыми числами, а также предикаты класса math.

В следующей программе вычисляются наибольший общий делитель и наименьшее общее кратное целых чисел. Для вычисления используется алгоритм Евклида. Наименьшее общее кратное двух чисел находится как частное от деления произведения чисел на их наибольший общий делитель.

class predicates
    gcd: (unsigned64, unsigned64) -> unsigned64.
clauses
    gcd(X, 0) = X:- !.
    gcd(X, Y) = gcd(Y, X mod Y).

    run():-
        X = 194546742,  Y = 387623625,
        Z = gcd(X, Y),
        writef("Наибольший общий делитель % и % = %\n",
            X, Y, Z),
        writef("Наименьшее общее кратное % и % = %\n",
            X, Y, (Y div Z) * X),
        _ = readLine().

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

Конструкция if-then-else позволяет определить операцию вычисления наибольшего делителя с помощью всего одного правила (см. программу).

class predicates
    simplify: (tuple{integer, integer}) -> tuple{integer, integer}.
    gcd: (integer, integer) -> integer.
    toStr: (integer) -> string.
clauses
    simplify(tuple(X, Y)) = tuple(X div Z, Y div Z):-
        Z = gcd(X, Y).

    gcd(X, Y) = if Y = 0 then X else gcd(Y, X mod Y) end if.

    toStr(X) = toString(X):-
        X >= 0,
        !.
    toStr(X) = string::format("(%)", X).

    run():-
        X = 386,  Y = -514,
        tuple(A, B) = simplify(tuple(X, Y)),
        writef("% / % = % / %", X, toStr(Y), A, toStr(B)),
        _ = readLine().

Предикат toString переводит элемент любого домена в элемент домена string. Предикат format форматирует строку.

В приведенной ниже программе строится снежинка Коха (рис. 10.1). Отрезок в консоли изображается в виде последовательности точек. Перед печатанием каждой точки делается небольшая пауза. Цвет точек генерируется случайным образом.

(рис 10.1) Снежинка Коха: (a) 0; (b) 3 итерации
     open core, console, console_native, math

class facts
    w : integer := getConsoleWidth() - 1.

class predicates
    fractal: (positive, real, real, real, real, real [out], real [out]).
    line: (integer, integer, integer, integer).
    printPoint: (integer, integer).
    f: (real) -> integer.
clauses
    f(X) = restrict(round(X), 0, w).

    printPoint(X, Y):-
        programControl::sleep(10),
        setLocation(coord(X + 0, Y + 0)),
        setTextAttribute(math::random(7) + 9),
        write(".").

    line(X1, Y, X2, Y):- !,
        foreach X = std::between(X1, X2) do printPoint(X, Y)
        end foreach.
    line(X1, Y1, X2, Y2):-
        foreach Y = std::between(Y1, Y2) do
            printPoint(round(X1 + (Y - Y1) * (X2 - X1)/(Y2 - Y1)), Y)
        end foreach.

    fractal(0, X, Y, L, A, X1, Y1):-  !,
        X1 = X + L * cos(A),
        Y1 = Y + L * sin(A),
        line(f(2 * X), f(Y), f(2 * X1), f(Y1)).
    fractal(I, X, Y, L, A, X4, Y4):-
        I1 = I - 1,
        L1 = L / 3,
        fractal(I1, X, Y, L1, A, X1, Y1),
        fractal(I1, X1, Y1, L1, A - pi/3, X2, Y2),
        fractal(I1, X2, Y2, L1, A + pi/3, X3, Y3),
        fractal(I1, X3, Y3, L1, A, X4, Y4).

    run():-
        N = 3,
        L = 36,
        fractal(N, 2, 11, L, 0, X1, Y1),
        fractal(N, X1, Y1, L, 2 * pi/3, X2, Y2),
        fractal(N, X2, Y2, L, 4 * pi/3, _, _),
        _ = readLine().

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

Программа выполняет три итерации. На нулевой итерации рисуется равносторонний треугольник (см. рис 10.1 (см. рис. 10.1 (b)).

10.2. Обработка строк

В языке Visual Prolog имеется класс string, в котором реализовано большое количество предикатов обработки строк.

Следующая программа посвящена поиску токенов наибольшей длины в строке. Длиной строки (string) называется количество символов. Строка разбивается на токены. Находится токен наибольшей длины. В результате следующего преобразования в списке остаются только токены, длина которых совпадает с длиной токена наибольшей длины (используется предикат list::filter/2).

Предикат string::frontToken отделяет первый токен от строки. Предикат toTokens разбивает строку на список токенов. Предикат string::length возвращает длину строки.

    open core, console, string

class predicates
    toTokens: (string) -> string*.
    maxTokens: (string, string* [out], charCount [out]).
clauses
    toTokens(Str) = [Tok | toTokens(RestStr)]:-
        frontToken(Str, Tok, RestStr),
        !.
    toTokens(_) = [].

    maxTokens(Str, MaxTokList, MaxLen):-
        TokList = toTokens(Str),
        TokLenList = list::map(TokList, {(Tok) = length(Tok)}),
        MaxLen = list::maximum(TokLenList),
        MaxTokList = list::filter(TokList, {(S):- length(S) = MaxLen}).

    run():-
        Str = "И редкий солнца луч, и первые морозы",
        maxTokens(Str, MaxTokList, Length),
        write(MaxTokList, "\n", Length),
        _ = readLine().

В следующей программе текст разбивается на предложения. Знаки препинания ".", "!" и "?" используются в тексте только как знаки конца предложения. Предложение может заканчиваться последовательностью знаков, например, многоточием.

    open core, console, string

class predicates
    toSentences: (string) -> string*.
    getSentence: (string, string, string Sent [out], string [out]).
clauses
    toSentences(Str) = [Sentence | toSentences(RestStr)]:-
        splitStringBySeparators(Str, ".?!", FirstStr, Sign, Str1),
        FirstStr1 = trim(FirstStr),
        FirstStr1 <> "",
        !,
        getSentence(Str1, concat(FirstStr1, charToString(Sign)), 
            Sentence, RestStr).
    toSentences(_) = [].

    getSentence(Str, Sent, Sentence, Rest):-
        splitStringBySeparators(Str, ".?!", FirstStr, Sign, RestStr),
        "" = trim(FirstStr),
        !,
        getSentence(RestStr, concat(Sent, charToString(Sign)), 
            Sentence, Rest).
    getSentence(Str, Sent, Sent, Str).

    run():-
        Str = "Зима!.. Крестьянин, торжествуя, "
                "На дровнях обновляет путь...",
        L = toSentences(Str),
        list::forAll(L, {(Sent):- write(Sent), nl}),
        _ = readLine().

Предикат splitStringBySeparators находит префикс строки до какого-либо разделителя из символов ".?!", встретившегося в строке первым. Кроме этого, он возвращает сам разделитель, а также остаток строки. Предикат concat выполняет операцию конкатенации строк.

Упражнения

  • Найдите двоичное представление натурального числа в виде списка нулей и единиц.
  • По двоичному представлению натурального числа в виде списка нулей и единиц найдите его десятичное представление.
  • Поменяйте порядок следования слов (токенов) в строке на противоположный.
  • Найдите в строке слова-палиндромы.
  • Вычислите количество гласных в строке.
  • Разбейте строку на подстроки максимальной длины, не превосходящей заданного числа, которые оканчиваются на гласные.
  • Отобразите в окне консоли волну (синусоиду).
  • Напишите программу, которая переводит
  • римские числа в арабские;
  • арабские числа в римские.
  • Страницы:

    В настоящей главе рассматриваются примеры реализации некоторых операций над целыми числами, предикаты класса math и предикаты класса string. Строится изображение троичного острова Коха.

    10.1. Математические операции

    Рассмотрим встроенные операции над целыми числами, а также предикаты класса math.

    В следующей программе вычисляются наибольший общий делитель и наименьшее общее кратное целых чисел. Для вычисления используется алгоритм Евклида. Наименьшее общее кратное двух чисел находится как частное от деления произведения чисел на их наибольший общий делитель.

    class predicates
        gcd: (unsigned64, unsigned64) -> unsigned64.
    clauses
        gcd(X, 0) = X:- !.
        gcd(X, Y) = gcd(Y, X mod Y).
    
        run():-
            X = 194546742,  Y = 387623625,
            Z = gcd(X, Y),
            writef("Наибольший общий делитель % и % = %\n",
                X, Y, Z),
            writef("Наименьшее общее кратное % и % = %\n",
                X, Y, (Y div Z) * X),
            _ = readLine().
    

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

    Конструкция if-then-else позволяет определить операцию вычисления наибольшего делителя с помощью всего одного правила (см. программу).

    class predicates
        simplify: (tuple{integer, integer}) -> tuple{integer, integer}.
        gcd: (integer, integer) -> integer.
        toStr: (integer) -> string.
    clauses
        simplify(tuple(X, Y)) = tuple(X div Z, Y div Z):-
            Z = gcd(X, Y).
    
        gcd(X, Y) = if Y = 0 then X else gcd(Y, X mod Y) end if.
    
        toStr(X) = toString(X):-
            X >= 0,
            !.
        toStr(X) = string::format("(%)", X).
    
        run():-
            X = 386,  Y = -514,
            tuple(A, B) = simplify(tuple(X, Y)),
            writef("% / % = % / %", X, toStr(Y), A, toStr(B)),
            _ = readLine().
    

    Предикат toString переводит элемент любого домена в элемент домена string. Предикат format форматирует строку.

    В приведенной ниже программе строится снежинка Коха (рис. 10.1). Отрезок в консоли изображается в виде последовательности точек. Перед печатанием каждой точки делается небольшая пауза. Цвет точек генерируется случайным образом.

    (рис 10.1) Снежинка Коха: (a) 0; (b) 3 итерации
         open core, console, console_native, math
    
    class facts
        w : integer := getConsoleWidth() - 1.
    
    class predicates
        fractal: (positive, real, real, real, real, real [out], real [out]).
        line: (integer, integer, integer, integer).
        printPoint: (integer, integer).
        f: (real) -> integer.
    clauses
        f(X) = restrict(round(X), 0, w).
    
        printPoint(X, Y):-
            programControl::sleep(10),
            setLocation(coord(X + 0, Y + 0)),
            setTextAttribute(math::random(7) + 9),
            write(".").
    
        line(X1, Y, X2, Y):- !,
            foreach X = std::between(X1, X2) do printPoint(X, Y)
            end foreach.
        line(X1, Y1, X2, Y2):-
            foreach Y = std::between(Y1, Y2) do
                printPoint(round(X1 + (Y - Y1) * (X2 - X1)/(Y2 - Y1)), Y)
            end foreach.
    
        fractal(0, X, Y, L, A, X1, Y1):-  !,
            X1 = X + L * cos(A),
            Y1 = Y + L * sin(A),
            line(f(2 * X), f(Y), f(2 * X1), f(Y1)).
        fractal(I, X, Y, L, A, X4, Y4):-
            I1 = I - 1,
            L1 = L / 3,
            fractal(I1, X, Y, L1, A, X1, Y1),
            fractal(I1, X1, Y1, L1, A - pi/3, X2, Y2),
            fractal(I1, X2, Y2, L1, A + pi/3, X3, Y3),
            fractal(I1, X3, Y3, L1, A, X4, Y4).
    
        run():-
            N = 3,
            L = 36,
            fractal(N, 2, 11, L, 0, X1, Y1),
            fractal(N, X1, Y1, L, 2 * pi/3, X2, Y2),
            fractal(N, X2, Y2, L, 4 * pi/3, _, _),
            _ = readLine().
    

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

    Программа выполняет три итерации. На нулевой итерации рисуется равносторонний треугольник (см. рис 10.1 (см. рис. 10.1 (b)).

    10.2. Обработка строк

    В языке Visual Prolog имеется класс string, в котором реализовано большое количество предикатов обработки строк.

    Следующая программа посвящена поиску токенов наибольшей длины в строке. Длиной строки (string) называется количество символов. Строка разбивается на токены. Находится токен наибольшей длины. В результате следующего преобразования в списке остаются только токены, длина которых совпадает с длиной токена наибольшей длины (используется предикат list::filter/2).

    Предикат string::frontToken отделяет первый токен от строки. Предикат toTokens разбивает строку на список токенов. Предикат string::length возвращает длину строки.

        open core, console, string
    
    class predicates
        toTokens: (string) -> string*.
        maxTokens: (string, string* [out], charCount [out]).
    clauses
        toTokens(Str) = [Tok | toTokens(RestStr)]:-
            frontToken(Str, Tok, RestStr),
            !.
        toTokens(_) = [].
    
        maxTokens(Str, MaxTokList, MaxLen):-
            TokList = toTokens(Str),
            TokLenList = list::map(TokList, {(Tok) = length(Tok)}),
            MaxLen = list::maximum(TokLenList),
            MaxTokList = list::filter(TokList, {(S):- length(S) = MaxLen}).
    
        run():-
            Str = "И редкий солнца луч, и первые морозы",
            maxTokens(Str, MaxTokList, Length),
            write(MaxTokList, "\n", Length),
            _ = readLine().
    

    В следующей программе текст разбивается на предложения. Знаки препинания ".", "!" и "?" используются в тексте только как знаки конца предложения. Предложение может заканчиваться последовательностью знаков, например, многоточием.

        open core, console, string
    
    class predicates
        toSentences: (string) -> string*.
        getSentence: (string, string, string Sent [out], string [out]).
    clauses
        toSentences(Str) = [Sentence | toSentences(RestStr)]:-
            splitStringBySeparators(Str, ".?!", FirstStr, Sign, Str1),
            FirstStr1 = trim(FirstStr),
            FirstStr1 <> "",
            !,
            getSentence(Str1, concat(FirstStr1, charToString(Sign)), 
                Sentence, RestStr).
        toSentences(_) = [].
    
        getSentence(Str, Sent, Sentence, Rest):-
            splitStringBySeparators(Str, ".?!", FirstStr, Sign, RestStr),
            "" = trim(FirstStr),
            !,
            getSentence(RestStr, concat(Sent, charToString(Sign)), 
                Sentence, Rest).
        getSentence(Str, Sent, Sent, Str).
    
        run():-
            Str = "Зима!.. Крестьянин, торжествуя, "
                    "На дровнях обновляет путь...",
            L = toSentences(Str),
            list::forAll(L, {(Sent):- write(Sent), nl}),
            _ = readLine().
    

    Предикат splitStringBySeparators находит префикс строки до какого-либо разделителя из символов ".?!", встретившегося в строке первым. Кроме этого, он возвращает сам разделитель, а также остаток строки. Предикат concat выполняет операцию конкатенации строк.

    Упражнения

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