Числовой инструментарий, часть вторая
 
Автор: (C) Кристоф Шпиль [Christoph Spiel]
Перевод: (C) Сергей Скороходов


В первой части мы разобрали основные возможности пакетов GNU/Octave 2.1.34, Scilab 2.6 и Tela 1.32. Сегодня мы поговорим о матрицах, рассмотрим некоторые предопределенные функции и научимся создавать свои собственные, введем операторы управления ходом выполнения программы. В завершении главы мы кратко обсудим имеющихся в пакетах возможности ввода/вывода.

Матрицы

Векторы хороши, когда данные зависят от одного параметра. Разные значения параметра отображаются в разные индексных значениях. Но если данные зависят от двух параметров, использовать векторы неудобно и требуется структура более общего характера, которая позволяет использование двух независимых индексов. Такая структура называется матрицей. Матрица напоминает только что купленый ящик пива: ячейки образуют прямоугольник и все бутылки -- пардон, элементы -- на месте.

Матрицы могут быть составлены из скалярных величин, как в этом примере (GNU/Octave):

    octave:1> #                  температура    осадки    солнечные
    octave:1> #                  гр. F          дюймы     часы
    octave:1> weather_data = [   73.4,          0.0,      10.8;  ...
    >                            70.7,          0.0,       8.5;  ...
    >                            65.2,          1.3,       0.7;  ...
    >                            68.2,          0.2,       4.1]
    weather_data =
      73.40000   0.00000  10.80000
      70.70000   0.00000   8.50000
      65.20000   1.30000   0.70000
      68.20000   0.20000   4.10000

Здесь можно увидеть три новых идеи. Во-первых, мы используем комментарии для того, чтобы дать обозначения колонкам нашей матрицы. Комментарий начинается с символа "решетки" "#" и "простираются" до конца строки. Во-вторых, ряды в матрице разделяются точкой с запятой ";". И в третьих, если выражение не помещается в одной строке, незавершенные строки необходимо заканчивать оператором продолжения строки [line-continuation operator] "...".

Подобно векторам, матрицы могут состоять не только из скаляров, но и из векторов или других матриц.Например, если у нас есть несколько переменных, хранящих ежедневные данные по погоде:

    weather_mon = [73.4, 0.0, 10.8]
    weather_tue = [70.7, 0.0,  8.5]
    weather_wed = [65.2, 1.3,  0.7]
    weather_thu = [68.2, 0.2,  4.1]

мы можем определить weather_data так:

    weather_data = [weather_mon; weather_tue; weather_wed; weather_thu]

или, если мы получаем данные от каждого измерительного инструмента:

    temperature = [73.4; 70.7; 65.2; 68.2]
    rain = [0.0; 0.0; 1.3; 0.2]
    sunshine = [10.8; 8.5; 0.7; 4.1]

weather_data может быть определена так:

    weather_data = [temperature, rain, sunshine]

Общее правило: запятые разделяют колонки, символы "точка с запятой" отделяют строки.

Доступ к скалярным значениям, содержащимся в матрице m можно получить, используя два индекса: m(row, column), где row -- индекс строки, а column -- индекс столбца. Таким образом, количество выпавших в среду осадков можно получить:

    octave:10> weather_data(3, 2)
    ans = 1.3000

Можно изменять элементы, присваивая им новые значения:

    octave:11> weather_data(3, 2) = 1.1
    weather_data =
      73.40000   0.00000  10.80000
      70.70000   0.00000   8.50000
      65.20000   1.10000   0.70000
      68.20000   0.20000   4.10000

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

    данные_о_погоде_в_тропическом_лесу = weather_data + 2.1
    данные_по_зимней_погоде_в_сибири = weather_data / 3.8

не слишком осмыслены, хотя компьютер и не будет жаловаться. В первом случае он покорно прибавляет 2.1 к каждому элементу weather_data, а во втором -- послушный, как собака -- делит каждый элемент на 3.8.

