Как избежать бесконечного цикла при создании планировщика - PullRequest
5 голосов
/ 10 ноября 2010

Введение в проблему:

Мне даны рецепты изготовления предметов. Рецепт в формате: {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);
}

Редактировать

Проблема решена.

Для любого Google, который наткнулся на это, вот что я придумал:

<code>/** <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]
 * 
* * @param g * Направленный ациклический граф , где вершины хранятся как * {@link java.util.HashMap HashMap} элементы. * * @return Линейное упорядочение входных узлов. * Исключения @throws * Брошенный, когда циклическая зависимость обнаружена, сообщение об ошибке также * содержит элементы в цикле. * * / public static ArrayList tSort (java.util.Map > g) исключение / ** * @param L * Ответ. * @param S * Не посещенные вершины листа. * @param V * Посещенные вершины. * @param P * Определенные вершины. * @param n * Текущий элемент. * / { java.util.ArrayList L = новый ArrayList (g.size ()); java.util.Queue S = новый java.util.concurrent.LinkedBlockingDeque (); java.util.HashSet V = новый java.util.HashSet (), P = новый java.util.HashSet (); P.addAll (g.keySet ()); Т н; // Найти листовые узлы. для (T t: P) if (g.get (t) == null || g.get (t) .isEmpty ()) S.add (т); // Посетить все конечные узлы. Результат построения из вершин, которые посещены // в первый раз. Добавьте вершины в не посещенные вершины листа S, если // он содержит текущий элемент n и все его значения посещены. while (! S.isEmpty ()) { if (V.add (n = S.poll ())) L.add (п); для (T t: g.keySet ()) if (g.get (t)! = null &&! g.get (t) .isEmpty () &&! V.contains (t) && V.containsAll (g.get (t))) S.add (т); } // Вернуть результат. если (L.containsAll (P)) возврат L; // Бросить исключение. StringBuilder sb = new StringBuilder ( "\ nНеверный DAG: обнаружена циклическая зависимость: \ n"); для (T t: P) если (! L.contains (t)) sb.append (t) .append (""); бросить новое исключение (sb.append ("\ n"). toString ()); } / ** * Метод удаляет самостоятельные зависимости и добавляет недостающие конечные узлы. * * @param g * Направленный ациклический граф , где вершины хранятся как * {@link java.util.HashMap HashMap} элементы. * / public static void tSortFix (java.util.Map > g) { java.util.ArrayList tmp; java.util.HashSet P = новый java.util.HashSet (); P.addAll (g.keySet ()); для (T t: P) if (g.get (t)! = null ||! g.get (t) .isEmpty ()) { (tmp = g.get (t)). remove (t); для (T m: tmp) если (! P. содержит (м)) g.put (м, новый ArrayList (0)); } }

Ответы [ 2 ]

10 голосов
/ 10 ноября 2010

Проблема, которую вы решаете, известна как топологическая сортировка . Алгоритм Кана решает проблему, одновременно обнаруживая неверный ввод (то есть содержащий циклы).

3 голосов
/ 10 ноября 2010

Быстрый способ сделать это - запомнить набор элементов, которые вы уже видели, и просто выбросить исключение, если вы собираетесь выработать требования для элемента, уже включенного в этот список.Это определенно будет указывать на некую круглость, которую мы, вероятно, считаем плохой вещью.

Более сложным решением, если допустимы циклы в графе объектов, было бы не только хранить элементы, но и отображатьих к их решению.Поскольку вы находитесь в процессе расчета решений, возможно, вам понадобится Future , которое вы вставите в карту, чтобы указать, что оценка может быть еще не завершена.Затем вы можете добавить «заполнители решения» на карту, как только вы посетите данный элемент.Следовательно, бесконечные циклы работают нормально (глядя на ваш тривиальный неверный случай):

  1. Посетите F1.Положите будущее для этого на карте.Пройдите упражнение F2.
  2. Посетите F2.Положите будущее для этого на карте.Посмотрите, что F1 уже «решена», и запишите конкретное решение для F2 как простое создание F1.

Это, конечно, представляет цикл в объектной модели решения, но на самом деле это законное представлениеввода.Если бы вы отображали это графически, возможно, в виде дерева, которое расширяется на один уровень за раз, это тоже отразилось бы соответствующим образом («Как создать F1? Вам нужно создать F2. Как создать that *»1014 *? Вам нужно сделать F1 "и т. Д. На столько уровней, сколько пользователь расширил дерево).

(Обратите внимание, что, хотя это звучит глупо, оно может на самом деле отражать некоторые действительные сценарии, например, в Fallout: Новый Вегас, несколько рецептов крафта конвертируют между различными типами энергетических боеприпасов. Таким образом, чтобы создать ячейку Микрофузии, вам нужно три ячейки малой энергии. И чтобы сделать три ячейки малой энергии вам нужна ячейка микрофузии. Это создаст петлю на графике, нов этом случае действительно допустимый ввод. Я просто указываю на это, если вы предполагаете, что циклы всегда неправильный вход.)

Если возможно, чтобы рецепт имелальтернативы, возможно, где-то между этими двумя подходами является лучшим выбором.Следуйте по всем альтернативным путям, но если вы попадаете в бесконечный цикл, перестаньте следовать этому и идите с другими.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...