两个数组的智能合并(类似三路归并)

4

我有两个数组,分别表示故事列表。两个用户可以同时修改顺序、添加或删除故事,我想要将这些更改合并。

以下示例可能会更加清晰

Original       1,2,3,4,5
UserA (mine)   3,1,2,4,5 (moved story 3 to start)
UserB (theirs) 1,2,3,5,4 (moved story 5 forward)

以上操作的结果应该是:
Merge (result) 3,1,2,5,4

在冲突的情况下,用户A应始终获胜。

我用这种简单的方法取得了很大的进展。首先,我删除了我的代码中应该删除的部分(该部分代码未显示,因为它是微不足道的),然后我迭代我的代码,插入并移动他们的代码中所需的内容(mstories = mine,tstories = theirs):

     for (var idx=0;idx<mstories.length;idx++) {
        var storyId = mstories[idx];

        // new story by theirs
        if (tstories[idx] !== undefined && mstories.indexOf(tstories[idx]) == -1) {
           mstories.splice(idx+1, 0, tstories[idx]);
           idx--;
           continue;
        }
        // new story by mine
        if (tstories.indexOf(storyId) == -1 && ostories.indexOf(storyId) == -1) {
           tstories.splice(idx+offset, 0, storyId);
           offset += 1;
        // story moved by me
        } else if ((tstories.indexOf(storyId) != idx + offset) && ostories.indexOf(storyId) != idx) {
           tstories.splice(tstories.indexOf(storyId), 1);
           tstories.splice(idx+offset, 0, storyId);
        // story moved by them
        } else if (tstories.indexOf(storyId) != idx + offset) {
           mstories.splice(mstories.indexOf(storyId), 1);
           mstories.splice(idx+offset, 0, storyId);
           mdebug(storyId, 'S moved by them, moffset--');
        }
     }
     result = tstories

这个问题很接近解决,但当太多的故事被移到前面或后面并且中间有其他用户触摸的故事时,它会变得混乱。

我有一个扩展版本,对原始内容进行检查并更加智能 - 持有2个偏移量等等,但我觉得这是一个必须有a)名称b)完美解决方案的问题,我不想重新发明轮子。


3
请了解一下操作转换(Operational Transformation),它通常用于协作系统的并发控制。Google Wave 在保持文档同步方面广泛使用它。详情请见 http://www.waveprotocol.org/whitepapers/operational-transform 或维基百科 http://en.wikipedia.org/wiki/Operational_transformation。 - Anurag
有趣的问题!我非常想看到它的解决方案。我尝试了一下,但没有成功,也许等头痛好了再试试。 - Cristian Sanchez
1
确实,OT帮了我很多。解决方案是提取两个更改(移动、插入、删除)的操作集,然后将其中一个更改(基本上是偏移量)转换为在另一个更改之后适用..然后应用两个更改。我现在怀疑这可以在一个循环中清晰地编写,就像我尝试的那样。 - oberhamsi
1个回答

3

这里有一个函数,它遍历提供的数组,并让另一个函数决定在给定索引处如何合并。(参见策略模式)

// params:
//   original, a, b: equally dimensioned arrays
//   mergeStrategy : function with params _original, _a, _b that
//                   accept the values inhabiting the arrays
//                   original, a, and b resp.
//       
//                  - Is called for each array index.  
//                  - Should return one of the arguments passed.
//                  - May throw "conflict" exception.
// returns merged array

function merge(original, a, b, mergeStrategy){
    var result=[];

    for ( var i =0; i< a.length; i++ )  {
        try {
            result.push( mergeStrategy( original[i], a[i], b[i] ));
        } catch (e if e=="conflict") {
            throw ("conflict at index "+i);
        }
    }
    return result;
}

以下是一些使用示例...您的:

// example:  always choose a over b
function aWins( original, a, b ) {
    return ( original == a ) ? b : a;
}

// returns [3,1,2,5,4]    
merge(
    [1,2,3,4,5],
    [3,1,2,4,5],
    [1,2,3,5,4], aWins);

...并且在冲突时抛出异常的一种:

function complain( original, a, b ) {
    if (original == a )
        return b;

    if (original == b )
        return a;

    if ( a == b )
        return a;

    throw ("conflict");
}

// throws "conflict at index 3"    
merge(
    [1,2,3,4,5],
    [3,1,2,1,5],
    [1,2,3,5,4], complain);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接