Положим, мы хотим сделать с weather_data нечто осмысленное и перевести температуру из градусов Фаренгейта в температуру по Цельсию. Для этого нам надо обработать все элементы первого столбца. Интересующий нас вектор таков:

    octave:16>     temp = [weather_data(1, 1); ...
    >                      weather_data(2, 1); ...
    >                      weather_data(3, 1); ...
    >                      weather_data(4, 1)]
    temp =
      73.400
      70.700
      65.200
      68.200

Очевидно, что индексы столбцов [1, 2, 3, 4] сами образуют вектор. Можно "срезать угол" и воспользоваться этим вектором индексов

    temp = weather_data([1, 2, 3, 4], 1)

Вообще говоря, любой вектор может быть использован, как вектор индексов. Единственно, следите за тем, чтобы не выходить за пределы индексирования. Порядок индексов имеет значение (напрмер weather_data([2, 1, 4, 3], 1) помещает на первое место температуру вторника), допускается повторениие индексов (например результат weather_data([3, 3, 3, 3, 3, 3, 3], 1) содержит семь экземпляров температуры в среду).

В нашем примере вектор индексов мог бы создаваться специальным встроенным оператором диапазона [range] ":". Для того, чтобы создать вектор, начинающийся со значения low и содержащий все целые числа от low до high, мы пишем:

    low:high

Например:

    octave:1> -5:2
    ans =
      -5  -4  -3  -2  -1   0   1   2

Пример с погодой упрощается до вида:

    temp = weather_data(1:4, 1)

Поскольу извлечение целого столбца или строки -- очень частая операция, имеется возможность "срезать угол" еще круче. Если мы опустим и low, и high в операторе :, он самостоятельно сгенерирует все возможные индексы. Мы, тем самым, получили самую краткую форму извлечения всех элементов первой колнки.

    octave:17> temp = weather_data(:, 1)
    temp =
      73.400
      70.700
      65.200
      68.200

Воспользуемся вновь обретенным знанием и извлечем число солнечных часов во вторник, среду и четверг:

    octave:19> sunnyhours = weather_data(2:4, 3)
    sunnyhours =
      8.50000
      0.70000
      4.10000

а теперь -- сотояние погоды во вторник:

    octave:20> tue_all = weather_data(2, :)
    tue_all =
      70.70000   0.00000   8.50000

Преобразования количества выпавших осадков из дюймов в миллиметры теперь тривиально: умножим вторую колонку weather_data на 25.4 (миллиметров на дюйм) и перейдем в метрическую систему:

    octave:21> rain_in_mm = 25.4 * weather_data(:, 2)
    rain_in_mm =
       0.00000
       0.00000
      27.94000
       5.08000

Мы уже видели, что векторы можно использовать со скалярными величинами:

    1.25 + [0.5, 0.75, 1.0]

или

    [-4.49, -4.32, 1.76] * 2

Точно также, скаляры можно использовать с матрицами.

    octave:1> 1.25 + [ 0.5,   0.75, 1.0; ...
    >                 -0.75,  0.5,  1.25; ...
    >                 -1.0,  -1.25, 0.5]
    ans =
      1.75000  2.00000  2.25000
      0.50000  1.75000  2.50000
      0.25000  0.00000  1.75000
    octave:2> [-4.49, -4.32, 1.76; ...
    >           9.17,  6.35, 3.27] * 2
    ans =
       -8.9800   -8.6400    3.5200
       18.3400   12.7000    6.5400

В обоих случаях результатом будет применение скаляра к каждому элементу вектора или матрицы.

А как векторы и матрицы? Очевидно, что выражения подобные

    [7, 4, 9] + [3, 2, 7, 6, 6]
    [2, 4; 1, 6] - [1, 1, 9, 4]

бессмыслены. В первом случае не согласуется размер векторов (3 и 5 элементов соответственно), во втором случае -- различна форма (матрица 2х2 и четыре столбца). Для того, чтобы операция имела смысл, векторы и матрицы, учавствующие в сложении или вычитании, должны иметь одинаковую форму, т.е. одинаковое число строк и столбцов. Технический термин "форма" [shape] в данном контексте означает размерность. Узнать размерность любого объекта можно с помощью встроеной функции size().

    octave:22> size(weather_data)
    ans =
      4  3
    octave:23> size(sunnyhours)
    ans =
      3  1

Ответ -- вектор, первый элемент которого соответствует числу строк, а второй -- числу столбцов в аргументе size().

Умножение и деление матриц можно задать двумя способами, в наших "числодробилках" реализованы оба.

  • Почленное умножение или деление двух векторов или матриц одинаковой размерности: число в первой строке и первом столбце первой матрицы умножается на число в первой строке и первом столбце второй матрицы и т.д.

            a = [3, 3; ...
                 6, 4; ...
                 6, 3]
            b = [9, 3; ...
                 8, 2; ...
                 0, 3]
        
            octave:1> a .* b
            ans =
        
              27   9
              48   8
               0   9
        

    Символы поэлементных операторов начинаются с точки: поэлементное умножение обозначается ".*", а поэлементное деление -- "./".

  • Матричное умножение, известное из линейной алгебры: c =  a * b, где a -- матрица p на q, а b -- матрица q на r. Результатом будет матрица p на r.

    Пример:

            a = [3, 3; ...
                 6, 4; ...
                 6, 3]
        
            b = [-4,  0, 1, -4; ...
                 -1, -3, 2,  0]
        
            octave:1> a * b
            ans =
        
              -15   -9    9  -12
              -28  -12   14  -24
              -27   -9   12  -24
        

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

            for i = 1:p
                for j = 1:r
                    sum = 0
                    for k = 1:q
                        sum = sum + a(i, k)*b(k, j)
                    end
                    c(i, j) = sum
                end
            end
        

    Сравните этот цикл с тройным уровнем вложенности с простым выражением c = a * b.

  • Деление матриц? На матрицу делить нельзя! Однако, оператор "/" определен для векторов и матриц. Но запись x = b / a, где a и b -- матрицы или векторы, не имеет ничего общего с делением! Она означает: будьте добры, решите систему линейных уравнений

            x * a = b
        

    относительно x, полагая известными матрицу a и матрицу b с правой стороны выражения. "*" здесь соответствует умножению матриц, как он описан в предыдущем пункте, с соблюдением правил согласования размерности a и b.

            a = [-2, 3,  1; ...
                  7, 8,  6; ...
                  2, 0, -1]
        
            b = [-26,  5, -6; ...
                  24, 53, 26]
        
            octave:1> x = b / a
            x =
        
               7.00000  -2.00000   1.00000
               7.00000   4.00000   5.00000
        

    Простой способ решить систему линейных уравнений, правда? Представьте себе, что вы сами выписываете весь нужный для этого код.

    И, наконец, проверим результат умножением наa:

            octave:2> x*a
            ans =
        
              -26.0000    5.0000   -6.0000
               24.0000   53.0000   26.0000
        

    Что восстанавливает b, как и ожидалось.

Подробности

  • Для удобства в GNU/Octave и Scilab определен альтернативный оператор матричного деления "\". x =  a \ b решает линейную систему уравнений

            a * x = b
        

    относительно x, считая известными матрицу a и правую часть равенства b. Большинство пользователей предпочитают именно эту форму, поскольку здесь x -- вектор-столбец, в противоположность оператору "/", который возвращает вектор-строку.

  • У оператора "\" есть "кузен с точкой" -- ".\", причем свойстово a ./ b  == b .\ a сохраняется.

Различия

  • Scilab и Tela используют комментарии в стиле C++

            // В Scilab или Tela это комментарий
        
  • Tela не пуждается и не понимает оператора продолжения строки "...".

            weather_data = #(73.4, 0.0, 10.8;
                             70.7, 0.0,  8.5;
                             65.2, 1.3,  0.7;
                             68.2, 0.2,  4.1)
        

    В интерактивном режиме Tela не обрабатывает приведенное многострочное выражение. Но многострочные выражения могут быть прочитаны из файла с исходным кодом командой source("filename.t").

  • В Tela операторы "*" и "/" действуют "поэлементно", т.е. так, как в GNU/Octave и Scilab действуют операторы ".*" и "./". Матричное умножение (a * b в GNU/Octave или Scilab) записывается так:

            a ** b
        

    или

            matmul(a, b)
        

    решение системы линейных уравнений (в Octave или Scilab -- b / a) делается так:

            linsolve(a, b)
        

