簡體   English   中英

從集合中刪除項目的最佳方法

[英]Best way to remove items from a collection

一旦項目已知,但不是索引,從 C# 中的集合中刪除項目的最佳方法是什么? 這是一種方法,但充其量似乎不優雅。

//Remove the existing role assignment for the user.
int cnt = 0;
int assToDelete = 0;
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        assToDelete = cnt;
    }
    cnt++;
}
workspace.RoleAssignments.Remove(assToDelete);

我真正想做的是找到要按屬性(在本例中為名稱)刪除的項目,而不需要遍歷整個集合並使用 2 個附加變量。

如果 RoleAssignments 是List<T>您可以使用以下代碼。

workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);

如果您想通過集合的屬性之一訪問集合的成員,您可以考慮改用Dictionary<T>KeyedCollection<T> 這樣您就不必搜索您要查找的項目。

否則,你至少可以這樣做:

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}

@smaclell 在對@sambo99 的評論中詢問為什么反向迭代更有效。

有時效率更高。 假設您有一個人員列表,並且您想要刪除或過濾信用等級 < 1000 的所有客戶;

我們有以下數據

"Bob" 999
"Mary" 999
"Ted" 1000

如果我們向前迭代,我們很快就會陷入困境

for( int idx = 0; idx < list.Count ; idx++ )
{
    if( list[idx].Rating < 1000 )
    {
        list.RemoveAt(idx); // whoops!
    }
}

在 idx = 0 時,我們移除Bob ,然后將所有剩余元素向左移動。 下一次通過循環 idx = 1,但 list[1] 現在是Ted而不是Mary 我們最終錯誤地跳過了Mary 我們可以使用一個while循環,我們可以引入更多的變量。

或者,我們只是反向迭代:

for (int idx = list.Count-1; idx >= 0; idx--)
{
    if (list[idx].Rating < 1000)
    {
        list.RemoveAt(idx);
    }
}

已刪除項目左側的所有索引保持不變,因此您不會跳過任何項目。

如果給定要從數組中刪除的索引列表,則同樣的原則也適用。 為了使事情保持直截了當,您需要對列表進行排序,然后從最高索引到最低索引刪除項目。

現在您可以使用 Linq 並以直接的方式聲明您正在執行的操作。

list.RemoveAll(o => o.Rating < 1000);

對於刪除單個項目的這種情況,向前或向后迭代不再有效。 您也可以為此使用 Linq。

int removeIndex = list.FindIndex(o => o.Name == "Ted");
if( removeIndex != -1 )
{
    list.RemoveAt(removeIndex);
}

如果它是ICollection那么您將沒有RemoveAll方法。 這是一個可以做到這一點的擴展方法:

    public static void RemoveAll<T>(this ICollection<T> source, 
                                    Func<T, bool> predicate)
    {
        if (source == null)
            throw new ArgumentNullException("source", "source is null.");

        if (predicate == null)
            throw new ArgumentNullException("predicate", "predicate is null.");

        source.Where(predicate).ToList().ForEach(e => source.Remove(e));
    }

基於: http : //phejndorf.wordpress.com/2011/03/09/a-removeall-extension-for-the-collection-class/

對於簡單的 List 結構,最有效的方法似乎是使用 Predicate RemoveAll 實現。

例如。

 workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);

原因是:

  1. Predicate/Linq RemoveAll 方法在 List 中實現,可以訪問存儲實際數據的內部數組。 它將移動數據並調整內部數組的大小。
  2. RemoveAt 方法的實現非常慢,並且會將整個底層數據數組復制到一個新數組中。 這意味着反向迭代對於 List 是無用的

如果您在 c# 3.0 之前的時代堅持實現這一點。 您有 2 個選擇。

  • 易於維護的選項。 將所有匹配項復制到新列表中並交換基礎列表。

例如。

List<int> list2 = new List<int>() ; 
foreach (int i in GetList())
{
    if (!(i % 2 == 0))
    {
        list2.Add(i);
    }
}
list2 = list2;

要么

  • 棘手的稍微快一點的選項,它涉及將列表中的所有數據在不匹配時向下移動,然后調整數組的大小。

如果您真的經常從列表中刪除內容,也許另一種結構,例如HashTable (.net 1.1) 或Dictionary (.net 2.0) 或HashSet (.net 3.5) 更適合此目的。

集合是什么類型? 如果是列表,您可以使用有用的“RemoveAll”:

int cnt = workspace.RoleAssignments
                      .RemoveAll(spa => spa.Member.Name == shortName)

(這適用於 .NET 2.0。當然,如果您沒有較新的編譯器,則必須使用“delegate (SPRoleAssignment spa) { return spa.Member.Name == shortName; }” 而不是 nice lambda 語法。)

如果它不是列表,但仍然是 ICollection,則另一種方法:

   var toRemove = workspace.RoleAssignments
                              .FirstOrDefault(spa => spa.Member.Name == shortName)
   if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);

這需要 Enumerable 擴展方法。 (如果您堅持使用 .NET 2.0,您可以將 Mono 復制進來)。 如果是一些不能帶項目但必須帶索引的自定義集合,則其他一些 Enumerable 方法,例如 Select,會為您傳入整數索引。

這是一個很好的方法來做到這一點

http://support.microsoft.com/kb/555972

        System.Collections.ArrayList arr = new System.Collections.ArrayList();
        arr.Add("1");
        arr.Add("2");
        arr.Add("3");

        /*This throws an exception
        foreach (string s in arr)
        {
            arr.Remove(s);
        }
        */

        //where as this works correctly
        Console.WriteLine(arr.Count);
        foreach (string s in new System.Collections.ArrayList(arr)) 
        {
            arr.Remove(s);
        }
        Console.WriteLine(arr.Count);
        Console.ReadKey();

這是我的通用解決方案

public static IEnumerable<T> Remove<T>(this IEnumerable<T> items, Func<T, bool> match)
    {
        var list = items.ToList();
        for (int idx = 0; idx < list.Count(); idx++)
        {
            if (match(list[idx]))
            {
                list.RemoveAt(idx);
                idx--; // the list is 1 item shorter
            }
        }
        return list.AsEnumerable();
    }

如果擴展方法支持按引用傳遞,看起來會簡單得多! 用法:

var result = string[]{"mike", "john", "ali"}
result = result.Remove(x => x.Username == "mike").ToArray();
Assert.IsTrue(result.Length == 2);

編輯:確保即使在通過減少索引 (idx) 刪除項目時,列表循環仍然有效。

要在循環遍歷集合時執行此操作而不是修改集合異常,這是我過去采用的方法(注意原始集合末尾的 .ToList(),這會在內存中創建另一個集合,然后可以修改現有的集合)

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
    }
}

根據您使用收藏的方式,您還可以采用另一種方法。 如果您一次性下載作業(例如,當應用程序運行時),您可以即時將集合轉換為哈希表,其中:

簡稱 => SPRoleAssignment

如果你這樣做,那么當你想通過短名稱刪除一個項目時,你需要做的就是通過鍵從哈希表中刪除該項目。

不幸的是,如果您大量加載這些 SPRoleAssignments,那么就時間而言,這顯然不會更具成本效益。 如果您使用的是 .NET Framework 的新版本,其他人就使用 Linq 提出的建議會很好,但否則,您就必須堅持使用的方法。

這里有很多很好的回應; 我特別喜歡 lambda 表達式……非常干凈。 然而,我疏忽了沒有指​​定 Collection 的類型。 這是一個 SPRoleAssignmentCollection(來自 MOSS),它只有 Remove(int) 和 Remove(SPPrincipal),而不是方便的 RemoveAll()。 所以,我已經解決了這個問題,除非有更好的建議。

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name != shortName) continue;
    workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member);
    break;
}

類似於 Dictionary Collection 的觀點,我已經這樣做了。

Dictionary<string, bool> sourceDict = new Dictionary<string, bool>();
sourceDict.Add("Sai", true);
sourceDict.Add("Sri", false);
sourceDict.Add("SaiSri", true);
sourceDict.Add("SaiSriMahi", true);

var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false);

foreach (var item in itemsToDelete)
{
    sourceDict.Remove(item.Key);
}

注意:上面的代碼在 .Net Client Profile(3.5 和 4.5)中會失敗,還有一些觀眾提到它在 .Net4.0 中失敗,也不確定是哪些設置導致了問題。

所以用下面的代碼 (.ToList()) 替換 Where 語句,以避免該錯誤。 “集合已修改; 枚舉操作可能無法執行。”

var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();

根據 MSDN 從 .Net4.5 開始,Client Profile 已停止使用。 http://msdn.microsoft.com/en-us/library/cc656912(v=vs.110).aspx

首先保存您的項目,而不是刪除它們。

var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray();
for (int i = 0; i < itemsToDelete.Length; ++i)
    Items.Remove(itemsToDelete[i]);

您需要在 Item 類中覆蓋GetHashCode()

最好的方法是使用 linq。

示例類:

 public class Product
    {
        public string Name { get; set; }
        public string Price { get; set; }      
    }

林克查詢:

var subCollection = collection1.RemoveAll(w => collection2.Any(q => q.Name == w.Name));

該查詢將刪除所有元素collection1如果Name匹配任何元素Namecollection2

記得使用: using System.Linq;

如果你有一個List<T> ,那么List<T>.RemoveAll是你最好的選擇。 沒有比這更有效的了。 在內部,它使陣列一次移動,更不用說它是 O(N)。

如果你得到的只是一個IList<T>或一個ICollection<T>你大概有這三個選項:

    public static void RemoveAll<T>(this IList<T> ilist, Predicate<T> predicate) // O(N^2)
    {
        for (var index = ilist.Count - 1; index >= 0; index--)
        {
            var item = ilist[index];
            if (predicate(item))
            {
                ilist.RemoveAt(index);
            }
        }
    }

    public static void RemoveAll<T>(this ICollection<T> icollection, Predicate<T> predicate) // O(N)
    {
        var nonMatchingItems = new List<T>();

        // Move all the items that do not match to another collection.
        foreach (var item in icollection) 
        {
            if (!predicate(item))
            {
                nonMatchingItems.Add(item);
            }
        }

        // Clear the collection and then copy back the non-matched items.
        icollection.Clear();
        foreach (var item in nonMatchingItems)
        {
            icollection.Add(item);
        }
    }

    public static void RemoveAll<T>(this ICollection<T> icollection, Func<T, bool> predicate) // O(N^2)
    {
        foreach (var item in icollection.Where(predicate).ToList())
        {
            icollection.Remove(item);
        }
    }

選擇 1 或 2。

如果要執行的刪除操作較少(即謂詞在大多數情況下為假),則 1 占用的內存更小且速度更快。

如果要執行更多刪除,則 2 會更快。

3 是最干凈的代碼,但 IMO 的性能很差。 同樣,這一切都取決於輸入數據。

有關一些基准測試的詳細信息,請參閱https://github.com/dotnet/BenchmarkDotNet/issues/1505

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM