You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

嵌套交叉引用列表重复项移除的高性能算法实现问题

嵌套交叉引用列表去重方案

核心思路

你的哈希预计算思路是可行的,结合无环引用链、子项最多1级的特性,可通过「自底向上归并+确定性指纹计算」实现线性时间复杂度,支持多线程并行,10万量级对象的处理耗时可控制在秒级。


方案实现步骤

1. 预处理规则简化

首先利用给定的约束条件降低计算复杂度:

  • 对象不能同时持有引用和子项,因此每个对象只会属于「引用型」「子项持有型」「基础型(无引用无子项)」三类,无需处理两种属性同时存在的分支
  • 引用链无环且最终指向ClassType为Type_Base的基础对象,可按引用深度倒序处理对象,避免重复递归计算

2. 确定性等价指纹计算

为每个对象生成全局唯一的等价指纹,指纹相同的对象直接判定为等价,无需重复逐字段比对:

  • 指纹计算规则(按依赖顺序):
    • 基础对象:指纹 = 哈希组合(ClassType + Name的不变文化哈希)
    • 引用型对象:指纹 = 哈希组合(ClassType + Name的不变文化哈希 + 被引用对象的指纹)
    • 子项持有型对象:指纹 = 哈希组合(ClassType + Name的不变文化哈希 + 所有子项按顺序拼接的指纹)
  • 为避免哈希冲突,采用双层哈希校验:第一层用64位FNV-1a哈希做粗筛,第二层用128位XXHash做精筛,两层哈希同时相等才判定为指纹等价,冲突概率可忽略。

3. 多线程并行处理流程

步骤1:并行计算所有对象的引用深度

遍历AllObjects,对每个对象递归计算到基础对象的引用链长度作为处理优先级,深度越大越先处理。该步骤可多线程并行,用线程安全字典存储深度结果。

步骤2:按深度分组并行计算指纹

把所有对象按深度降序分组,同深度的对象互相没有依赖,可完全并行计算指纹:

  • 用线程安全字典FingerprintMap<(ulong Hash1, ulong Hash2), uint>存储指纹对应的保留对象ID
  • 每个对象计算完指纹后查询FingerprintMap:
    • 不存在该指纹:将当前对象ID存入FingerprintMap,标记为保留
    • 已存在该指纹:标记当前对象为重复,记录映射关系DuplicateMap<uint, uint>(重复对象ID -> 保留对象ID)

步骤3:批量更新引用和子项

遍历所有保留对象,将对象的ReferencedID、子项的ReferencedID按DuplicateMap替换为保留对象ID。

步骤4:清理死链和重复对象

遍历AllObjects,删除所有标记为重复的对象,同时删除未被任何保留对象引用的死链对象。


核心代码示例

// 指纹结构,值类型避免装箱开销
public readonly struct ObjectFingerprint : IEquatable<ObjectFingerprint>
{
    public ulong Hash1 { get; }
    public ulong Hash2 { get; }

    public ObjectFingerprint(ulong hash1, ulong hash2)
    {
        Hash1 = hash1;
        Hash2 = hash2;
    }

    public bool Equals(ObjectFingerprint other)
    {
        return Hash1 == other.Hash1 && Hash2 == other.Hash2;
    }

    public override bool Equals(object obj)
    {
        return obj is ObjectFingerprint other && Equals(other);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Hash1, Hash2);
    }
}