Встроенные функции матричных вычислений

Ох -- это сколько же всего надо упомянуть! Наши числовые инструменты предлагают десятки предопределенных функций. Здесь я могу только подогреть интерес читателя.

Генерация специальных матриц
Некоторые матрицы появляются в рассчетах достаточно часто для того, чтобы имело смысл предоставить для их создания особые функции. Например, матрица m на n, заполненная нулями: zeros(m, n) или единицами: ones(m, n); единичная [eigenvalue] n на n матрица с состоящей исключительно из единиц диагональю: eye(n); матрица, диагональные элементы которой заданы вектором: diag([a1, a2, ..., aN]).
Анализ матриц

Получение наименьшего или наибольшего элемента матрицы a: min(a), max(a) или суммы всех элементов [totaling] матрицы a: sum(a).

Различия:В GNU/Octave min(a), max(a) и sum(a) возвращают вектор-строку, содержащий результат для каждого столбца исходной матрицы. Для того, чтобы получить минимум, максимум или сумму всех элементов матрицы a, следует воспользоваться вызовами min(min(a)), max(max(a)), sum(sum(a)).

Линейная алгебра

Мы уже упоминали, что системы линейных уравнений наподобие следующей x * a = b могут быть решены относительно x с помощью оператора "/". Существуют, однако, много других функций линейной алгебры, например разложение матрицы по сингулярным числам [single value decomposition]: svd(a) или вычисление собственных значений [eigenvalue]: eig(a).

Различия: В Tela вместо svd(a) используется SVD(a), а в Scilab вместо eig(a) используется spec(a).

Замечание по поводу производительности: все три программы -- интерпретаторы. Это означает, что каждое выражение сначала разбирается [parsed], затем интерпретатор выполняет необходимые вычисления и наконец вызывает внутри выражения функции -- в сравнении с откомпилированной программой это относительно медленный процесс. Но упомянутые ранее функции используются в откомпилированном виде! Они выполняются почти с максимальной скоростью. Интерпретатор в данном случае просто передает матрицу целеком в откомпилированную функцию, написанную на Fortran'е, C или C++. Функция делает свое дело, после чего интерпретатор забирает результат.

Мы, таким образом, вывели фундаментальное правило успешного использования "числодробилок": отдавайте предпочтение откомпилированным функциям, а не интерпретируемому коду. Что значительно ускоряет процесс счета.

Функции, определяемые пользователем

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

Пользовательские функции лучше определять в файлах для того, чтобы ими можно было воспользоваться в последующих сессиях. В GNU/Octave файлы с функциями имеют расширение .m, а загружаются либо автоматически, либо командой source("filename.m"). Scilab называет свои файлы функций .sci, их надо загружать командой getf("filename.sci"). Функции Tela храняться в файлах .t и загружаются с помощью source("filename.t"). За вычетом этой разнице в процедуре загрузки, во всех трех инструментах применяется очень похожий синтаксис определения функций.

GNU/Octave и Scilab

    function [res1, res2, ..., resM] = foo(arg1, arg2, ..., argN)
        # function body
    endfunction

Tela

    function [res1, res2, ..., resM] = foo(arg1, arg2, ..., argN)
    {
        // function body
    };

где arg1 по argN соответсвуют аргументам функции (также известные, как параметры), а res1 по resN соотсветствуют возвращаемым значениям. Да, ваши глаза не лгут, допускаются множественные возвращаемые значения, что может удивить большинство читателей, знакомых с наиболее распространенными языками программирования. Это, впрочем, является необходимостью, поскольку функции не могут изменять значений входных аргументов.

Хватит теории! Давайте напишем функцию, которая на входе принимает матрицу и возвращает матрицу такой же размерности, но с элементами, отмасштабированными в интервале (0, 1).

    ### Octave
    function y = normalize(x)
        ## Возвращает матрицу X, масштабированную в интервале (0, 1).

        minval = min(min(x))
        maxval = max(max(x))

        y = (x - minval) / (maxval - minval)
    endfunction

