【问题标题】:Complicated grouping on multiple properties issue多个属性问题的复杂分组
【发布时间】:2016-08-18 22:23:08
【问题描述】:

我有大量目录类型数据,包括电子邮件、电话、邮政编码等属性。我想检测和分组重复但在所有多个属性上,其中组可以由不同属性上的重复组成,但并非所有都必须彼此直接重复,但可以通过另一行关联。

示例数据:

Name       |     Email     |     Tel     |     Postcode
John Sim   |     j@j.com   |     111     |     C67
J Sim      |               |     111     |
John S     |               |             |     C67

我希望能够找到一种方法来检测电子邮件、电话和邮政编码属性上的重复项,但即使它们并不完全匹配。因此,在上面的示例中,我会将上述所有 3 个分组,即使最后一个与中间的没有直接匹配,但第一个与两者共享一个匹配。

如果这有意义!显然这是非常简化的,我有 100 条记录正在尝试以一种可以显示重复组的方式进行分组。

到目前为止,我发现了一些非常低效的方法,包括遍历每一行,对任何直接重复的内容进行分组,然后检查这些重复中的任何一个是否也有重复,如果有,将它们一起移动到一个新的团体。但我正在努力寻找一些灵感和更有效的方法:)

谢谢!

【问题讨论】:

  • 是什么让这 3 条记录重复,我只看到 2 条符合条件?
  • @Aybe 听起来是因为 first.Tel == second.Tel 和 first.Postcode == third.Postcode。
  • 如果最后一条记录的 Tel 为 999 会怎样。这仍会被视为重复吗?
  • 另外你会考虑名字吗?还是完全忽略它?例如。如果你有唱片 Jane Doe |电话:111,你想和这个一起分组吗?
  • 是的,要求有点模糊。

标签: c# .net linq duplicates grouping


【解决方案1】:

您可以使用 foreach 循环中的索引并在每次迭代中进行组合,如下所示将复杂度降低到 O(n):

        foreach (var entry in list)
        {
            Group emailGroup = null;
            Group telGroup = null;
            Group postcodeGroup = null;

            if (entry.Email != null && _emailGroups.TryGetValue(entry.Email, out emailGroup))
                if (!emailGroup.Add(entry)) emailGroup = null;
            if (entry.Tel != null && _telGroups.TryGetValue(entry.Tel, out telGroup))
                if (!telGroup.Add(entry)) telGroup = null;
            if (entry.Postcode != null && _postcodeGroups.TryGetValue(entry.Postcode, out postcodeGroup))
                if (!postcodeGroup.Add(entry)) postcodeGroup = null;

            if (emailGroup == null && telGroup == null && postcodeGroup == null)
            {
                CreateGroup(entry);
                continue;
            }

            CombineGroups(emailGroup, telGroup, postcodeGroup);
        }

当然,您必须决定 + 处理您想要在碰撞等情况下执行的操作,如果需要,添加任何名称逻辑(例如拆分名字 + 中间名 + 姓氏),然后在每个上执行 2 路包含(相当昂贵,所以可能想看看字符串索引)

完整代码 + 测试

见方法

[Test]
public void Test()

在下面。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using NUnit.Framework;

namespace StackOverflow
{
    [TestFixture]
    public class Class1
    {
        private Dictionary<string, Group> _emailGroups;
        private Dictionary<string, Group> _telGroups;
        private Dictionary<string, Group> _postcodeGroups;

        private void CreateGroup(Entry entry)
        {
            var group = new Group(entry);

            if (group.Email != null && !_emailGroups.ContainsKey(group.Email))
                _emailGroups[group.Email] = group;

            if (group.Tel != null && !_emailGroups.ContainsKey(group.Tel))
                _telGroups[group.Tel] = group;

            if (group.PostCode != null && !_emailGroups.ContainsKey(group.PostCode))
                _postcodeGroups[group.PostCode] = group;
        }

        private void CombineGroups(Group emailGroup, Group telGroup, Group postcodeGroup)
        {
            if (emailGroup != telGroup && emailGroup != null && telGroup != null)
            {
                if (emailGroup.CanCombine(telGroup))
                {
                    emailGroup.Add(telGroup);
                    UpdateIndexes(emailGroup, telGroup);
                    telGroup = null;
                }
                ;
            }

            if (emailGroup != postcodeGroup && emailGroup != null && postcodeGroup != null)
            {
                if (emailGroup.CanCombine(postcodeGroup))
                {
                    emailGroup.Add(postcodeGroup);
                    UpdateIndexes(emailGroup, postcodeGroup);
                    postcodeGroup = null;
                }
                ;
            }

            if (telGroup != postcodeGroup && telGroup != null && postcodeGroup != null)
            {
                if (telGroup.CanCombine(postcodeGroup))
                {
                    telGroup.Add(postcodeGroup);
                    UpdateIndexes(telGroup, postcodeGroup);
                    postcodeGroup = null;
                }
                ;
            }
        }

