Индекс строкового значения в массиве MiniZinc
Вопрос
Имеется массив строк MiniZinc:
int: numStats;
set of int: Stats = 1..numStats;
array[Stats] of string: statNames;
... с данными, загруженными из файла данных MiniZinc:
numStats = 3;
statNames = ["HEALTH", "ARMOR", "MANA"];
Как можно найти индекс конкретной строки в массиве? Например, этот ARMOR находится в позиции 2.
Контекст
Мне нужно найти оптимальный выбор предметов с учетом некоторых ограничений их характеристик. Эта информация хранится в двумерном массиве, объявленном следующим образом:
int: numItems;
set of int: Items = 1..numItems;
array[Items, Stats] of float: itemStats;
Поэтому, чтобы написать ограничение, скажем, для минимального количества ARMOR, полученного через выбранные элементы, мне нужно знать, что ARMOR имеет индекс 2 во внутреннем массиве.
Поскольку файл данных создается внешней программой, а число и порядок статистики являются динамическими, я не могу жестко закодировать индексы в ограничениях.
Одно решение (это не сработает в моем случае)
В учебнике MiniZinc используется интересный трюк для достижения чего-то похожего:
set of int: Colors = 1..3;
int: red = 1;
int: yellow = 2;
int: blue = 3;
array[Colors] of string: name = ["red", "yellow", "blue"];
var Colors: x;
constraint x != red;
output [ name[fix(x)] ];
К сожалению, поскольку объявления переменных в файлах данных MiniZinc недопустимы, этот прием не будет работать в моем случае.
4 ответа
Вы можете написать свою собственную пользовательскую функцию, чтобы получить индекс строки в массиве строк:
function int: getIndexOfString(string: str,
array[int] of string: string_array) =
sum( [ if str = string_array[i]
then i
else 0 endif
| i in index_set(string_array) ]
);
В этой функции я создаю массив целых чисел, где целое число в позиции i
либо равен индексу str
если string_array[i]=str
а также 0
иначе. Например, для вашего образца массива строк ["HEALTH", "ARMOR", "MANA"]
и ул ARMOR
результирующий массив int будет [0,2,0]
,
Вот почему я могу просто суммировать по массиву int, чтобы получить индекс строки. Если строка не встречается, возвращаемое значение 0
Это нормально, так как индексы в MiniZinc по умолчанию начинаются с 1.
Вот как вы можете вызвать функцию выше для вашего первого примера:
int: numStats;
set of int: Stats = 1..numStats;
array[Stats] of string: statNames;
numStats = 3;
statNames = ["HEALTH", "ARMOR", "MANA"];
var int: indexOfArmor;
constraint
indexOfArmor = getIndexOfString("ARMOR",statNames);
solve satisfy;
Однако обратите внимание, что функция выше ограничена и имеет некоторые недостатки. Во-первых, если у вас есть несколько вхождений строки в массиве, то вы получите недопустимый индекс (сумма всех индексов, где str
произошло). Кроме того, если у вас есть собственный индекс для массива строк (скажем, (2..6)
), то вам нужно будет адаптировать функцию.
Другой, более чистый вариант - написать функцию, которая использует рекурсивную вспомогательную функцию:
% main function
function int: index_of(string: elem, array[int] of string: elements) =
let {
int: index = length(elements);
} in % calls the helper function with the last index
get_index(elem, elements, index)
;
% recursive helper function
function int: get_index(string: elem, array[int] of string: elements, int: index) =
if index == 0
then -1 % the element was not found (base case of recursion)
elseif elements[index] == elem
then index % the element was found
else
get_index(elem, elements, index - 1) % continue searching
endif
;
Вспомогательная функция рекурсивно выполняет итерацию по массиву, начиная с последнего элемента, и когда она находит элемент, она возвращает индекс. Если элемент не был найден в массиве, то -1
возвращается Кроме того, вы также можете бросить утверждение, следуя предложению Patrick Trentin, заменив then -1
с then assert(false, "unknown element: " + elem)
,
Пример вызова этой функции:
set of int: Customers = 1..5;
array[Customers] of string: ids = ["a-1", "a-2", "a-3", "a-4", "a-5"];
var int: index = index_of("a-3", ids);
var int: unknown_index = index_of("x-3", ids);
где index
будет назначен 3
а также unknown_index
будет -1
,
Альтернативный подход, предложенный Andrea Rendl-Pitrey, следующий:
array[int] of string: statNames = array1d(10..12, ["HEALTH", "ARMOR", "MANA"]);
var int: indexOfArmor =
sum([i | i in index_set(statNames) where statNames[i] = "ARMOR"]);
solve satisfy;
output [
"indexOfArmor=", show(indexOfArmor), "\n",
];
какие выводы:
~$ mzn2fzn example.mzn ; flatzinc example.fzn
indexOfArmor = 11;
----------
примечание: что var
может быть исключен из декларацииindexOfArmor
, так как индекс может быть вычислен статически. Я сохранил это здесь только для выходных целей.
Лучшее решение - объявить новый predicate
:
predicate index_of_str_in_array(var int: idx,
string: str,
array[int] of string: arr) =
assert(
not exists(i in index_set(arr), j in index_set(arr))
(i != j /\ arr[i] = str /\ arr[j] = str),
"input string occurs at multiple locations",
assert(
exists(i in index_set(arr))
(arr[i] = str),
"input string does not occur in the input array",
exists(i in index_set(arr))
(arr[i] = str /\ i = idx)
));
который обеспечивает соблюдение обоих следующих условий:
str
происходит по крайней мере один раз вarr
str
не происходит несколько раз вarr
например
predicate index_of_str_in_array(var int: idx,
string: str,
array[int] of string: arr) =
...
array[10..13] of string: statNames =
array1d(10..13, ["HEALTH", "ARMOR", "MANA", "ATTACK"]);
var int: indexOfArmor;
constraint index_of_str_in_array(indexOfArmor, "ARMOR", statNames);
solve satisfy;
output [
"indexOfArmor=", show(indexOfArmor), "\n",
];
выходы
~$ mzn2fzn example.mzn ; flatzinc example.fzn
indexOfArmor = 11;
----------
Если один изменится statNames
следующим образом
array[10..13] of string: statNames =
array1d(10..13, ["HEALTH", "ARMOR", "MANA", "ARMOR"]);
затем mzn2fzn
обнаруживает нарушение утверждения:
~$ mzn2fzn example.mzn ; flatzinc example.fzn
MiniZinc: evaluation error:
example.mzn:24:
in call 'index_of_str_in_array'
example.mzn:4:
in call 'assert'
Assertion failed: input string occurs at multiple locations
flatzinc:
example.fzn: cannot open input file: No such file
Аналогичный результат будет получен при поиске индекса строки, которой нет в массиве. Это условие, конечно, может быть удалено, если не нужно.
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: старые версии mzn2fzn
не проверять, что заявленный index-set
из array of strings
переменная соответствует index-set
из array of strings literal
это присваивается ему. Это правило применяется в более новых версиях, так же как и для других типов данных.
Согласно этому другому посту о Stackru, в MiniZinc нет способа конвертировать строки в целые числа, только наоборот. Сначала вам нужно предварительно обработать данные на другом языке и превратить их в целые числа. Однако вы можете превратить эти целые числа в строку, как только закончите в MiniZinc.
Однако вы можете загрузить файлы MiniZinc вместо файлов данных, если хотите. Используйте синтаксис include для включения любого файла.mzn.