А теперь в Scilab определим функцию, возвращающую спектральный радиус [spectral radius] матрицы. Мы воспользуемся встроенной функцией abs(), которая вычисляет абсолютную величину своего аргумента (в том числе и комплексного).

    // Scilab

    function r = spectral_radius(m)
        // Возварщает спектралный радиус R матрицы M.

        r = max(abs(spec(m)))
    endfunction

Наконец, в Tela напишем функцию, которая вычесляет Евклидову (Фробениуса) норму [Frobenius norm] для данной матрицы.

    // Tela

    function x = frobenius(m)
    // Возвращает Фробенисову норму X of matrix M.
    {
        x = sqrt(sum(abs(m)^2))
    };

Нюансы:

"Автомагическая" загрузка файлов функций в GNU/Octave работает следующим образом: когда Octave сталкивается с функцией без определения, просматривается список директорий, задаваемый встороенной переменной LOADPATH, и ищется файл с расширением .m и именем, совпадающим с неопределенной функцией. Например, вызов x = my_square_root(2.0) приведет к поиску файла my_square_root.m во всех каталогах, перечисленных в LOADPATH.

Операторы управления ходом выполнения программы

Весь код, который мы написали до сих пор, выполняется строго последовательно, мы не использовали таких операторов, как операторы условного перехода или операторы цикла.

Прежде, чем мы начнем манипулировать с передачей управления, нам нужно рассмотреть логические выражения, поскольку на них опирается провека условий в операторах условного перехода и в циклах. Логические выражения образуются из (1.) чисел, (2.) операций сравнения и (3.) логических выражений, объединенных в одно целое с помощью логических операторов.

  1. Ноль логически означает ложь, любое не равное нулю число логически означает истину, так что программирующие на C будут чувствовать себя, как дома.
  2. Присутсвует привычная "шайка" операций сравнения: меньше-чем "<", меньше-чем-или-равно "<=", больше-чем ">", больше-чем-или равно ">=" и равно "==".

    Различия: Проверка на неравенство в разных программах немного различается: Octave не может решить, хочет ли она быть как C, Smalltalk или Pascal. Scilab хочет быть как Smalltalk и Pascal одновременно. :-)

                !=   ~=   <>    # Octave
                ~=   <>         // Scilab
                !=              // Tela
            
  3. Сложные логические выражения образуются с помощью логических операций "and", "or" и "not", синтаксис которых заимствован из C. Каждая программа, однако, использует собственный набор операций. Так что нам придется перечислить хотя бы некоторые.

    Различия:

                and      or     not
                ----    ----    ----
                 &       |      !  ~     #  Octave
                 &       |      ~        // Scilab
                 &&      ||     !        // Tela
            

Теперь мы готовы для первого условного оператора, а именно оператора условного перехода if. Обратите внимания, что скобки вокруг условия могут быть обязательны (как в C). Наличие ветви else не обязательно в любом случае.

    # Octave                // Scilab               // Tela
    if ( условие )          if условие then         if ( условие ) {
        # если да               // если да              // если да
    else                    else                    } else {
        # иначе                 // иначе                // иначе
    endif                   end                     };

условие является логическим выражением в описанном выше смысле.

оператор цикла while:

    # Octave                // Scilab               // Tela
    while ( условие )       while условие           while ( условие ) {
        # тело цикла            // тело цикла           // тело цикла
    endwhile                end                     };

условие, конечно же, является логическим выражением.

В Octave и Scilab оператор for переберает по одной колонки выражения expr. Это выражение чаще всего является вектором, созданным оператором диапазона ":", например for i = 1:10. В Tela оператор for работает так же, как в C.

    # Octave                // Scilab               // Tela
    for var = expr          for var = expr          for (init; cond; step) {
        # работа                // работа               // работа
    endfor                  end                     };

Вот несколько примеров.

