我有一组测试用例,我想找到运行它们的最佳顺序。顺序约束为:
A
可以产生另一个测试用例B
所需的输出,因此A
必须在B
之前进行处理< / li>
B
继承自A
,则在A
之前处理B
将会是 nice (但绝不是必须的) 如果我们有这些约束集的 ,我们可以做一个topographical sort并用它来完成,但是将两者结合起来似乎会使事情变得更加棘手,因为它们可以相互矛盾。
我可以看到两种可能的解决方案:
给每个项目2个等级:项目在硬约束树和软约束树中的深度。然后,我们可以基于这些等级进行排序-赋予硬约束等级的排序优先级,并在硬约束等级相等时比较软约束等级。
这肯定会给我们一个有效的顺序,但最终可能会导致很多不必要的软约束。例如,考虑项目A
,B
,C
和D
,其中:
A
必须先于B
C
必须先于D
B
早于C
硬约束排名(A:1,B:2,C:1,D:2
)表示C
和B
之间的硬约束实际上并不存在,当我们遇到时,我们以A,C,B,D
的顺序结束希望使用A,B,C,D
从项目L
的集合中建立订单列表S
:
A
中删除任意项目S
A
中尚未存在的所有L
硬约束的前任,并将它们从S
中删除A
中的所有S
的软约束前任,将它们从S
中删除A
添加到L
S
为空。我认为这会产生一个有效的订单,该订单只会在必要时打破软约束(它确实适用于上面的示例),但是我不相信我们不能做得更好。
找到一种可以完全满足一组约束同时最大程度地满足另一组约束的项目排序问题是否得到了接受?
答案 0 :(得分:0)
根据您的严格约束,使用诸如http://rosettacode.org/wiki/Topological_sort#Python这样的程序进行拓扑排序,该程序输出可以按任何顺序应用的项目。
应用仅在硬约束排序以子顺序划分之后具有相同优先级的项目之间排序的所有软约束。
答案 1 :(得分:0)
在CS stackoverflow上发布此问题后,我们发现这里要查找的是最大无环子图。这是一个NP难题,因此实际上并没有一个快速,最佳的解决方案。
我实现了这个图类,它提供了一个合理的解决方案:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
public class DirectedWeightedGraph<T> {
private final Map<T, Node<T>> nodes = new HashMap<>();
public DirectedWeightedGraph( Collection<T> values ) {
values.forEach( v -> nodes.put( v, new Node<>( v ) ) );
}
/**
* Adds edges to the graph
*
* @param weight The weight of the edges
* @param target A function from edge origin to target. Returns <code>null</code> for no edge.
* @return <code>this</code>
*/
public DirectedWeightedGraph<T> edge( int weight, UnaryOperator<T> target ) {
nodes.forEach( ( o, n ) -> Optional.ofNullable( target.apply( o ) )
.ifPresent( t -> n.edgeTo( nodes.get( t ), weight ) ) );
return this;
}
/**
* Removes cycles from the graph by removing the lowest-weight edge in each cycle
*
* @return <code>this</code>
*/
public DirectedWeightedGraph<T> removeCycles() {
nodes.values().forEach( n -> n.removeCycles() );
return this;
}
/**
* @return The graph node values, in an order such that linked-<i>to</i> values come before
* linked-<i>from</i> values. Note that cycles in the graph will result in incorrect
* results
* @see #removeCycles()
*/
public List<T> order() {
List<T> order = new ArrayList<>();
Set<T> values = new HashSet<>( nodes.keySet() );
while ( !values.isEmpty() ) {
T t = values.iterator().next();
nodes.get( t ).add( order, values );
}
return order;
}
@Override
public String toString() {
return nodes.values().stream()
.map( n -> n.toString() )
.sorted()
.collect( Collectors.joining( "\n" ) );
}
private static class Node<T> {
private enum State {
/**
* Node has not been visited yet
*/
UNKNOWN,
/**
* Node is being explored
*/
EXPLORING,
/**
* Node and all descendants are not part of a cycle
*/
ACYCLIC
}
public final T value;
private final Set<Edge<T>> edges = new HashSet<>();
private State state = State.UNKNOWN;
private Edge<T> lastExplored;
public Node( T value ) {
this.value = value;
}
public Node<T> edgeTo( Node<T> target, int weight ) {
edges.add( new Edge<>( this, target, weight ) );
return this;
}
public Node<T> remove( Edge<T> e ) {
edges.remove( e );
return this;
}
public void removeCycles() {
if ( state == State.ACYCLIC ) {
return;
}
else if ( state == State.UNKNOWN ) {
// recurse!
state = State.EXPLORING;
Set<Edge<T>> iterate = new HashSet<>( edges );
for ( Edge<T> e : iterate ) {
if ( edges.contains( e ) ) {
lastExplored = e;
e.to.removeCycles();
}
}
lastExplored = null;
state = State.ACYCLIC;
}
else if ( state == State.EXPLORING ) {
// we've been here before!
// Trace through the lastExplored edges till we're back here again to find the minimum
// weight edge
Edge<T> minimum = lastExplored;
Node<T> n = lastExplored.to;
while ( n != this ) {
if ( minimum.weight > n.lastExplored.weight ) {
minimum = n.lastExplored;
}
n = n.lastExplored.to;
}
// delete the lightest edge in the cycle. Choosing the optimal edge to delete is the bit
// that makes this problem NP-hard, so let's not bother worrying about it too much
minimum.delete();
}
}
/**
* Adds the node's prerequisites (the nodes that it links to), then itself to the list
*
* @param order The list to build
* @param values The set of nodes not yet in the last
*/
public void add( List<T> order, Set<T> values ) {
if ( values.remove( value ) ) {
edges.forEach( e -> e.to.add( order, values ) );
order.add( value );
}
}
@Override
public String toString() {
return value + edges.stream().map( e -> "\n\t" + e ).collect( Collectors.joining() );
}
}
private static class Edge<T> {
public final Node<T> from;
public final Node<T> to;
public final int weight;
public Edge( Node<T> from, Node<T> to, int weight ) {
this.from = from;
this.to = to;
this.weight = weight;
}
public void delete() {
from.remove( this );
}
@Override
public String toString() {
return from.value + "-" + weight + "->" + to.value;
}
}
}
硬约束边缘的权重高于软约束的权重,因此在破坏图中的循环时,我们将始终删除软约束。