我给了食谱如何制作物品。食谱的格式为:{element that is being crafter}: {list of elements, that is needed}
。在我能够制作元素x
之前,我需要知道如何制作它所构成的元素。所以我想找到以什么顺序学习食谱。
对于有效的输入,如下所示一切正常:
// Input:
{
"F1: F2 F3 F4", "F5: F6 F4", "F6: F7 F8 F4", "F2: F3 F8 F4", "F8: F4",
"F9: F4", "F7: F4", "F10: F7 F4", "F11: F4", "F4:", "F3: F6"
}
// Output:
[F4, F7, F8, F6, F3, F2, F1, F5, F9, F10, F11]
问题是,这项任务更复杂。我不时丢失一些食谱或你的食谱无效。输入无效的示例:{ "F1: F2", "F2: F1" }
。
mp
包含食谱名称作为键,元素作为值,labels
是唯一的mp
键,result
将包含答案。如果满足无限循环,我正在寻找一种返回empty
结果的方法。
private void getArray(HashMap<String, ArrayList<String>> mp,
ArrayList<String> result, ArrayList<String> labels) {
for (String a : labels) {
if (mp.get(a) != null)
for (String label : mp.get(a))
getArray(mp, result, label);
if (!result.contains(a))
result.add(a);
}
}
private void getArray(HashMap<String, ArrayList<String>> mp,
ArrayList<String> result, String label) {
if (result.contains(label))
return;
if (mp.get(label) == null) {
result.add(label);
return;
}
for (String l : mp.get(label))
getArray(mp, result, l);
if (!result.contains(label))
result.add(label);
}
问题解决了。
对于任何绊倒这个的谷歌来说,这就是我提出的:
/** <p>
* <b>Topological sort</b> solves a problem of - finding a linear ordering
* of the vertices of <i>V</i> such that for each edge <i>(i, j) ∈ E</i>,
* vertex <i>i</i> is to the left of vertex <i>j</i>. (Skiena 2008, p. 481)
* </p>
*
* <p>
* Method is derived from of <a
* href="http://en.wikipedia.org/wiki/Topological_sort#Algorithms" > Kahn's
* pseudo code</a> and traverses over vertices as they are returned by input
* map. Leaf nodes can have null or empty values. This method assumes, that
* input is valid DAG, so if cyclic dependency is detected, error is thrown.
* tSortFix is a fix to remove self dependencies and add missing leaf nodes.
* </p>
*
* <pre>
* // For input with elements:
* { F1=[F2, F3, F4], F10=[F7, F4], F11=[F4], F2=[F3, F8, F4], F3=[F6],
* F4=null, F5=[F6, F4], F6=[F7, F8, F4], F7=[F4], F8=[F4], F9=[F4]}
*
* // Output based on input map type:
* HashMap: [F4, F11, F8, F9, F7, F10, F6, F5, F3, F2, F1]
* TreeMap: [F4, F11, F7, F8, F9, F10, F6, F3, F5, F2, F1]
* </pre>
*
* @param g
* <a href="http://en.wikipedia.org/wiki/Directed_acyclic_graph"
* > Directed Acyclic Graph</a>, where vertices are stored as
* {@link java.util.HashMap HashMap} elements.
*
* @return Linear ordering of input nodes.
* @throws Exception
* Thrown when cyclic dependency is detected, error message also
* contains elements in cycle.
*
*/
public static <T> ArrayList<T> tSort(java.util.Map<T, ArrayList<T>> g)
throws Exception
/**
* @param L
* Answer.
* @param S
* Not visited leaf vertices.
* @param V
* Visited vertices.
* @param P
* Defined vertices.
* @param n
* Current element.
*/
{
java.util.ArrayList<T> L = new ArrayList<T>(g.size());
java.util.Queue<T> S = new java.util.concurrent.LinkedBlockingDeque<T>();
java.util.HashSet<T> V = new java.util.HashSet<T>(),
P = new java.util.HashSet<T>();
P.addAll(g.keySet());
T n;
// Find leaf nodes.
for (T t : P)
if (g.get(t) == null || g.get(t).isEmpty())
S.add(t);
// Visit all leaf nodes. Build result from vertices, that are visited
// for the first time. Add vertices to not visited leaf vertices S, if
// it contains current element n an all of it's values are visited.
while (!S.isEmpty()) {
if (V.add(n = S.poll()))
L.add(n);
for (T t : g.keySet())
if (g.get(t) != null && !g.get(t).isEmpty() && !V.contains(t)
&& V.containsAll(g.get(t)))
S.add(t);
}
// Return result.
if (L.containsAll(P))
return L;
// Throw exception.
StringBuilder sb = new StringBuilder(
"\nInvalid DAG: a cyclic dependency detected :\n");
for (T t : P)
if (!L.contains(t))
sb.append(t).append(" ");
throw new Exception(sb.append("\n").toString());
}
/**
* Method removes self dependencies and adds missing leaf nodes.
*
* @param g
* <a href="http://en.wikipedia.org/wiki/Directed_acyclic_graph"
* > Directed Acyclic Graph</a>, where vertices are stored as
* {@link java.util.HashMap HashMap} elements.
*/
public static <T> void tSortFix(java.util.Map<T, ArrayList<T>> g) {
java.util.ArrayList<T> tmp;
java.util.HashSet<T> P = new java.util.HashSet<T>();
P.addAll(g.keySet());
for (T t : P)
if (g.get(t) != null || !g.get(t).isEmpty()) {
(tmp = g.get(t)).remove(t);
for (T m : tmp)
if (!P.contains(m))
g.put(m, new ArrayList<T>(0));
}
}
答案 0 :(得分:10)
您要解决的问题称为topological sort。 Kahn's algorithm解决了问题,同时还检测到无效输入(即包含循环)。
答案 1 :(得分:3)
快速执行此操作的方法是记住您已经看过的项目集,如果您要计算该列表中已有项目的要求,则只需抛出异常即可。这肯定表明某种循环,我们可以假设这是一件坏事。
更高级的解决方案,如果对象图中的循环是允许的,那么不仅要存储项目,还要将它们映射到他们的解决方案。由于您正在计算解决方案,因此可能需要在地图中弹出Future以表明评估可能尚未完成。然后,只要您访问给定项目,就可以在地图中添加“解决方案占位符”。因此,无限循环工作正常(查看您无关紧要的无效案例):
这当然代表了解决方案对象模型中的循环,但这实际上是输入的合法表示。如果你以图形方式显示它,也许作为一次扩展一个级别的树,这也会适当地呈现(“我如何创建F1?你需要创建一个F2。我如何创建那个< / em>?你需要创建一个F1“,依此类推,因为用户扩展了树的数量级别。”
(请注意,虽然这听起来很愚蠢,但它实际上可以反映一些有效的场景;例如在Fallout:New Vegas中,几种制作配方在不同的能量弹药类型之间进行转换。因此,要制作一个Microfusion Cell,您需要三个小能量细胞。制作三个小能量单元,你需要一个Microfusion Cell。这会在图中创建一个循环,但在这种情况下实际上是有效的输入。我只是指出这个,以防你假设循环总是错误输入。)
如果配方有可能有替代品,也许在这两种方法之间的某个地方是最好的选择。遵循所有替代路径,但如果您进入无限循环,请停止跟随该路径并与其他路径一起使用。