Octave

    function n = catch22(x0)
        ## Знаминитая функция catch-22 (двадцать второй капкан:)
        ## или уловка-22 (по названию знаменитого романа Дж. Хеллера,
        ## подробностей переводчик не знает:) :
        ## невозможно определить заранее, завершатся ли вычисления
        ## для каждого конкретного значения аргумента.
        ## Возвращает число итераций.
        n = 0
        x = x0
        while (x != 1)
            if (x - floor(x/2)*2 == 0) 
                x = x / 2
            else
                x = 3*x + 1
            endif
            n = n + 1
        endwhile
    endfunction

Scilab

    function m = vandermonde(v)
        // Возвращает M: матрицу Вандермонде,
        // основанную на векторе V.

        [rows, cols] = size(v)
        m = []                      // empty matrix
        if rows < cols then
            for i = 0 : (cols-1)
                m = [m; v^i]
            end
        else
            for i = 0 : (rows-1)
                m = [m, v^i]
            end
        end
    endfunction

Tela

    function vp = sieve(n)
    // Решето Эратосфена; возвращает вектор VP всех
    // простых чисел VP, которые строго меньше 2*N.
    // 1 не рассматривается, как простое число.
    {
        vp = #();               // пустой вектор
        if (n <= 2) { return };
        vp = #(2);
        flags = ones(1, n + 1);
        for (i = 0; i <= n - 2; i = i + 1)
        {
            if (flags[i + 1])
            {
                p = i + i + 3;
                vp = #(vp, p);
                for (j = p + i; j <= n; j = j + p)
                {
                    flags[j + 1] = 0
                }
            }
        }
    };

Ввод/Вывод

Мы долго-долго работаем с одним из наших цифровых инструментов и в определенный момент нам уже хочется закончить наш долгий рабочий день. Но нам и не хочется потерять все наработанные за день результаты. Функции мы уже сохранили в файлах с исходным кодом. Пришло время узнать, как дать возможность существования вне программы нашим данным.

Простые операции ввода/вывода

Во всех трех программах используется одинаковая модель ввода/вывода (I/O), в значительной степени заимствованная из языка программирования C. В этой модели есть возможность тонкого управления нюансами чтения и записи. Однако нередко нет необходимости прямого задания формата записываемого файла. Если все, что нужно -- сохранить переменные для того, чтобы ими можно было бы воспользоваться в следующий раз, то вполне подойдут и упрощенные команды I/O.

  • Octave предлагает наиболее гибкое решение с помощью парных комманд save/load.

                save filename varname1 varname2 ... varnameN
        

    сохраняет переменные varname1, varname2, ..., varnameN в файле filename. Парная ей

                load filename varname1 varname2 ... varnameN
        

    восстанавливает их из filename. Если load не указать имен переменных, будут загружены все переменные, содержащиеся в filename. Если load указать конкретные имена, то будут загружены только выбранные переменные.

    Обратите внимание, что в командах save и load не ставятся скобки, а аргументы разделяются пробелами вместо запятых. Имена файла и переменных должны быть строками.

                save "model.oct-data" "prantl" "reynolds" "grashoff"
                load "model.oct-data" "reynolds"
            

    По умолчанию load не перезаписывает существующие переменные, а жалуется, если пользователь пытается это сделать. Если потеря значений существующих переменных безопасно, добавление в вызов load опции "-force"

                load -force "model.oct-data" "reynolds"model.oct-data
            

    и переменная reynolds будет загружена из файла model.oct-data не зависимо от того, существовала ли она в памяти или нет.

  • В Scilab'е простой ввод/вывод схож с GNU/Octave:

                save(filename, var1, var2, ..., varN)
            

    Переменные var1, ..., varN, однако, здесь не строки с именами переменных, а собственно переменные, т.е. они должны указываться непосредственно. Это означает, что имя переменной не сохраняется в файле. Связь между именем и значением утрачена!

    Парная функция

                load(filename, varname1, varname2, ..., varnameN)
            

    восстанавливает содержимое файла filename в переменные с именами varname1, varname2, ... varnameN.

  • Tela позволяет пользователю сохранять переменные вызовом функции

                save(filename, varname1, varname2, ..., varnameN)
            

    сохраняющей связь между именем переменной и ее содержимым. Парная ей фунция

                load(filename)
            

    загружает все переменные, содержащиеся в filename. Выбор конкретных переменных невозможен.