// 并行处理入口
public static void DeduplicateObjects(Dictionary<uint, MyObject> allObjects, Dictionary<uint, MyObject> preserved)
{
    // 1. 并行计算所有对象的引用深度
    var depthMap = new ConcurrentDictionary<uint, int>();
    Parallel.ForEach(allObjects.Values, obj =>
    {
        depthMap[obj.ID] = CalculateDepth(obj, allObjects);
    });

    // 2. 按深度降序分组
    var depthGroups = allObjects.Values
        .GroupBy(obj => depthMap[obj.ID])
        .OrderByDescending(g => g.Key)
        .ToList();

    var fingerprintMap = new ConcurrentDictionary<ObjectFingerprint, uint>();
    var duplicateMap = new ConcurrentDictionary<uint, uint>();
    var fingerprintCache = new ConcurrentDictionary<uint, ObjectFingerprint>();

    // 3. 按深度分组并行计算指纹
    foreach (var group in depthGroups)
    {
        Parallel.ForEach(group, obj =>
        {
            var fingerprint = CalculateFingerprint(obj, allObjects, fingerprintCache);
            if (fingerprintMap.TryAdd(fingerprint, obj.ID))
            {
                fingerprintCache[obj.ID] = fingerprint;
                return;
            }
            // 标记为重复
            var preservedId = fingerprintMap[fingerprint];
            duplicateMap[obj.ID] = preservedId;
        });
    }

    // 4. 批量更新所有保留对象的引用
    // 注意:若MyObject为不可变类,需重新构造实例替换allObjects中的原对象
    Parallel.ForEach(allObjects.Values.Where(obj => !duplicateMap.ContainsKey(obj.ID)), obj =>
    {
        // 更新自身引用
        if (obj.ReferencedID != 0 && duplicateMap.TryGetValue(obj.ReferencedID, out var newRefId))
        {
            obj.ReferencedID = newRefId;
        }
        // 更新子项引用
        foreach (var child in obj.Children)
        {
            if (child.ReferencedID != 0 && duplicateMap.TryGetValue(child.ReferencedID, out var childRefId))
            {
                child.ReferencedID = childRefId;
            }
        }
    });

    // 5. 清理重复对象和死链
    var referencedIds = new HashSet<uint>(preserved.Keys);
    foreach (var obj in allObjects.Values.Where(o => !duplicateMap.ContainsKey(o.ID)))
    {
        if (obj.ReferencedID != 0) referencedIds.Add(obj.ReferencedID);
        foreach (var child in obj.Children)
        {
            if (child.ReferencedID !=0) referencedIds.Add(child.ReferencedID);
        }
    }
    // 移除重复+未被引用的对象
    foreach (var id in allObjects.Keys.ToList())
    {
        if (duplicateMap.ContainsKey(id) || !referencedIds.Contains(id))
        {
            allObjects.Remove(id);
        }
    }
}

// 深度计算辅助方法
private static int CalculateDepth(MyObject obj, Dictionary<uint, MyObject> allObjects)
{
    if (obj.ReferencedID == 0) return 0;
    var refObj = allObjects[obj.ReferencedID];
    return CalculateDepth(refObj, allObjects) + 1;
}

// 指纹计算辅助方法,FNV-1a和XXHash的实现可替换为项目中已有的实现
private static ObjectFingerprint CalculateFingerprint(MyObject obj, Dictionary<uint, MyObject> allObjects, ConcurrentDictionary<uint, ObjectFingerprint> cache)
{
    if (cache.TryGetValue(obj.ID, out var cached)) return cached;
    
    ulong hash1 = 14695981039346656037UL; // FNV-1a初始值
    ulong hash2 = 0; // XXHash初始值

    // 合并ClassType
    hash1 ^= (ulong)obj.ClassType;
    hash1 *= 1099511628211UL;
    hash2 = HashCode.Combine(hash2, obj.ClassType);

    // 合并Name
    if (obj.Name != null)
    {
        foreach (var c in obj.Name)
        {
            hash1 ^= c;
            hash1 *= 1099511628211UL;
        }
        hash2 = HashCode.Combine(hash2, obj.Name.GetHashCode(StringComparison.InvariantCulture));
    }

    // 合并引用
    if (obj.ReferencedID != 0)
    {
        var refFingerprint = CalculateFingerprint(allObjects[obj.ReferencedID], allObjects, cache);
        hash1 ^= refFingerprint.Hash1;
        hash1 *= 1099511628211UL;
        hash2 = HashCode.Combine(hash2, refFingerprint.Hash2);
    }
    // 合并子项
    else
    {
        foreach (var child in obj.Children)
        {
            var childFingerprint = CalculateFingerprint(child, allObjects, cache);
            hash1 ^= childFingerprint.Hash1;
            hash1 *= 1099511628211UL;
            hash2 = HashCode.Combine(hash2, childFingerprint.Hash2);
        }
    }

    var result = new ObjectFingerprint(hash1, hash2);
    cache[obj.ID] = result;
    return result;
}

性能说明

  1. 时间复杂度为O(N*L),其中N是总对象数,L是引用链平均长度(5-15),10万量级下总运算量不到200万次,单线程即可在1秒内完成,多线程下耗时可进一步降低
  2. 所有分组处理阶段完全并行,没有全局锁竞争,可充分利用多核CPU
  3. 确定性指纹计算保证了结果和单线程处理完全一致,没有多线程并发导致的结果差异

内容的提问来源于stack exchange,提问作者Simmy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 18:06:01