我在Perl中有以下一组约束(只是一组示例约束,而不是我真正需要的约束):
$a < $b
$b > $c
$a is odd => $a in [10..18]
$a > 0
$c < 30
我需要找到符合约束条件的列表($a, $b, $c)
。我天真的解决方案是
sub check_constraint {
my ($a, $b, $c) = @_;
if !($a < $b) {return 0;}
if !($b > $c) {return 0;}
if (($a % 2) && !(10 <= $a && $a <= 18)) {return 0;}
if !($a > 0) {return 0;}
if !($c < 30) {return 0;}
return 1;
}
sub gen_abc {
my $c = int rand 30;
my $b = int rand $c;
my $a = int rand $b;
return ($a, $b, $c);
}
($a, $b, $c) = &gen_abc();
while (!&check_constraint($a, $b, $c)) {
($a, $b, $c) = &gen_abc();
}
现在,这个解决方案并不能保证结束,而且一般来说效率很低。在Perl中有更好的方法吗?
编辑:我需要这个随机测试生成器,因此解决方案需要使用随机函数,例如rand()
。完全确定性的解决方案是不够的,尽管如果该解决方案可以给我一个可能的组合列表,我可以随机选择一个索引:
@solutions = &find_allowed_combinations(); # solutions is an array of array references
$index = int rand($#solutions);
($a, $b, $c) = @$solution[$index];
编辑2:此处的约束很容易用暴力解决。但是,如果有许多变量具有大范围的可能值,则不能选择强力。
答案 0 :(得分:14)
此优化问题的主要挑战是数学性质。
我可以根据您对gen_abc
方法的定义推断,您的目标是通过查找各种变量($a
,$b
等的边界间隔来修剪搜索空间。 )
最佳策略是从完整的约束集中提取尽可能多的线性约束,尝试推断边界(使用linear programming技术,见下文),然后继续详尽(针对修剪变量空间的试验和错误测试。
典型的linear programming problem格式为:
minimize (maximize) <something>
subject to <constraints>
例如,给定三个变量a
,b
和c
,以及以下线性约束:
<<linear_constraints>>::
$a < $b
$b > $c
$a > 0
$c < 30
您可以找到$a
,$b
和$c
的上限和下限,如下所示:
lower_bound_$a = minimize $a subject to <<linear_constraints>>
upper_bound_$a = maximize $a subject to <<linear_constraints>>
lower_bound_$b = minimize $b subject to <<linear_constraints>>
upper_bound_$b = maximize $b subject to <<linear_constraints>>
lower_bound_$c = minimize $c subject to <<linear_constraints>>
upper_bound_$c = maximize $c subject to <<linear_constraints>>
在Perl中,您可以将Math::LP用于此目的。
示例强>
线性约束的形式为“C eqop C1×$V1 ± C2×$V2 ± C3×$V3 ...
”,其中
eqop
是<
,>
,==
,>=
,<=
$V1
,$V2
等是变量,C
,C1
,C2
等是常量,可能等于0. 例如,给定......
$a < $b
$b > $c
$a > 0
$c < 30
...将所有变量(及其系数)移到不等式的左边,并将不等式右边的唯一常量移动:
$a - $b < 0
$b - $c > 0
$a > 0
$c < 30
...并调整约束,以便仅使用=
,<=
和>=
(in)等式(假设我们的变量是离散的,即整数值):
......就是这样,
$a - $b <= -1
$b - $c >= 1
$a >= 1
$c <= 29
...然后写下这样的东西:
use Math::LP qw(:types); # imports optimization types
use Math::LP::Constraint qw(:types); # imports constraint types
my $lp = new Math::LP;
my $a = new Math::LP::Variable(name => 'a');
my $b = new Math::LP::Variable(name => 'b');
my $c = new Math::LP::Variable(name => 'c');
my $constr1 = new Math::LP::Constraint(
lhs => make Math::LP::LinearCombination($a, 1, $b, -1), # 1*$a -1*$b
rhs => -1,
type => $LE,
);
$lp->add_constraint($constr1);
my $constr2 = new Math::LP::Constraint(
lhs => make Math::LP::LinearCombination($b, 1, $c, -1), # 1*$b -1*$c
rhs => 1,
type => $GE,
);
$lp->add_constraint($constr2);
...
my $obj_fn_a = make Math::LP::LinearCombination($a,1);
my $min_a = $lp->minimize_for($obj_fn_a);
my $max_a = $lp->maximize_for($obj_fn_a);
my $obj_fn_b = make Math::LP::LinearCombination($b,1);
my $min_b = $lp->minimize_for($obj_fn_b);
my $max_b = $lp->maximize_for($obj_fn_b);
...
# do exhaustive search over ranges for $a, $b, $c
当然,上述内容可以推广到任意数量的变量V1
,V2
,......(例如$a
,$b
,$c
,$d
,...),包含任何系数C1
,C2
,...(例如-1,1,0,123等)和任何常数值{{ 1}}(例如-1,1,30,29等),只要您可以将约束表达式解析为相应的矩阵表示,例如:
C
应用您提供的示例,
V1 V2 V3 C
[ C11 C12 C13 <=> C1 ]
[ C21 C22 C23 <=> C2 ]
[ C31 C32 C33 <=> C3 ]
... ... ... ... ... ...
注意强>
作为旁注,如果执行非确定性( $a $b $c C
[ 1 -1 0 <= -1 ] <= plug this into a Constraint + LinearCombination
[ 0 1 -1 >= 1 ] <= plug this into a Constraint + LinearCombination
[ 1 0 0 >= 1 ] <= plug this into a Constraint + LinearCombination
[ 0 0 1 <= 29 ] <= plug this into a Constraint + LinearCombination
- )测试,跟踪(例如在散列中)rand
元组的跟踪可能是也可能不是一个好主意已经过测试,以避免再次测试,当且仅当:
答案 1 :(得分:2)
我使用Data::Constraint。您编写实现单个约束的小子例程,然后连续应用所需的所有约束。我在“动态子程序”一章的Mastering Perl中讨论了这一点。
use Data::Constraint; Data::Constraint->add_constraint( 'a_less_than_b', run => sub { $_[1] < $_[2] }, description => "a < b", ); Data::Constraint->add_constraint( 'b_greater_than_c', run => sub { $_[2] > $_[3] }, description => "b > c", ); Data::Constraint->add_constraint( 'a_greater_than_0', run => sub { $_[1] > 0 }, description => "a > 0", ); Data::Constraint->add_constraint( 'c_less_than_30', run => sub { $_[3] < 30 }, description => "c < 30", ); Data::Constraint->add_constraint( 'a_is_odd_between_10_18', run => sub { return 1 if( $_[1] < 10 or $_[1] > 18); return 0 unless $_[1] % 2, }, description => "a is odd between 10 and 18", ); for ( 1 .. 10 ) { my( $a, $b, $c ) = gen_abc(); print "a = $a | b = $b | c = $c\n"; foreach my $name ( Data::Constraint->get_all_names ) { print "\tFailed $name\n" unless Data::Constraint->get_by_name( $name )->check( $a, $b, $c ), } } sub gen_abc { my $c = int rand 30; my $b = int rand $c; my $a = int rand $b; return ($a, $b, $c); }
这样做意味着很容易检查结果以查看失败而不是整体失败:
a = 2 | b = 4 | c = 5 Failed a_less_than_b Failed b_greater_than_c a = 0 | b = 0 | c = 2 Failed a_greater_than_0 Failed a_less_than_b Failed b_greater_than_c a = 0 | b = 0 | c = 2 Failed a_greater_than_0 Failed a_less_than_b Failed b_greater_than_c a = 7 | b = 14 | c = 25 Failed a_less_than_b Failed b_greater_than_c a = 0 | b = 0 | c = 29 Failed a_greater_than_0 Failed a_less_than_b Failed b_greater_than_c a = 0 | b = 0 | c = 20 Failed a_greater_than_0 Failed a_less_than_b Failed b_greater_than_c a = 0 | b = 4 | c = 22 Failed a_greater_than_0 Failed a_less_than_b Failed b_greater_than_c a = 4 | b = 16 | c = 28 Failed a_less_than_b Failed b_greater_than_c a = 0 | b = 22 | c = 26 Failed a_greater_than_0 Failed a_less_than_b Failed b_greater_than_c a = 0 | b = 3 | c = 6 Failed a_greater_than_0 Failed a_less_than_b Failed b_greater_than_c
如果你想要更多硬核,我的Brick模块会处理约束树,包括修剪和分支。这些对于更大的系统是有意义的,在这些系统中,您将混合和匹配不同情况的各种约束,因为大多数代码都是设置约束对象。如果你只有一个情况,你可能只想坚持你所拥有的。
祝你好运,:))
答案 2 :(得分:1)
“真正的”答案需要解析关系的表达式和推理。除此之外,我建议使用值空间的系统遍历,而不是随意尝试值。例如,
my $count = 0;
for (my $c = 0; $c < 30 && $count < $SOMELIMIT; ++$c) {
# check all other constraints on only $c here
# next if any fail
for (my $b = $c + 1; $b < $UPPERLIMIT && $count < $SOMELIMIT; ++$b) {
# check all other constraints on only $b and $c here
# next if any fail
for (my $a = 1; $a < $b && $count < $SOMELIMIT; ++$a) {
#check all remaining constraints on $a, $b, and $c here
# next if any fail
# now use surviving combinations
++$count;
}
}
}
我将具有最多个别约束的变量置于最外层,接下来约束最接近等等。
至少使用这种结构,你不会多次测试同一个组合(因为随机版很可能会这样做),如果你看它运行,你可能会看到模式出现,让你缩短执行时间。 / p>
答案 3 :(得分:1)
我不确定你会找到一个简单的答案(虽然我想证明是错的!)。
您的问题似乎非常适合genetic algorithm。健身 函数应该易于编写,每个满意约束只得1,否则为0。 AI::Genetic似乎是一个可以帮助您的模块,无论是编写代码还是理解您需要编写的内容。
这应该比蛮力方法更快。
答案 4 :(得分:0)
似乎Simo::Constrain就是你想要的
答案 5 :(得分:0)
我会创建一个生成一堆有效列表的算法,随机生成或不生成(它应该是微不足道的),将它们写入文件,然后使用该文件来提供测试程序,这样他就可以随机选择它想要的任何清单。