Ввод/вывод, ориентированный на матрицы

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

Предположим, что у нас есть ASCII файл datafile.ascii, содержащий строки

    # run 271
    # 2000-4-27
    #
    # P/bar   T/K     R/Ohm
    # ======  ======  ======
    19.6      0.118352  0.893906e4
    15.9846   0.1  0.253311e5
    39.66     0.378377  0.678877e4
    13.6      0.752707  0.00622945e4
    12.4877   0.126462  0.61755e5

и находящийся в рабочей директории. Пять первых строк файла -- не числовые. Наши программы пропускают их при чтении, но они могли бы помочь пользователю разобраться с данными. Я специально взял не слишком аккуратно отформатированные данные, как это обычно и бывает в жизни. Функции загрузки матриц разбивают читаемый поток по пробельным символам, а не по конкретным колонкам, так что файл datafile.ascii им понравится.

    octave:1> data = load("datafile.ascii")
    data =

       1.9600e+01   1.1835e-01   8.9391e+03
       1.5985e+01   1.0000e-01   2.5331e+04
       3.9660e+01   3.7838e-01   6.7888e+03
       1.3600e+01   7.5271e-01   6.2294e+01
       1.2488e+01   1.2646e-01   6.1755e+04

или в Scilab'е

    -->data = fscanfMat("datafile.ascii")
     data  =

    !   19.6       0.118352    8939.06 !
    !   15.9846    0.1         25331.1 !
    !   39.66      0.378377    6788.77 !
    !   13.6       0.752707    62.2945 !
    !   12.4877    0.126462    61755.  !

а так в Tela

    >data1 = import1("datafile.ascii")
    >data1
    #(      19.6,     0.118352,   8939.06;
            15.9846,  0.1,        25331.1;
            39.66,    0.378377,   6788.77;
            13.6,     0.752707,   62.2945;
            12.4877,  0.126462,   61755)

Во всех трех примерах data будут содержать матрицу 5 на 3, заполненную значениями из datafile.ascii.

Парные команды для сохранения отдельной матрице в файле формата ASCII:

    save("data.ascii", "data")                # GNU/Octave
    fprintfMat("data.ascii", data, "%12.6g")  // Scilab
    export_ASCII("data.ascii", data)          // Tela

Обратите внимание, что fprintfMat() в Scilab'е требует третий параметр, определяющий формат вывода в стиле форматной строки языка C.

Ни одна из описанных выше команд, естественно, не сохраняет оригинальный заголовок: т.е. строки файла datafile.ascii, начинающиеся с символа "решетки". Для того, чтобы записать и их, необходимо прибегнуть к "низкоуровневым" функциям ввода/вывода в стиле языка C, которые присутствуют в каждой из рассматриваемых программ.

Ввод/вывод в стиле языка C

Для точного управления вводом и выводом предлагается модель C I/O. Во всех трех приложениях реализована функция

    printf(format, ...)

Более того, GNU/Octave и Tela следуют схеме именования языка C:

    handle = fopen(filename)
    fprintf(handle, format, ...)
    fclose(handle)

в то время, как Scilab добавляет к именам функций префикс "m" вместо "f"

    handle = mopen(filename)
    mprintf(handle, format, ...)
    mclose(handle)

Не зависимо от того, называются ли функция fprintf() или mprintf(), они работают одинаково.

В следующем месяце: Графика, графики функций и отображение данных.


Кристоф Шпиль [Christoph Spiel]

Крис управляет консалтинговой компанией по открытому программному обеспечению в верхней Баварии, Германия. По своей специальности - физике -- у него есть докторская степень от Munich University of Technology -- его основные интересы лежат в области теории чисел, гетерогенных программных сред и программного инжиниринга. С ним можно связаться по адресу [email protected].


Copyright (C) 2001, Christoph Spiel.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 70 of Linux Gazette, September 2001

Вернуться на главную страницу