Слияние данных с неизвестной функцией заказа - PullRequest
4 голосов
/ 12 ноября 2008

У меня есть несколько строковых массивов. Строки в каждом массиве упорядочены одинаково, в соответствии с теми же критериями. Тем не менее, некоторые строки могут отсутствовать в некоторых массивах, и может не быть массива, который имеет полный набор строк. Более того, критерии, используемые для сравнения строк, мне недоступны: вне контекста массива я не могу сказать, какая строка должна предшествовать другой.

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

Кто-нибудь знаком с такой проблемой? Какой правильный алгоритм?

Примеры:

A B D
A C D

Не могу правильно заказать, не могу определить порядок B и C

A B D
A B C
A C D

Этой информации достаточно, чтобы правильно заказать ABCD.

Ответы [ 10 ]

5 голосов
/ 13 ноября 2008

Один из возможных способов, который я могу придумать, хотя, возможно, не самый эффективный:

Я собираюсь использовать ваш пример, чтобы объяснить:

A B D
A B C
A C D

создайте массив уникальных символов, чтобы вы получили (например):

A B D C

Также у вас, вероятно, должно быть перечисление, чтобы описать возможные отношения

enum Type
{
    Unknown = 0,
    Greater = 1,
    Equal = 2,
    Less = 3,
}

Теперь создайте квадратную матрицу, размеры которой совпадают с массивом выше, по умолчанию все равно Type.Unknown.

0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0

Ваш массив будет служить индексом матрицы, когда вы будете выяснять порядок. Чтобы увидеть, что я имею в виду, посмотрите здесь:

  A B D C
A 0 0 0 0
B 0 0 0 0
D 0 0 0 0
C 0 0 0 0

Пройдите и сделайте диагональ как Type.Equal

2 0 0 0
0 2 0 0
0 0 2 0
0 0 0 2

Теперь вам нужно заполнить матрицу, пройтись по каждому входному массиву и получить каждый символ и один после него. Используйте эти 2 символа, найдите индекс в матрице для каждого (используя ваш массив) и обновите матрицу.

for(int i=0; i<len(arr)-1; i++)
{
    char c1 = arr[i], c2 = arr[i+1];
    int i1,i2;
    //get indices of characters in array and put in i1, and i2 respectively
    matrix[i1][i2] = Type.Less;
    matrix[i2][i1] = Type.Greater
}

Вы назначаете 2 места в сетке каждый раз, потому что когда один символ меньше другого, это также означает, что второй символ больше первого.

Теперь вы просто вставляете символы в массив по одному (Linked List будет самым простым, вы поймете почему через секунду)

Каждый раз, когда вы вставляете символ, вы начинаете с начала вашего возвращаемого массива и выполняете итерацию, просматривая индексы в первом массиве и проверяя матрицу для Type.Greater или Type.Less (Зависит от по какому пути вы сравниваете, переводите char в массив или array to current char) и вставляете его, если встречаете значение, отличное от ожидаемого.

Если вы нажали Type.Unknown в matix во время вставки, вы знаете, что у вас недостаточно информации для сортировки этих массивов, если вы нажали Type.Equal, вы можете игнорировать вставку этого символа (если вы не Я не хочу дубликатов в вашем результате.)

И тогда вы выведете свой возвращаемый массив

4 голосов
/ 13 ноября 2008

Это звучит как особый случай проблемы топологической сортировки .

2 голосов
/ 13 ноября 2008

1) сортировка данных. Если частичные упорядочения несовместимы, тогда будет цикл, и сортировка завершится неудачно. В противном случае у вас есть последовательность s (0) ... s (n-1).

2) Для каждой пары s (i), s (i + 1) ищите в частичных последовательностях одну и ту же пару, появляющуюся рядом друг с другом. Если вы найдете его в одном из них, продолжайте идти дальше. Если вы его не нашли, то не удалось, потому что частичные последовательности не упорядочивают s (i) и s (i + 1).

Почему бы и нет? Потому что, если они их заказали, но они не появляются рядом друг с другом, то должно быть что-то «между ними». Но если между ними есть что-то промежуточное в частичных последовательностях, но не в полной последовательности, которую вы получили из tsort, то этот нарушитель должен быть в полной последовательности либо перед s (i), либо после s (i + 1). Это противоречит согласованности полной последовательности с частичными последовательностями, что уже доказано.

Шаг 2 - это O (n * m), где n - количество элементов для заказа, а m - общая длина всех частичных последовательностей. Возможно, вам удастся добиться большего успеха, но это довольно просто, потому что вы можете взять сортировку с полки для шага 1, а шаг 2 - это очевидная связка вложенных циклов. Обратите внимание, что вы можете прекратить поиск каждой частичной последовательности, если найдете s (i) (или s (i + 1) в этом отношении), потому что это точно не может повториться.

0 голосов
/ 13 ноября 2008

Как насчет простой рекурсивной процедуры?

Используя ваш второй пример:

A B D
A B C
A C D

Создайте хеш-таблицу всех уникальных упорядочений:

table = {A -> B, C
         B -> C, D
         C -> D}

Подсчитайте все уникальные значения

countUnique = 4

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

main()
{
  foreach (s in table.keys)
  {
    stack.push(s)
    searchForPath()
    stack.pop()
  }
}

