【问题标题】:How to diff two lists and get net changes如何区分两个列表并获得净变化
【发布时间】:2012-12-17 23:45:22
【问题描述】:

我有一个对象和两个列表如下:

    public class MyObject
    {
        public int Key;
        public DateTime Day;
        public decimal Value;
    }        

    List<MyObject> listA = new List<MyObject>()
    {
        new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 8 },
        new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
        new MyObject() { Key = 2, Day = new DateTime(2012, 12, 17), Value = 8 },
        new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 4 },
        new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 }
    };

    List<MyObject> listB = new List<MyObject>()
    {
        new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 2 },
        new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
        new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 8 },
        new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 },
        new MyObject() { Key = 5, Day = new DateTime(2012, 12, 17), Value = 10 }
    };

我正在寻找的结果是:

    List<MyObject> listChanges = new List<MyObject>()
    {
        new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = -6 },
        new MyObject() { Key = 2, Day = new DateTime(2012, 12, 17), Value = -8 },
        new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 4 },
        new MyObject() { Key = 5, Day = new DateTime(2012, 12, 17), Value = 10 }
    };

基本上,我正在尝试创建一个列表,其中包含将 listA 变为 listB 所需的更改。因此,虽然可以使用 LINQ 中的 except 和 Intersect,但我认为它们不会有最好的性能来完成这样的任务,因为您仍然需要另一个比较来获得值的差异。

我的一个想法是:如果循环遍历 listA,我可以从 listA 和 listB 中删除该项目(如果找到并且此时我可以确定 +/- 差异)。完成 listA 后,listB 将只包含添加内容。

如何获得更改结果?

【问题讨论】:

  • 这是您突破Stopwatch class 并测试您刚刚列出的方案的绝佳机会。 :)
  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • 您真的有性能/优化问题吗?您是否在比较 数千 个条目?如果不是,那么我非常怀疑是否会出现性能问题,在这种情况下,我建议您专注于组合一个易于维护的设计。关于实际设计,也许你的MyObject 应该有某种Compare 方法来指示另一个MyObject 实例是否相等,如果不相等,哪些方面不同。
  • John - 感谢您的澄清,对最初的标题感到抱歉。
  • Chris - 我现在没有性能问题,只是好奇。我一直在尝试看看是否有更好/更快的方法来做某事。我已经为此对象类型实现了一个 IEqualityComparer,以便在不同的场景中与 except & Intersect 一起使用,但我不确定它是否可以在这里使用(除非可以修改 except 以返回更改对象)。

标签: c# algorithm list


【解决方案1】:

应该这样做。如果您的任何Key/Day 组合在您的任何一个输入中都不是唯一的,它将引发异常。

public static IEnumerable<MyObject> GetChanges(
    IEnumerable<MyObject> from, IEnumerable<MyObject> to)
{
    var dict = to.ToDictionary(mo => new { mo.Key, mo.Day });

    // Check that keys are distinct in from, too:
    var throwaway = from.ToDictionary(mo => new { mo.Key, mo.Day });

    // Adjustments of items found in "from"
    foreach (MyObject mo in from)
    {
        var key = new { mo.Key, mo.Day };
        MyObject newVal;
        if (dict.TryGetValue(key, out newVal))
        {
            // Return item indicating adjustment
            yield return new MyObject { 
                Key = mo.Key, Day = mo.Day, Value = newVal.Value - mo.Value };
            dict.Remove(key);
        }
        else
        {
            // Return item indicating removal
            yield return new MyObject {
                Key = mo.Key, Day = mo.Day, Value = -mo.Value };
        }
    }

    // Creation of new items found in "to"
    foreach (MyObject mo in dict.Values)
    {
        // Return item indicating addition
        // (Clone as all our other yields are new objects)
        yield return new MyObject {
            Key = mo.Key, Day = mo.Day, Value = mo.Value };
    }
}

可以通过删除from 上的唯一性检查或即时进行(尝试将每个项目的关键部分添加到HashSet)来加快此速度,但我不这样做'认为你不能避免循环遍历 to 的一部分 - 一次构建字典,一次返回剩余部分。

