Возможность работы с произвольными наборами символьных строк является одним из ключевых преимуществ сценариев AMPL. Далее описывается оператор конкатенации строк и несколько функций для построения выражений со строковыми значениями, которые можно использовать везде, где элементы набора могут появляться в инструкциях AMPL.
Также показывается, как строковые выражения могут использоваться для указания символьных строк, которые служат не только элементам набора, но и другим целям. Эта функция позволяет сценарию AMPL, например, записывать другой файл или устанавливать разные значения параметров при каждом проходе цикла в соответствии с информацией, полученной из содержимого наборов индексации цикла.
s & t | объединить строки s и t |
num(s) | преобразовать строку s в число; ошибка, если удаление начальных и конечных пробелов не приводит к действительному десятичному числу |
num0(s) | убрать начальные пробелы и интерпретировать как можно больше s как число, но не выявлять ошибку |
ichar(s) | Unicode-значение первого символа в строке s |
char(n)
setof {i in 65..90}char(i) setof {i in 97..122}char(i) |
строковое представление символа n; инверсия ichar
A-Z a-z |
length(s) | длина строки s |
substr(s,m,n) | n символьная подстрока s, начиная с позиции m; если n опущено, остаток строки |
sprintf(f,exprlistopt) | форматировать аргументы в соответствии со строкой формата fmt |
match(s,re) | начальная позиция регулярного выражения re в s или 0, если не найден |
sub(s,re,repl) | заменить repl для первого появления регулярного выражения re в s |
gsub(s,re,repl) | заменить repl для всех вхождений регулярного выражения re в s |
Таблица 1: Встроенные функции строк и регулярных выражений.
Оператор конкатенации & принимает две строки в качестве операндов и возвращает строку, состоящую из левого операнда, за которым следует правый операнд. Например, для наборов NUTR и FOOD, определенных как:
set NUTR:= A B1 B2 C NA CAL ; set FOOD:= BEEF CHK FISH HAM MCH MTL SPG TUR ;
можно использовать конкатенацию для определения набора NUTR_FOOD, элементы которого представляют пары питательное вещество - еда:
set NUTR_FOOD := setof {i in NUTR,j in FOOD} i & "_" & j; display NUTR_FOOD; set NUTR_FOOD := A_BEEF B1_BEEF B2_BEEF C_BEEF NA_BEEF CAL_BEEF A_CHK B1_CHK B2_CHK C_CHK NA_CHK CAL_CHK A_FISH B1_FISH B2_FISH C_FISH NA_FISH CAL_FISH A_HAM B1_HAM B2_HAM C_HAM NA_HAM CAL_HAM A_MCH B1_MCH B2_MCH C_MCH NA_MCH CAL_MCH A_MTL B1_MTL B2_MTL C_MTL NA_MTL CAL_MTL A_SPG B1_SPG B2_SPG C_SPG NA_SPG CAL_SPG A_TUR B1_TUR B2_TUR C_TUR NA_TUR CAL_TUR;
Это не совсем тот набор, который мы привыкли определять, однако его можно использовать, если нужно прочитать данные, в которых появляются строки типа B2_BEEF.
Числа, которые появляются как аргументы &, автоматически преобразуются в строки. Например, для многонедельной модели можно создать набор периодов с общим именем WEEK1, WEEK2 и т. д., сделав следующую запись:
param T integer > 1; set WEEKS ordered = setof {t in 1..T} "WEEK" & t;
Числовые операнды в & всегда преобразуются с полной точностью (формат%.0g). Таким образом, преобразование дает ожидаемые результаты для конкатенации числовых констант и индексов, которые пробегают наборы целых чисел или констант, как в наших примерах. Однако, точное преобразование вычисленных дробных значений может иногда давать неожиданные результаты. Кажется, что следующий вариант предыдущего примера создаст набор элементов WEEK0.1, WEEK0.2 и т. д.:
param T integer > 1; set WEEKS ordered = setof {t in 1..T} "WEEK" & 0.1*t;
Однако, фактический набор получается другим:
let T := 4; display WEEKS; set WEEKS := WEEK0.1 WEEK0.30000000000000004 WEEK0.2 WEEK0.4;
Поскольку 0,1 не может быть сохранено точно в двоичном представлении, значение 0,1 * 3 немного отличается от 0,3 в «полной» точности. Нет простого способа предсказать это поведение, но его можно предотвратить, указав явное преобразование с помощью sprintf.
Функция sprintf выполняет преобразование формата так же, как printf, за исключением того, что полученная отформатированная строка не отправляется в выходной поток, а вместо этого становится возвращаемым значением функции. В нашем примере WEEK&0.1*t можно заменить на sprintf("WEEK% 3.1f", 0.1*t).
Length, match
Строковая функция length принимает строку в качестве аргумента и возвращает количество символов в ней. Функция match принимает два строковых аргумента и возвращает первую позицию, где второй аргумент появляется как подстрока в первом, или ноль, если второй аргумент никогда не появляется как подстрока в первом. Например:
display {j in FOOD} (length(j), match(j,"H")); : length(j) match(j, ’H’) := BEEF 4 0 CHK 3 2 FISH 4 4 HAM 3 1 MCH 3 3 MTL 3 0 SPG 3 0 TUR 3 0 ;
substr
Функция substr принимает в качестве аргументов строку и одно или два целых числа. Возвращает подстроку первого аргумента, которая начинается с позиции, заданной вторым аргументом. Подстрока имеет длину, заданную третьим аргументом, или простирается до конца строки, если третий аргумент не указан. Пустая строка возвращается, если второй аргумент больше длины первого аргумента, или если третий аргумент меньше 1.
В качестве примера, объединяющего несколько из этих функций, предположим, что необходимо предоставить данные о количестве питательных веществ в каждом продукте:
param: NUTR_FOOD: amt_nutr := A_BEEF 60 B1_BEEF 10 CAL_BEEF 295 CAL_CHK 770 ...
Для этого, в дополнение к объявлениям для параметра amt, используемого в модели:
set NUTR; set FOOD; param amt {NUTR,FOOD} >= 0;
нам нужно объявить набор и параметр для хранения данных «нестандартных»:
set NUTR_FOOD; param amt_nutr {NUTR_FOOD} >= 0;
В этом случае, чтобы использовать модель, необходимо написать назначение, чтобы получить данные из набора NUTR_FOOD и параметра amt_nutr в наборы NUTR и FOOD и параметр amt.
Одним из решений является сначала извлечь наборы, а затем преобразовать параметры:
set NUTR = setof {ij in NUTR_FOOD} substr(ij,1,match(ij,"_")-1); set FOOD = setof {ij in NUTR_FOOD} substr(ij,match(ij,"_")+1); param amt {i in NUTR, j in FOOD} = amt_nutr[i & "_" & j];
В качестве альтернативы можно извлечь наборы и параметры с применением сценария. Например:
param iNUTR symbolic; param jFOOD symbolic; param upos > 0; let NUTR:= {}; let FOOD:= {}; for {ij in NUTR_FOOD} { let upos:= match(ij,"_"); let iNUTR:= substr(ij,1,upos-1); let jFOOD:= substr(ij,upos+1); let NUTR:= NUTR union {iNUTR}; let FOOD:= FOOD union {jFOOD}; let amt[iNUTR,jFOOD] := amt_nutr[ij]; }
В любом из вариантов ошибки, такие как отсутствие _ в элементе NUTR_FOOD, в конечном итоге сигнализируются сообщениями об ошибках.
Sub, gsub
AMPL предоставляет две другие функции, sub и gsub, которые ищут второй аргумент в первом, например, match, но затем заменяют третий аргумент либо для первого вхождения sub, либо для всех найденных вхождений gsub. Второй аргумент всех трех из этих функций на самом деле является регулярным выражением. Если он содержит определенные специальные символы, он интерпретируется как шаблон, который может соответствовать множеству подстрок. Например, шаблон ˆB[0-9]+_ совпадает с любой подстрокой, состоящей в начале строки из B, за которым следуют одна или несколько цифр и затем символ подчеркивания.