订购受限制的列表列表

时间:2013-09-02 23:13:43

标签: scala recursion matrix backtracking constraint-programming

我遇到了一个令人惊讶的挑战性问题,它安排了一个类似矩阵(列表的列表)的值受到以下约束(或决定它是不可能的):

m个随机生成的行的矩阵,最多包含n个不同的值(行内没有重复),排列矩阵,使得以下成员保持(如果可能):

1)矩阵必须是“下三角”;行必须以递增的长度排序,因此唯一的“间隙”位于右上角

2)如果某个值出现在多个行中,则它必须位于同一列中(即允许重新排列行中值的顺序)。

需要在函数式语言(例如Scala)中表达问题/解决方案。

示例1 - 具有解决方案

A B
C E D
C A B

成为(作为一个解决方案)

A B
E D C
A B C

因为A,B和C分别出现在第1,2和3列中。

示例2 - 没有解决方案

A B C
A B D
B C D

没有解决方案,因为约束要求第三行在第三行中具有C和D. 列不可能。

2 个答案:

答案 0 :(得分:1)

我认为这是一个有趣的问题,并在MiniZinc(一个非常高级别的约束编程系统)中建立了概念验证版本,这似乎是正确的。我不确定它是否有用,说实话,我不确定它是否适用于最大的问题实例。

第一个问题实例 - 根据这个模型 - 有4个解决方案:

 B A _
 E D C
 B A C
 ----------
 B A _
 D E C
 B A C
 ----------
 A B _
 E D C
 A B C
 ----------
 A B _
 D E C
 A B C

第二个例子被认为是不可满足的(应该如此)。

完整的模型在这里:http://www.hakank.org/minizinc/ordering_a_list_of_lists.mzn

基本方法是使用矩阵,其中较短的行填充空值(此处为0,为零)。问题实例是矩阵“矩阵”;得到的解决方案在矩阵“x”中(决策变量,作为整数,然后转换为输出中的字符串)。然后有一个辅助矩阵,“perms”,用于确保“x”中的每一行是“矩阵”中相应行的排列,用谓词“permutation3”完成。还有一些其他辅助数组/集合简化了约束。

主要的MiniZinc型号(无输出)如下所示。

以下是一些可能会使模型失效的评论/假设:

  • 这只是一个概念验证模型,因为我觉得它很有趣 问题

  • 我假设矩阵中的行(问题数据)已经被排序了 按尺寸(下三角形)。这应该很容易作为预处理步骤 不需要约束编程的地方。

  • 较短的列表用0(零)填充,因此我们可以使用矩阵。

  • 因为MiniZinc是一种强类型语言而且不支持 符号,我们只定义整数1..5来表示字母A..E。 使用传统时,使用整数也很有用 约束编程系统。

    % The MiniZinc model (sans output)
    include "globals.mzn"; 
    int: rows = 3;
    int: cols = 3;

    int: A = 1;
    int: B = 2;
    int: C = 3;
    int: D = 4;
    int: E = 5;
    int: max_int = E;

    array[0..max_int] of string: str = array1d(0..max_int, ["_", "A","B","C","D","E"]);

    % problem A (satifiable)
    array[1..rows, 1..cols] of int: matrix = 
       array2d(1..rows, 1..cols,
       [
         A,B,0, % fill this shorter array with "0"
         E,D,C,
         A,B,C,
       ]);

     % the valid values (we skip 0, zero)
     set of int: values = {A,B,C,D,E};

     % identify which rows a specific values are.
     % E.g. for problem A: 
     %     value_rows: [{1, 3}, {1, 3}, 2..3, 2..2, 2..2]
     array[1..max_int] of set of int: value_rows = 
           [ {i | i in 1..rows, j in 1..cols where matrix[i,j] = v} | v in values];


     % decision variables
     % The resulting matrix
     array[1..rows, 1..cols] of var 0..max_int: x;

     % the permutations from matrix to x
     array[1..rows, 1..cols] of var 0..max_int: perms;


     %
     % permutation3(a,p,b) 
     %
     % get the permutation from a  b using the permutation p.
     %  
     predicate permutation3(array[int] of var int: a,
                            array[int] of var int: p,
                            array[int] of var int: b) =
         forall(i in index_set(a)) (
            b[i] = a[p[i]]
         )
     ;

     solve satisfy;

     constraint
        forall(i in 1..rows) (
           % ensure unicity of the values in the rows in x and perms (except for 0)
           alldifferent_except_0([x[i,j] | j in 1..cols]) /\
           alldifferent_except_0([perms[i,j] | j in 1..cols]) /\          
           permutation3([matrix[i,j] | j in 1..cols], [perms[i,j] | j in 1..cols], [x[i,j] | j in 1..cols])
        )
        /\ % zeros in x are where there zeros are in matrix
        forall(i in 1..rows, j in 1..cols) (
           if matrix[i,j] = 0 then
              x[i,j] = 0
           else
              true
           endif
        )

        /\ % ensure that same values are in the same column:
           %  - for each of the values
           %    - ensure that it is positioned in one column c
           forall(k in 1..max_int where k in values) (
              exists(j in 1..cols) (
                 forall(i in value_rows[k]) (
                   x[i,j] = k 
                 )
              )
           )
       ;

       % the output
       % ...

答案 1 :(得分:0)

我需要一个函数式语言(XQuery)的解决方案,所以我首先在Scala中实现了它,因为它具有表现力,我发布了下面的代码。它使用蛮力,广度优先的方式搜索解决方案。我对单个解决方案(如果存在)感兴趣,因此该算法会抛弃额外的解决方案。

def order[T](listOfLists: List[List[T]]): List[List[T]] = {

def isConsistent(list: List[T], listOfLists: List[List[T]]) = {
  def isSafe(list1: List[T], list2: List[T]) =
    (for (i <- list1.indices; j <- list2.indices) yield
      if (list1(i) == list2(j)) i == j else true
      ).forall(_ == true)

  (for (row <- listOfLists) yield isSafe(list, row)).forall(_ == true)
}


def solve(fixed: List[List[T]], remaining: List[List[T]]): List[List[T]] =
  if (remaining.isEmpty)
    fixed        // Solution found so return it
  else
    (for {
      permutation <- remaining.head.permutations.toList
      if isConsistent(permutation, fixed)
      ordered = solve(permutation :: fixed, remaining.tail)
      if !ordered.isEmpty
    } yield ordered) match {
      case solution1 :: otherSolutions =>       // There are one or more solutions so just return one
        solution1

      case Nil =>   // There are no solutions
        Nil
    }


// Ensure each list has unique items (i.e. no dups within the list)
  require (listOfLists.forall(list => list == list.distinct))

  /* 
   * The only optimisations applied to an otherwise full walk through all solutions is to sort the list of list so that the lengths 
   * of the lists are increasing in length and then starting the ordering with the first row fixed i.e. there is one degree of freedom
   * in selecting the first row; by having the shortest row first and fixing it we both guarantee that we aren't disabling a solution from being
   * found (i.e. by violating the "lower triangular" requirement) and can also avoid searching through the permutations of the first row since
   * these would just result in additional (essentially duplicate except for ordering differences) solutions.
   */
    //solve(Nil, listOfLists).reverse           // This is the unoptimised version
    val sorted = listOfLists.sortWith((a, b) => a.length < b.length)
    solve(List(sorted.head), sorted.tail).reverse
}