【讨论】:

    【解决方案2】:

    首先我将实现一个IEqualityComparer&lt;T&gt;,它基于KeyDay 属性检查相等性。然后你可以按如下方式使用 linq:

    var notInA = listB.Except(listA, myEqualityComparer);
    var notInB = listA.Except(listB, myEqualityComparer)
                       .Select(o => {
                          return new MyObject {
                             Key = item.Key,
                             Day = item.Day,
                             Value = item.Value * -1
                          };
                       });
    var listA2 = listA.Intersect(listB, myEqualityComparer)
                      .OrderBy(o => o.Key)
                      .ThenBy(o => o.Day);
    var listB2 = listB.Intersect(listA, myEqualityComparer)
                      .OrderBy(o => o.Key)
                      .ThenBy(o => o.Day);
    var diff = listA2.Zip(listB2, (first,second) => {
       return new MyObject {
         Key = first.Key,
         Day = first.Day,
         Value = second.Value - first.Value
    });
    diff = diff.Concat(notInA).Concat(notInB);
    

    【讨论】:

    • 这不包括“删除”不在 B 中的任何东西。它还依赖于在 A 和 B 中的交叉点的顺序相同。
    • notInA 变量包含 B 中不在 A 中的所有项目。Intersect 方法调用删除 A 中不在 B 中的所有内容。我已更新订购,感谢您的关注那个。
    • 但是在 A 中但不在 B 中的所有内容都应该以负值出现在输出中,而不是完全丢失。
    • 另一个好收获。我想我还在从新年庆祝活动中恢复过来。
    【解决方案3】:

    这个怎么样:

            List<MyObject> listA = new List<MyObject>(){
                new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 8 },
                new MyObject() { Key = 2, Day = new DateTime(2012, 12, 17), Value = 8 },
                new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
                new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 },
                new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 4 }
            };
    
            List<MyObject> listB = new List<MyObject>(){
                new MyObject() { Key = 1, Day = new DateTime(2012, 12, 17), Value = 2 },
                new MyObject() { Key = 3, Day = new DateTime(2012, 12, 17), Value = 8 },
                new MyObject() { Key = 4, Day = new DateTime(2012, 12, 17), Value = 4 },
                new MyObject() { Key = 1, Day = new DateTime(2012, 12, 18), Value = 8 },
                new MyObject() { Key = 5, Day = new DateTime(2012, 12, 17), Value = 10 }
            };
            List<MyObject> listChanges = Comparer(listA, listB);
            MyObject[] hasil = listChanges.ToArray();
            for (int a = 0; a < hasil.Length;a++ ) {
                Console.WriteLine(hasil[a].Key+" "+hasil[a].Day+" "+hasil[a].Value);
            }
    

    和功能:

        private MyObject[] sort(List<MyObject> input) {
            //sort input with it's key
            MyObject[] gg = input.ToArray();
            for (int a = 0; a < input.Count; a++) {
                for (int b = a + 1; b < input.Count; b++) {
                    if (gg[a].Key > gg[b].Key) {
                        MyObject temp = gg[a];
                        gg[a] = gg[b];
                        gg[b] = temp;
                    }
                }
            }
            //sort input, if key is same => sort the date
            for (int a = 0; a < input.Count; a++) {
                int indStart = a;
                int indEnd = a;
                for (int b = a + 1; b < input.Count; b++) {
                    if (gg[a].Key == gg[b].Key) {
                        indEnd++;
                    } else {
                        b = input.Count;
                    }
                }
                a = indEnd;
                for (int c = indStart; c <= indEnd; c++) {
                    for (int d = c + 1; d <= indEnd; d++) {
                        if (gg[c].Day > gg[d].Day) {
                            MyObject temp = gg[c];
                            gg[c] = gg[d];
                            gg[d] = temp;
                        }
                    }
                }
            }
            return gg;
        }
        private List<MyObject> Comparer(List<MyObject> listA, List<MyObject> listB) {
            List<MyObject> output = new List<MyObject>();
            //if you sure that the list was sorted, u just remove the sort function
            MyObject[] ff = sort(listA);
            MyObject[] gg = sort(listB);
            Boolean[] masuk = new Boolean[gg.Length];
            //foreach element in listA, search the changes in input
            for (int a = 0; a < listA.Count;a++ ) {
                //find element in input which contains the changes of element in listA
                Boolean ins = false;
                for (int b = 0; b < listB.Count;b++ ) {
                    if (masuk[b])
                        continue;
                    if (ff[a].Key >= gg[b].Key) {
                        if (ff[a].Key == gg[b].Key && ff[a].Day == gg[b].Day){
                            masuk[b] = true;
                            if (gg[b].Value != ff[a].Value) {
                                output.Add(new MyObject() { Key = gg[b].Key, Day = gg[b].Day, Value = gg[b].Value - ff[a].Value });
                                b = listB.Count;
                            }
                            ins = true;
                        }
                    } else {
                        b = listB.Count;
                    }
                }
                if (!ins) {
                    output.Add(new MyObject() { Key = ff[a].Key, Day = ff[a].Day, Value = -ff[a].Value });
                }
            }
            for (int a = 0; a < gg.Length;a++ ) {
                if(!masuk[a]){
                    output.Add(new MyObject() { Key = gg[a].Key, Day = gg[a].Day, Value = gg[a].Value });
                }
            }
            return output;
        }
    

    和输出:

        1 12/17/2012 12:00:00 AM -6
        2 12/17/2012 12:00:00 AM -8
        3 12/17/2012 12:00:00 AM 4
        5 12/17/2012 12:00:00 AM 10
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-30
      • 1970-01-01
      • 1970-01-01
      • 2022-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-30
      相关资源
      最近更新 更多