算法:有趣的差异算法

时间:2009-05-07 14:54:30

标签: algorithm list language-agnostic diff

这出现在现实世界的情况中,我想我会分享它,因为它可能会带来一些有趣的解决方案。从本质上讲,算法需要区分两个列表,但是让我给出一个更严格的问题定义。

数学公式

假设您有两个列表LR,每个列表都包含来自某些基础字母S的元素。此外,这些列表的属性是它们按顺序显示的公共元素:也就是说,L[i] = R[i*]L[j] = R[j*]以及i< j然后i *< j *。列表根本不需要任何共同元素,并且一个或两个可以是空的。 [澄清:您可以假设不重复元素。]

问题是产生一种列表的“差异”,可以将其视为有序对(x,y)的新列表,x来自L和{{1}来自y,来自以下属性:

  1. 如果两个列表中都显示R,则结果中会显示x
  2. 如果(x,x)中出现xL出现R,则结果中会显示(x,NULL)
  3. 如果y中出现RL出现(NULL,y),则结果中会显示L = (d) R = (a,b,c) Result = ((NULL,d), (a,NULL), (b,NULL), (c,NULL)) L = (a,b,c,d,e) R = (b,q,c,d,g,e) Result = ((a,NULL), (b,b), (NULL,q), (c,c), (d,d), (NULL,g), (e,e))
  4. 最后

    • 结果列表与每个输入列表具有“相同”的排序:粗略地说,它与上面的每个列表分别具有相同的排序属性(参见示例)。

    实施例

    {{1}}

    有没有人有任何好的算法来解决这个问题?复杂性是多少?

8 个答案:

答案 0 :(得分:3)

如果您愿意在不同的数据结构中复制其中一个列表,可以在O(n)中执行此操作。这是一个经典的时间/空间权衡。

创建列表R的哈希映射,其中键是元素,值是数组的原始索引;在C ++中,您可以使用tr1中的unordered_map或boost。

保留列表R的未处理部分的索引,初始化为第一个元素。

对于列表L中的每个元素,检查列表R中匹配的哈希映射。如果找不到,则输出(L值,NULL)。如果匹配,则从哈希映射中获取相应的索引。对于列表R中的每个未处理元素,直到匹配索引,输出(NULL,R值)。对于匹配,输出(值,值)。

当你到达列表L的末尾时,浏览列表R的剩余元素并输出(NULL,R值)。

编辑:这是Python的解决方案。对于那些说这个解决方案取决于是否存在良好的散列函数的人 - 当然它确实如此。如果这是一个问题,原始海报可能会对问题增加额外的限制,但在此之前我会采取乐观的态度。

def FindMatches(listL, listR):
    result=[]
    lookupR={}
    for i in range(0, len(listR)):
        lookupR[listR[i]] = i
    unprocessedR = 0
    for left in listL:
        if left in lookupR:
            for right in listR[unprocessedR:lookupR[left]]:
                result.append((None,right))
            result.append((left,left))
            unprocessedR = lookupR[left] + 1
        else:
            result.append((left,None))
    for right in listR[unprocessedR:]:
        result.append((None,right))
    return result

>>> FindMatches(('d'),('a','b','c'))
[('d', None), (None, 'a'), (None, 'b'), (None, 'c')]
>>> FindMatches(('a','b','c','d','e'),('b','q','c','d','g','e'))
[('a', None), ('b', 'b'), (None, 'q'), ('c', 'c'), ('d', 'd'), (None, 'g'), ('e','e')]

答案 1 :(得分:2)

最坏的情况,如定义和仅使用相等,必须是O(n * m)。请考虑以下两个列表:

A [] = {a,b,c,d,e,f,g}

B [] = {h,i,j,k,l,m,n}

假设这两个“有序”列表之间只存在一个匹配。它将进行O(n * m)比较,因为不存在比较,这样就不需要进行其他比较。

因此,您提出的任何算法都将是O(n * m),或更糟。

答案 2 :(得分:1)

通过遍历列表和匹配,可以在线性时间内完成差异排序列表。我将尝试在更新中发布一些伪代码。

由于我们不知道排序算法并且无法确定基于小于或大于运算符的任何排序,因此我们必须考虑无序列表。另外,考虑到如何格式化结果,您将面临扫描两个列表(至少在您找到匹配项之前,然后您可以添加书签并再次从那里开始)。它仍然是O(n ^ 2)性能,或更具体地是O(nm)。

答案 3 :(得分:1)

这与序列比对完全一样,您可以使用Needleman-Wunsch算法来解决它。该链接包含Python中的代码。只需确保设置得分,使得不匹配为负且匹配为正,并且最大化时与空白的对齐为0。该算法在O(n * m)时间和空间运行,但可以提高空间复杂度。

评分功能

int score(char x, char y){
    if ((x == ' ') || (y == ' ')){
        return 0;
    }
    else if (x != y){
        return -1;
    }
    else if (x == y){
        return 1;
    }
    else{
        puts("Error!");
        exit(2);
    }
}

<强>代码

#include <stdio.h>
#include <stdbool.h>

int max(int a, int b, int c){
    bool ab, ac, bc;
    ab = (a > b);
    ac = (a > c);
    bc = (b > c);
    if (ab && ac){
        return a;
    }
    if (!ab && bc){
        return b;
    }
    if (!ac && !bc){
        return c;
    }
}

int score(char x, char y){
    if ((x == ' ') || (y == ' ')){
        return 0;
    }
    else if (x != y){
        return -1;
    }
    else if (x == y){
        return 1;
    }
    else{
        puts("Error!");
        exit(2);
    }
}


void print_table(int **table, char str1[], char str2[]){
    unsigned int i, j, len1, len2;
    len1 = strlen(str1) + 1;
    len2 = strlen(str2) + 1;
    for (j = 0; j < len2; j++){
        if (j != 0){
            printf("%3c", str2[j - 1]);
        }
        else{
            printf("%3c%3c", ' ', ' ');
        }
    }
    putchar('\n');
    for (i = 0; i < len1; i++){
        if (i != 0){
            printf("%3c", str1[i - 1]);
        }
        else{
            printf("%3c", ' ');
        }
        for (j = 0; j < len2; j++){
            printf("%3d", table[i][j]);
        }
        putchar('\n');
    }
}

int **optimal_global_alignment_table(char str1[], char str2[]){
    unsigned int len1, len2, i, j;
    int **table;
    len1 = strlen(str1) + 1;
    len2 = strlen(str2) + 1;
    table = malloc(sizeof(int*) * len1);
    for (i = 0; i < len1; i++){
        table[i] = calloc(len2, sizeof(int));
    }
    for (i = 0; i < len1; i++){
        table[i][0] += i * score(str1[i], ' ');
    }
    for (j = 0; j < len1; j++){
        table[0][j] += j * score(str1[j], ' ');
    }
    for (i = 1; i < len1; i++){
        for (j = 1; j < len2; j++){
            table[i][j] = max(
                table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]),
                table[i - 1][j] + score(str1[i - 1], ' '),
                table[i][j - 1] + score(' ', str2[j - 1])
            );
        }
    }
    return table;
}

void prefix_char(char ch, char str[]){
    int i;
    for (i = strlen(str); i >= 0; i--){
        str[i+1] = str[i];
    }   
    str[0] = ch;
}

void optimal_global_alignment(int **table, char str1[], char str2[]){
    unsigned int i, j;
    char *align1, *align2;
    i = strlen(str1);
    j = strlen(str2);
    align1 = malloc(sizeof(char) * (i * j));
    align2 = malloc(sizeof(char) * (i * j));
    align1[0] = align2[0] = '\0';
    while((i > 0) && (j > 0)){
        if (table[i][j] == (table[i - 1][j - 1] + score(str1[i - 1], str2[j - 1]))){
            prefix_char(str1[i - 1], align1);
            prefix_char(str2[j - 1], align2);
            i--;
            j--;
        }
        else if (table[i][j] == (table[i - 1][j] + score(str1[i-1], ' '))){
            prefix_char(str1[i - 1], align1);
            prefix_char('_', align2);
            i--;
        }
        else if (table[i][j] == (table[i][j - 1] + score(' ', str2[j - 1]))){
            prefix_char('_', align1);
            prefix_char(str2[j - 1], align2);
            j--;
        }
    }
    while (i > 0){
        prefix_char(str1[i - 1], align1);
        prefix_char('_', align2);
        i--;
    }
    while(j > 0){
        prefix_char('_', align1);
        prefix_char(str2[j - 1], align2);
        j--;
    }
    puts(align1);
    puts(align2);
}

int main(int argc, char * argv[]){
    int **table;
    if (argc == 3){
        table = optimal_global_alignment_table(argv[1], argv[2]);
        print_table(table, argv[1], argv[2]);
        optimal_global_alignment(table, argv[1], argv[2]);
    }
    else{
        puts("Reqires to string arguments!");
    }
    return 0;
}

示例IO

$ cc dynamic_programming.c && ./a.out aab bba
__aab
bb_a_
$ cc dynamic_programming.c && ./a.out d abc
___d
abc_
$ cc dynamic_programming.c && ./a.out abcde bqcdge
ab_cd_e
_bqcdge

答案 4 :(得分:0)

这是一个非常简单的问题,因为你已经有了一个有序列表。

//this is very rough pseudocode
stack aList;
stack bList;
List resultList;
char aVal;
char bVal;

while(aList.Count > 0 || bList.Count > 0)
{
  aVal = aList.Peek; //grab the top item in A
  bVal = bList.Peek; //grab the top item in B

  if(aVal < bVal || bVal == null)
  {
     resultList.Add(new Tuple(aList.Pop(), null)));
  }
  if(bVal < aVal || aVal == null)
  {
     resultList.Add(new Tuple(null, bList.Pop()));
  }
  else //equal
  {
     resultList.Add(new Tuple(aList.Pop(), bList.Pop()));
  }
}

注意......此代码不会编译。它只是作为指导。

编辑基于OP评论

如果未公开排序算法,则列表必须被视为无序。 如果列表是无序的,则算法的时间复杂度为O(n ^ 2),特别是O(nm),其中n和m是每个列表中的项目数。

修改 解决这个问题的算法

L(A,B,C,d,E) R(B,Q,C,d,G,E)

//pseudo code... will not compile
//Note, this modifies aList and bList, so make copies.
List aList;
List bList;
List resultList;
var aVal;
var bVal;

while(aList.Count > 0)
{
   aVal = aList.Pop();
   for(int bIndex = 0; bIndex < bList.Count; bIndex++)
   {
      bVal = bList.Peek();
      if(aVal.RelevantlyEquivalentTo(bVal)
      {
         //The bList items that come BEFORE the match, are definetly not in aList
         for(int tempIndex = 0; tempIndex < bIndex; tempIndex++)
         {
             resultList.Add(new Tuple(null, bList.Pop()));
         }
         //This 'popped' item is the same as bVal right now
         resultList.Add(new Tuple(aVal, bList.Pop()));

         //Set aVal to null so it doesn't get added to resultList again
         aVal = null;

         //Break because it's guaranteed not to be in the rest of the list
         break;
      }
   }
   //No Matches
   if(aVal != null)
   {
      resultList.Add(new Tuple(aVal, null));
   }
}
//aList is now empty, and all the items left in bList need to be added to result set
while(bList.Count > 0)
{
   resultList.Add(new Tuple(null, bList.Pop()));
}

结果集将是

L(A,B,C,d,E) R(B,Q,C,d,G,E)

结果((a,null),(b,b),(null,q),(c,c),(d,d),(null,g),(e,e))

答案 5 :(得分:0)

没有真正有形的答案,只有模糊的直觉。因为您不知道排序算法,只知道数据在每个列表中排序,它听起来有点像用于“差异”文件的算法(例如在Beyond Compare中)并将线序列匹配在一起。或者也与regexp算法模糊地相似。

也可以有多种解决方案。(没关系,如果没有严格排序的重复元素,那就不行了。我在文件比较的过程中想的太多了)

答案 6 :(得分:0)

我认为你没有足够的信息。所有你断言的是匹配的元素以相同的顺序匹配,但找到第一个匹配对是O(nm)操作,除非你有其他一些你可以确定的顺序。

答案 7 :(得分:-1)

SELECT distinct l.element,r.element
FROM LeftList l
OUTER JOIN RightList r
ON l.element = r.element
ORDER BY l.id,r.id

假设每个元素的ID是它的排序。当然,您的列表包含在关系数据库中:)