同步两个相关对象列表的标准算法是什么?

时间:2009-12-18 14:24:00

标签: algorithm language-agnostic list synchronization

我很确定这必须出现在某种教科书中(或者更有可能出现在所有教科书中),但我似乎使用了错误的关键词来搜索它...... :(

我在编程时遇到的一个反复出现的任务是我正在处理来自不同来源的对象列表,我需要以某种方式保持同步。通常存在某种“主列表”,例如由一些外部API返回,然后是我自己创建的对象列表,每个对象都与主列表中的对象相对应(想想“包装器”或“适配器” - 它们通常包含有关我的应用程序特定的外部对象的扩展信息和/或者它们简化了对外部对象的访问。)

所有问题实例的硬特征:

  • 主列表的实现对我来说是隐藏的;它的界面是固定的
  • 两个列表中的元素不是分配兼容的
  • 我可以完全控制从属列表的实现
  • 我无法控制主列表中元素的顺序(即它不可排序)
  • 主列表要么根本不提供有关添加或删除元素的通知,要么通知不可靠,即同步只能按需发生,而不是实时
  • 只需在需要时从头开始清除和重建从属列表不是一个选项:
    • 初始化包装器对象应该被认为是昂贵的
    • 其他对象将保存对包装器的引用

在某些情况下的其他特征:

  • 主列表中的元素只能通过读取其属性来识别,而不是通过索引或内存地址直接访问它们:
    • 刷新后,主列表可能会返回一组全新的实例,即使它们仍然代表相同的信息
    • 访问主列表中元素的唯一接口可能是顺序枚举器
  • 大部分时间,主列表中元素的顺序是稳定的,即新元素总是在开头或结尾添加,从不在中间;但是,删除通常可以在任何位置进行

那么我通常如何解决这个问题呢?我应该谷歌的算法名称是什么?

在过去,我已经以各种方式实现了这一点(参见下面的示例),但总觉得应该有更清洁,更有效的方式,特别是不需要两次迭代的方法(每个列表一个)。

以下是一个示例方法:

  1. 迭代主列表
  2. 查找“奴隶清单”中的每个项目
  3. 添加尚不存在的项目
  4. 以某种方式跟踪两个列表中已存在的项目(例如,通过标记它们或保留另一个列表)
  5. 完成后,遍历从属列表并删除所有未标记的对象(请参阅4.)并再次清除其他所有对象

  6. 更新1 感谢您到目前为止的所有回复!我需要一些时间来查看链接 [...] (文字移至问题正文)

    更新2 将中间段重构为(希望)更易于解析的子弹列表,并在第一次更新后添加了详细信息。

8 个答案:

答案 0 :(得分:3)

这看起来像设置协调问题,即同步无序数据的问题。关于SO的问题被问到:Implementation of set reconciliation algorithm

Google上的大多数参考文献都是技术论文摘要。

答案 1 :(得分:2)

两种典型的解决方案是: 1.将主列表复制到同步列表。 2.在所有元素对之间进行O(N * N)比较。

您已经排除了智能选项:共享身份,排序和更改通知。

请注意,列表是否可以以有意义的方式排序,甚至完全无关。例如,在比较两个字符串列表时,按字母顺序排序是理想的。但是,如果您按字符串 length 对两个列表进行排序,列表比较仍然会更有效!你仍然需要对相同长度的字符串进行完全成对比较,但这可能是一个更小的数量对。

答案 2 :(得分:2)

解决此类问题的最佳方法通常是不直接解决问题。

如果您真的无法在代码中使用已排序的二进制可搜索容器(如集合甚至是已排序的向量),那么......

你的记忆力很强吗?如果没有,那么我只是创建一个包含其中一个列表内容的字典(例如std :: set),然后迭代第二个我希望与第一个同步的字典。

这样你就可以用 logn来创建字典(或者n X用于哈希字典,具体取决于哪个更有效)+ m logn操作来覆盖第二个列出并同步它(或者只是M Y) - 如果你真的必须首先使用列表,那就很难被击败 - 只有当你需要它时它也只做一次它并且它比保持它更好然后保持列表将所有时间排序,这两个任务都是^ 2任务。

答案 3 :(得分:1)

在C ++ STL中,算法称为set_union。此外,如果将联合作为第3个列表,实现算法可能会简单得多。

答案 4 :(得分:1)

看起来像一个名叫Michael Heyeck的人对这个问题有一个很好的O(n) solution。查看该博客文章以获得解释和一些代码。

基本上,该解决方案在一次通过中跟踪主列表和从列表,并将索引跟踪到每个列表中。然后管理两个数据结构:要在从属列表上重放的插入列表,以及删除列表。

它看起来很简单,并且还有一个极简主义证明的好处,Heyeck在subsequent post中跟进了它。这篇文章中的代码片段也更加紧凑:

def sync_ordered_list(a, b):
x = 0; y = 0; i = []; d = []
while (x < len(a)) or (y < len(b)):
    if y >= len(b): d.append(x); x += 1
    elif x >= len(a): i.append((y, b[y])); y += 1
    elif a[x] < b[y]: d.append(x); x += 1
    elif a[x] > b[y]: i.append((y, b[y])); y += 1
    else: x += 1; y += 1
return (i,d)

再次感谢Michael Heyeck。

答案 5 :(得分:0)

我过去在一个项目中遇到过这样的问题。

该项目有一个主数据源和几个独立更新数据的客户端,最后所有这些都必须拥有最新的统一数据集,这些数据是它们的总和。

我所做的是构建类似于SVN协议的东西,每次我想更新master数据库(可以通过Web服务访问)时,我都得到了修订号。将我的本地数据存储更新为该版本,然后将任何版本号未涵盖的实体提交到数据库。

每个客户都可以将其本地数据存储更新到最新版本。

答案 6 :(得分:0)

这是Michael Heyek的python代码的Javascript版本。

    var b= [1,3,8,12,16,19,22,24,26]; // new situation
    var a = [1,2,8,9,19,22,23,26]; // previous situation
    var result = sync_ordered_lists(a,b);
console.log(result);
    function sync_ordered_lists(a,b){
// by Michael Heyeck see http://www.mlsite.net/blog/?p=2250
// a is the subject list
// b is the target list
// x is the "current position" in the subject list
// y is the "current position" in the target list
// i is the list of inserts
// d is the list of deletes
        var x = 0; 
        var y = 0; 
        var i = []; 
        var d = []; 
        var acc = {}; // object containing inserts and deletes arrays
        while (x < a.length || y < b.length) {
            if (y >= b.length){
                d.push(x); 
                x++;
            } else if (x >= a.length){ 
                i.push([y, b[y]]); 
                y++;
            } else if (a[x] < b[y]){ 
                d.push(x); 
                x++;
            } else if (a[x] > b[y]){ 
                i.push([y, b[y]]); 
                y++;
            } else { 
                x++; y++;
            }
        }
        acc.inserts = i;
        acc.deletes = d;
        return acc;
    }

答案 7 :(得分:-1)

非常蛮力和纯粹的技术方法:

从List类继承(抱歉不知道你的语言是什么)。覆盖子列表类中的添加/删除方法。使用您的班级而不是基础班级。现在,您可以使用自己的方法跟踪更改并在线同步列表。