searchForPath()
{
  if (table.hasKey(stack.peek))
  {
    // we're not at the end yet
    foreach (s in table[stack.peek])
    {
      // avoid infinite recursion
      if (stack.contains(s)
        continue

      stack.push(s)
      searchForPath()
      stack.pop()
    }
  }
  else
  {
    if (stack.size == countUnique)
    {
      // solution found
      captureSolution()
    }
  }
}
0 голосов
/ 13 ноября 2008

Мне хочется сказать, что для того, чтобы иметь достаточно информации для завершения слияния, каждая пара x, y, которая заканчивается рядом друг с другом в окончательном объединенном массиве, должна присутствовать где-то во входных массивах. Другими словами, транзитивность может вообще не войти в картину. Может ли кто-нибудь привести контрпример?

Вы можете сделать это прямо здесь, в ответе, если хотите.

0 голосов
/ 13 ноября 2008

Даг МакКлин побеждает. Фраза, которая запала мне в голову, была стратификацией, и это привело к тупикам в веб-поиске. (Например, в логическом программировании полезно говорить о многослойных программах; это программы, которые вы можете визуализировать как стек слоев, каждый слой содержит один или несколько предикатов, а каждый предикат является экстенсиональным (нижний уровень) или следующим только из предикатов в слоях под ним. В идеале вам нужна форма, в которой каждый слой имеет не более одной строки.)

Ваш вариант алгоритма топосорта заставил бы вас создать DAG (вы подразумеваете, что ацикличность гарантирована), возможно, отслеживая узлы с нулевым входящим фронтом по ходу (простая оптимизация). Если после этого у вас есть более одного «корневого» узла, все готово, поскольку вы знаете, что у вас нет первого узла. В противном случае у вас есть ровно один; удалите его из группы обеспечения доступности баз данных, проверяя, является ли каждый из его дочерних узлов «корневым» узлом; и продолжайте, пока не получите более одного корневого узла (сбой) или не исчерпаете узлы (успех).

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

0 голосов
/ 13 ноября 2008

Основная проблема заключается в том, что, учитывая некоторые данные, вам необходимо определить функцию упорядочения. После этого это просто сортировка списка.

Создайте список первых элементов всех ваших массивов. Удалить дубликаты. Итак, это:

A B
A C
B C
C C
C D

становится (A, B, C). Затем мы хотим, чтобы набор пар, представляющих каждое известное нам значение, был меньше, чем что-то еще. ((A, B), (A, C), (B, C)) для этого примера.

Теперь для каждого значения, которое мы можем найти в начале массива, возьмем все массивы, которые начинаются с этого значения, в качестве группы. Уберите свои первые элементы. Что нас заводит:

B
C

C

и

C
D

Рекурсировать в каждом из этих списков массивов.

Как только мы закончили рекурсивно на всех наших подмножествах, мы объединяем пары, которые получены в то, что мы изначально придумали. Мы должны в конечном итоге ((A, B), (A, C), (B, C), (C, D)). В зависимости от свойств ваших значений, вы можете расширить это в ((A, B), (A, C), (A, D), (B, C), (B, D), (C, D) ).

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

0 голосов
/ 13 ноября 2008

Частичное решение:

Вам необходимо определить порядок. Вы можете сделать это, попросив данные «проголосовать» за то, сколько раз один символ предшествует другому. Вам понадобится квадратная матрица с размером, равным количеству символов. Инициализируйте его для всех нулей, затем отсканируйте все входные строки, добавив 1 к M (a, b) для каждой последующей пары (a, b). Затем вам необходимо очистить эти данные, чтобы получить транзитивный порядок (если A предшествует B и предшествует C, A также должен предшествовать C). Для этого вам нужно перебрать все триплеты различных символов и «согласовать» соответствующие триплеты неравенств, чтобы они учитывали транзитивность.

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

0 голосов
/ 13 ноября 2008

Чем это отличается от классического алгоритма сравнения / слияния ? Пока оба массива «отсортированы», независимо от механизма, по которому они отсортированы. Они отсортированы по сравнению друг с другом, поэтому вы должны иметь возможность использовать сходства, чтобы объединить различия. После этого это в основном произвольно.

Если вы хотите объединить [A] с [B, C, D], то у вас нет никакой идеи, куда пойдет A (фронт? Конец? Между C и D? Вы не знаете). В этих случаях вам просто нужно придумать соглашение (всегда впереди, всегда в конце, что-то в этом роде).

Если вы хотите объединить [Q, X, Y, A, B] с [Q, X, W, B, D], вам нужно будет решить, стоит ли буква W перед или позади "Y, A". Итак, [Q, X, Y, A, W, B, D] правильно или [Q, X, W, Y, A, B, D] правильно. Честно говоря, вам просто нужно позвонить и сделать это. Просто недостаточно информации.

0 голосов
/ 12 ноября 2008

Сколько у вас будет комплектов? Что-то, что может работать, если они не слишком редки, это:

  1. Итерация по первому элементу все массивы подсчитывают, насколько часто каждый Строка
  2. Какой элемент является наиболее распространенным выбран.
  3. Проверьте каждый элемент всех массивы для указанной строки.
  4. Если он существует в любом элементе, кроме первого в массиве, первый элемент его родительский массив выбран и перейдите к шагу 3.
  5. Удалить выбранный элемент из первый элемент всех массивов / деревьев и добавить его в конец вашего вывода список.
  6. Перейти к 1, пока все элементы не будут удален.

Если вы выбросили массивы в b-trees , поиск элементов может быть довольно быстрым. Вы также можете дополнительно ускорить шаг 3, играя с порядком массивов. Я все еще думаю об этом.

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

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