【问题标题】:Most efficient way to distribute non unique elements across multiple lists在多个列表中分配非唯一元素的最有效方法
【发布时间】:2021-06-04 09:26:23
【问题描述】:

假设我有一个整数或其他列表

List<int> motherlist = { 1, 1, 2, 5, 7, 2, 2, 2, 6, 1 }

Console.WriteLine(children.Count); // 10

我想查找所有重复项,而不是将它们从列表中删除,而是将它们分布到其他列表中,因此所有子项的最终计数应与母列表相同:

List<List<int>> children = { { 1, 2, 5, 7, 6 }, { 1, 2 }, { 1, 2 }, { 2 }}

Console.WriteLine(children.Sum(l => l.Count())); // 10 same as mother

到目前为止,我尝试了一种蛮力方法,循环遍历母亲的所有元素,将元素与所有其他元素进行比较并检查重复项,如果发现重复项,我将其添加到存储桶列表(列表列表)等等直到最后一个元素。 但是对于 300 个项目的母列表,蛮力方法只需要 7 个 CPU 秒。 我想如果我有 1000 件物品,这将需要很长时间。

在 C# .NET 中有更快的方法吗?

【问题讨论】:

  • 应该保持母列表中元素的顺序吗?
  • Motherlist 不是我关心的。因此,我需要很多孩子,如果以后需要,我可以重新排序他们的特定值。

标签: c# optimization


【解决方案1】:

我建议分组重复,然后循环考虑组的大小:

public static IEnumerable<List<T>> MyDo<T>(IEnumerable<T> source, 
                                           IEqualityComparer<T> comparer = null) {
  if (null == source)
    throw new ArgumentNullException(nameof(source));

  var groups = new Dictionary<T, List<T>>(comparer ?? EqualityComparer<T>.Default);

  int maxLength = 0;

  foreach (T item in source) {
    if (!groups.TryGetValue(item, out var list)) 
      groups.Add(item, list = new List<T>());
    
    list.Add(item);
    maxLength = Math.Max(maxLength, list.Count);
  }
  
  for (int i = 0; i < maxLength; ++i) {
    List<T> result = new List<T>();

    foreach (var value in groups.Values)
      if (i < value.Count)
        result.Add(value[i]);

    yield return result;
  }
}

演示:

  int[] source = new int[] { 1, 1, 2, 5, 7, 2, 2, 2, 6, 1 };

  var result = MyDo(source).ToList();

  string report = string.Join(Environment.NewLine, result
    .Select(line => $"[{string.Join(", ", line)}]"));

  Console.Write(report);

结果:

[1, 2, 5, 7, 6]
[1, 2]
[1, 2]
[2]

压力演示:

  Random random = new Random(1234); // seed, the results to be reproducible

  // We don't want 1000 items be forever; let's try 1_000_000 items
  int[] source = Enumerable
    .Range(1, 1_000_000)
    .Select(x => random.Next(1, 1000))
    .ToArray();

  Stopwatch sw = new Stopwatch();

  sw.Start();

  var result = MyDo(source).ToList();

  sw.Stop();

  Console.WriteLine($"Time: {sw.ElapsedMilliseconds} ms");

结果:(可能因工作站而异)

  Time: 50 ms

【讨论】:

  • 你的非 linq 方式可能比我的要快,因为没有预热
  • @Cid:没错,但您的解决方案仍然优化了关键部分,同时保持非关键部分简单易读。因此,如果不需要额外的微优化,我更喜欢你的。
  • 我只是看到你的方法类似于mine。那我就删掉我的。您可以将扩展名(谁会使用MyDo?)重命名为DistributeDuplicates
【解决方案2】:

我会GroupBy列表的元素,然后使用元素的计数来知道一个元素必须添加到的子列表的数量

List<int> motherlist = new List<int> { 1, 1, 2, 5, 7, 2, 2, 2, 6, 1 };
var childrens = motherlist.GroupBy(x => x).OrderByDescending(x => x.Count());
var result = new List<List<int>>();

foreach (var children in childrens)
{
    for (var i = 0; i < children.Count(); i++)
    {
        if (result.Count() <= i) result.Add(new List<int>());

        result[i].Add(children.Key);
    }
}
Console.WriteLine("{");
foreach (var res in result)
{
    Console.WriteLine($"\t{{ { string.Join(", ", res) } }}");
}
Console.WriteLine("}");

这个输出:

{
    { 2, 1, 5, 7, 6 }
    { 2, 1 }
    { 2, 1 }
    { 2 }
}

【讨论】:

  • 使用List.Count 而不是IEnumerable.Count()
【解决方案3】:

只是一个快速的镜头,但它似乎工作得很好......

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> motherlist = new List<int> { 1, 1, 2, 5, 7, 2, 2, 2, 6, 1 };

            var rnd = new Random(1);

            for (int i = 0; i < 1000; i++)
            {
                motherlist.Add(rnd.Next(1, 200));
            }

            var resultLists = new List<IEnumerable<int>>();

            while (motherlist.Any())
            {
                var subList = motherlist.Distinct().OrderBy(x => x).ToList();
                subList.ForEach(x => motherlist.Remove(x));
                resultLists.Add(subList);
            }
        }
    }
}

【讨论】:

    【解决方案4】:

    您可以使用Dictionary&lt;int, int&gt; 来跟踪每个元素的出现次数,并以O(n) 时间复杂度(大部分时间)在一次迭代中构建子列表,并且没有任何 LINQ:

    var motherlist = new List<int>() { 1, 1, 2, 5, 7, 2, 2, 2, 6, 1 };
    var counts = new Dictionary<int, int>();
    var children = new List<List<int>>();
    foreach(var element in motherlist)
    {
        counts.TryGetValue(element, out int count);
        counts[element] = ++count;
        if (children.Count < count)
        {
            children.Add(new List<int>() { element });
        }
        else
        {
            children[count - 1].Add(element);
        }
    }
    

    输出

    { 1, 2, 5, 7, 6 }
    { 1, 2 }
    { 2, 1 }
    { 2 }
    

    【讨论】:

      猜你喜欢
      • 2020-04-14
      • 1970-01-01
      • 2022-11-16
      • 1970-01-01
      • 2018-03-31
      • 1970-01-01
      • 2016-02-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多