繁体   English   中英

算法:有趣的差异算法

[英]Algorithms: Interesting diffing algorithm

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

数学公式

假设您有两个列表, LR每个列表包含来自某些底层字母S元素。 此外,这些列表具有它们按顺序出现的公共元素的属性:也就是说,如果L[i] = R[i*]L[j] = R[j*] ,并且i < j那么i * < j *。 列表根本不需要任何共同元素,并且一个或两个可以是空的。 [ 澄清:你可以假设没有重复的元素。 ]

问题是产生一种列表的“差异”,可以将其视为有序对(x,y)新列表,其中x来自Ly来自R ,具有以下属性:

  1. 如果x出现在两个列表中,则结果中会出现(x,x)
  2. 如果x出现在L ,而不出现在R ,则结果中会出现(x,NULL)
  3. 如果y出现在R ,但不出现在L ,则结果中会出现(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))

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

如果您愿意在不同的数据结构中复制其中一个列表,则可以在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')]

最坏的情况,如定义和仅使用相等,必须是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),或更糟。

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

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

这与序列比对完全一样,您可以使用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

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

//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是每个列表中的项目数。

EDIT算法来解决这个问题

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))

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM