【问题标题】:Most efficient way to remove element of certain value everywhere from List? C#从列表中删除任何地方的特定值元素的最有效方法? C#
【发布时间】:2021-12-31 12:45:51
【问题描述】:

编辑:本问题底部发布的不同技术的基准。

我有一个很大的List<int>,里面装满了整数。我想从List<int> 中删除所有出现的“3”。哪种技术最有效地做到这一点?我通常会使用.Remove(3) 扩展,直到它返回false,但我担心每次调用.Remove(3) 在内部都会不必要地循环整个List<int>

编辑:建议在 cmets 中尝试

TheList = TheList.Where(x => x != 3).ToList();

但是我需要在不实例化新列表的情况下删除元素

var TheList = new List<int> { 5, 7, 8, 2, 8, 3, 1, 0, 6, 3, 9, 3, 5, 2, 7, 9, 3, 5, 5, 1, 0, 4, 5, 3, 5, 8, 2, 3 };

//technique 1
//this technique has the shortest amount of code,
//but I fear that every time the Remove() method is called,
//the entire list is internally looped over again starting at index 0

while (TheList.Remove(3)) { }

//technique 2
//this technique is an attempt to keep the keep the list from
//being looped over every time an element is removed

for (var i = 0; i < TheList.Count; i++)
{
    if (TheList[i] == 3)
    {
        TheList.RemoveAt(i);
        i--;
    }
}

有没有更好的方法来做到这一点?

基准

我测试了三种技术以从具有 100,000 个元素的数组中删除 10,138:上面显示的两种,以及 Serg 在答案中推荐的一种。结果如下:

  1. 'while' 循环:179.6808ms
  2. 'for' 循环:65.5099 毫秒
  3. 'RemoveAll' 谓词:0.5982 毫秒

基准代码:

var RNG = new Random();
//inclusive min and max random number
Func<int, int, int> RandomInt = delegate (int min, int max) { return RNG.Next(min - 1, max) + 1; };

var TheList = new List<int>();
var ThreeCount = 0;
for (var i = 0; i < 100000; i++)
{
    var TheInteger = RandomInt(0, 9);
    if (TheInteger == 3) { ThreeCount++; }
    TheList.Add(TheInteger);
}
var Technique1List = TheList.ToList();
var Technique2List = TheList.ToList();
var Technique3List = TheList.ToList();
<div style="background-color:aquamarine;color:#000000;">Time to remove @ThreeCount items</div>

//technique 1
var Technique1Stopwatch = Stopwatch.StartNew();
while (Technique1List.Remove(3)) { }
var Technique1Time = Technique1Stopwatch.Elapsed.TotalMilliseconds;
<div style="background-color:#ffffff;color:#000000;">Technique 1: @(Technique1Time)ms ('while' loop)</div>

//technique 2
var Technique2Stopwatch = Stopwatch.StartNew();
for (var i = 0; i < Technique2List.Count; i++)
{
    if (Technique2List[i] == 3)
    {
        Technique2List.RemoveAt(i);
        i--;
    }
}
var Technique2Time = Technique2Stopwatch.Elapsed.TotalMilliseconds;
<div style="background-color:#ffffff;color:#000000;">Technique 2: @(Technique2Time)ms ('for' loop)</div>

//technique 3
var Technique3Stopwatch = Stopwatch.StartNew();
var RemovedCount = Technique3List.RemoveAll(x => x == 3);
var Technique3Time = Technique3Stopwatch.Elapsed.TotalMilliseconds;
<div style="background-color:#ffffff;color:#000000;">Technique 3: @(Technique3Time)ms ('RemoveAll' predicate)</div>

【问题讨论】:

  • 这里是一行代码list = list.Where(x=&gt;x!=3).ToList();
  • @ShiljoPaulson 这是一个很好的单行程序,但不会通过调用 ToList() 来实例化一个新列表吗?我需要在不实例化新元素的情况下删除元素
  • 请注意,如果您想高效频繁地执行此操作,您可能需要考虑使用链表。从List&lt;T&gt; 中删除项目涉及复制列表的其余部分。
  • 尽管RemoveAll() 的复杂度是O(N),这是List&lt;T&gt; 的最低要求,您仍然可以通过删除所有不必要的类型检查来稍微提高性能,使方法成为非-generic 并从 Func&lt;T&gt; 委托转向原始类型检查。

标签: c# list loops


【解决方案1】:

您可以只使用 List&lt;T&gt;.RemoveAll 并传递您的谓词 - https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.removeall?view=net-6.0#System_Collections_Generic_List_1_RemoveAll_System_Predicate__0__ 。这保证了线性复杂度 O(list.Count)

TheList.RemoveAll(x=>x==3);

此外,RemoveAll 在内部执行一些特定于 GC 的事情,所以我认为在某些情况下,与简单的手工循环实现相比,这可能会提供一些额外的性能优势(但我不确定这里)。

如果你想自己做这一切,你可以查看RemoveAllhere的实现。一般来说,它只是一个while 循环,就像你的问题一样。

此外,正如我们从 GitHub 实现中看到的(以及评论中提到的 Jon Skeet),删除操作会导致列表的其余部分(第一个删除项目之后的所有项目)被复制(移动)到可用空间上,删除引起的。因此,如果您的列表非常庞大和/或想要频繁删除某些内容,您可以考虑切换到其他一些数据结构,例如链表。

【讨论】:

  • 这是最快的技术。如果您有兴趣,我会将基准测试结果放在我原来的帖子中。
猜你喜欢
  • 1970-01-01
  • 2019-09-19
  • 2016-02-11
  • 1970-01-01
  • 1970-01-01
  • 2018-03-31
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
相关资源
最近更新 更多