        private void UpdateIndexes(Group newGroup, Group oldGroup)
        {
            Group group;
            if (oldGroup.Email != null
                && _emailGroups.TryGetValue(oldGroup.Email, out group)
                && group == oldGroup)
                _emailGroups[oldGroup.Email] = newGroup;

            if (oldGroup.Tel != null
                && _telGroups.TryGetValue(oldGroup.Tel, out group)
                && group == oldGroup)
                _telGroups[oldGroup.Tel] = newGroup;

            if (oldGroup.PostCode != null
                && _postcodeGroups.TryGetValue(oldGroup.PostCode, out group)
                && group == oldGroup)
                _postcodeGroups[oldGroup.PostCode] = newGroup;
        }

        public class Group
        {
            public HashSet<Entry> Entries = new HashSet<Entry>();

            public Group(Entry entry)
            {
                Email = entry.Email;
                Tel = entry.Tel;
                PostCode = entry.PostCode;
                Entries.Add(entry);
            }

            public string Email { get; set; }
            public string Tel { get; set; }
            public string PostCode { get; set; }

            public bool Matches(Entry entry)
            {
                if (Email != null && entry.Email != null && entry.Email != Email)
                    return false;

                if (Tel != null && entry.Tel != null && entry.Tel != Tel)
                    return false;

                if (PostCode != null && entry.PostCode != null && entry.PostCode != PostCode)
                    return false;

                return true;
            }

            public bool Add(Entry entry)
            {
                if (!Matches(entry))
                    return false;

                Entries.Add(entry);

                if (Email == null && entry.Email != null)
                    Email = entry.Email;

                if (Tel == null && entry.Tel != null)
                    Tel = entry.Tel;

                if (PostCode == null && entry.PostCode != null)
                    PostCode = entry.PostCode;

                return true;
            }

            public bool CanCombine(Group entry)
            {
                if (Email != null && entry.Email != null && Email != entry.Email)
                    return false;

                if (Tel != null && entry.Tel != null && Tel != entry.Tel)
                    return false;

                if (PostCode != null && entry.PostCode != null && PostCode != entry.PostCode)
                    return false;

                return true;
            }

            public void Add(Group group)
            {
                foreach (var entry in group.Entries)
                {
                    Add(entry);
                }
            }

            public override string ToString()
            {
                var sb = new StringBuilder();

                sb.AppendLine($"Key: {Email ?? "null"} | {Tel ?? "null"} | {PostCode ?? "null"}");

                foreach (var entry in Entries)
                {
                    sb.AppendLine(entry.ToString());
                }

                return sb.ToString();
            }
        }

        public class Entry
        {
            public Entry(string name, string email, string tel, string postCode)
            {
                Name = name;
                Email = email;
                Tel = tel;
                PostCode = postCode;
            }

            public string Name { get; set; }
            public string Email { get; set; }
            public string Tel { get; set; }
            public string PostCode { get; set; }

            public override string ToString()
            {
                return $"Entry: {Name ?? "null"} | {Email ?? "null"} | {Tel ?? "null"} | {PostCode ?? "null"}";
            }
        }

        [Test]
        public void Test()
        {
            var list = new List<Entry>
            {
                new Entry("John S", null, null, "C67"),
                new Entry("J Sim", null, "111", null),
                new Entry("John Sim", "j@j.com", "111", "C67")
            };

            _emailGroups = new Dictionary<string, Group>();
            _telGroups = new Dictionary<string, Group>();
            _postcodeGroups = new Dictionary<string, Group>();

            foreach (var entry in list)
            {
                Group emailGroup = null;
                Group telGroup = null;
                Group postcodeGroup = null;

                if (entry.Email != null && _emailGroups.TryGetValue(entry.Email, out emailGroup))
                    if (!emailGroup.Add(entry)) emailGroup = null;
                if (entry.Tel != null && _telGroups.TryGetValue(entry.Tel, out telGroup))
                    if (!telGroup.Add(entry)) telGroup = null;
                if (entry.PostCode != null && _postcodeGroups.TryGetValue(entry.PostCode, out postcodeGroup))
                    if (!postcodeGroup.Add(entry)) postcodeGroup = null;

                if (emailGroup == null && telGroup == null && postcodeGroup == null)
                {
                    CreateGroup(entry);
                    continue;
                }

                CombineGroups(emailGroup, telGroup, postcodeGroup);
            }

            var groups = _emailGroups.Select(x => x.Value)
                .Union(_telGroups.Select(x => x.Value))
                .Union(_postcodeGroups.Select(x => x.Value))
                .Distinct()
                .ToList();

            foreach (var grp in groups)
            {
                Console.WriteLine(grp.ToString());
            }

            groups.Should().HaveCount(1);

            groups.First().Entries.Should().HaveCount(3);
        }
    }
}

【讨论】:

    猜你喜欢
    • 2011-10-24
    • 2011-09-21
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    • 2017-04-10
    • 2012-02-08
    • 2021-05-18
    • 2011-01-16
    相关资源
    最近更新 更多