MiniZinc数组中字符串值的索引

时间:2016-03-02 21:40:04

标签: constraints minizinc

问题

给出一个MiniZinc字符串数组:

int: numStats;
set of int: Stats = 1..numStats;
array[Stats] of string: statNames;

...从MiniZinc数据文件加载数据:

numStats = 3;
statNames = ["HEALTH", "ARMOR", "MANA"];

如何查找数组中特定字符串的索引?例如,ARMOR位于第2位。

上下文

我需要针对其统计数据的某些限制找到最佳项目选择。此信息存储在声明如下的2D数组中:

int: numItems;
set of int: Items = 1..numItems;
array[Items, Stats] of float: itemStats;

因此,为了在通过所选项获得的最小ARMOR数量上写一个约束,我需要知道ARMOR在内部数组中有索引2。

由于数据文件是由外部程序生成的,并且统计数据的数量和顺序是动态的,我无法对约束中的索引进行硬编码。

一种解决方案(在我的情况下不起作用)

MiniZinc tutorial使用了一个有趣的技巧来实现类似的东西:

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 个答案:

答案 0 :(得分:3)

您可以编写自己的自定义函数来获取字符串数组中字符串的索引:

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"]和str 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)),则需要调整该函数。

答案 1 :(得分:1)

根据Stackoverflow上的this其他帖子,没有办法在MiniZinc中将字符串转换为整数,只是相反。您需要先用其他语言预处理数据并将其转换为整数。但是,一旦完成MiniZinc,你就可以将这些整数转换为字符串。

但是,如果您愿意,可以加载MiniZinc文件而不是数据文件。使用include语法包含任何.mzn文件。

答案 2 :(得分: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)
    ));

强制执行以下两个条件:

  • strarr
  • 中至少出现一次
  • strarr
  • 中不会多次出现

<强> e.g

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的{​​{1}}匹配正在分配给它的array of strings literal。此规则在较新版本上强制执行,因为它对其他数据类型也有效。

答案 3 :(得分:0)

另一个更简洁的选择是编写一个使用递归帮助器函数的函数:

% 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  
;

helper函数从最后一个元素开始,对数组进行递归迭代,并在找到该元素时返回索引。如果在数组中未找到该元素,则返回-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