2012-03-28 47 views
6

我有一個列表,其中包含一些字符串類型的項目。刪除給定索引下的列表元素

List<string> lstOriginal; 

我有另一個列表,其中包含應從第一個列表中刪除的idids。

List<int> lstIndices; 

我會試着用RemoveAt移除()方法來完成這項工作,

foreach(int indice in lstIndices) 
{ 
    lstOriginal.RemoveAt(indice); 
} 

,但它崩潰,說我「索引超出範圍。」

+0

哪裏有索引列表來自哪裏?因爲您刪除了不在列表中的索引 – Frederiek 2012-03-28 13:32:37

+3

當您刪除某個項目時,它會更改之後項目的索引。如果你有索引1和3,當你刪除索引1時,索引3不再指向同一個對象。現在它可能超出界限,這就是爲什麼你會得到這個例外。 – SomeWritesReserved 2012-03-28 13:33:09

+0

這是崩潰,因爲當您從列表中刪除第一個項目時,所有索引都會相應更改。 – bporter 2012-03-28 13:33:51

回答

26

您需要對您希望從最大返回到最小的索引進行排序,以避免刪除錯誤索引處的內容。

foreach(int indice in lstIndices.OrderByDescending(v => v)) 
{ 
    lstOriginal.RemoveAt(indice); 
} 

這是爲什麼:讓我們說有五個項目的清單,並且希望在指數24刪除的項目。如果您先刪除2處的物品,那麼位於索引4處的物品將位於索引3處,並且索引4將不再位於列表中(導致您的例外)。如果您倒退,所有索引都會在您準備移除相應項目的時刻到達。

+1

這可能會奏效,但是對索引進行for循環並刪除index-i – SimpleVar 2012-03-28 13:34:22

+5

@YoryeNathan會假設列表中的索引已經以升序排列。如果他們無序,你需要做一些排序。 – Servy 2012-03-28 13:36:37

+0

@Servy如果要刪除的索引沒有被排序,那麼它將無法工作。他必須用兩種方法對他們進行排序,除非他知道他們是有序的。如果它們已經按降序排列,那麼一個簡單的循環就可以完成,但他不會遇到這個問題,所以這不是一件容易的事情。 – SimpleVar 2012-03-28 13:39:56

5

出現這種情況的原因是因爲當你從列表中刪除一個項目,每一個項目的索引它有效地減少一個之後,因此,如果你在增加索引順序刪除它們,並接近原來的結尾部分項目清單將被刪除,這些索引現在無效,因爲隨着先前的項目被刪除,清單變得更短。

最簡單的解決方法是(第一最高指數)排序降序索引列表,然後跨迭代。

-2
lstIndices.OrderByDescending(p => p).ToList().ForEach(p => lstOriginal.RemoveAt((int)p)); 

作爲一個方面說明,在foreach語句中,最好不要修改foreach運行的Ienumerable。超出範圍的錯誤可能是由於這種情況。

+0

這是不正確的:超出範圍錯誤是由於刪除項目更改其他項目的索引,並且此解決方案不能解決此問題的結果。例如,假設你想刪除索引5和6中的項目,你可以按順序調用RemoveAt(6)和RemoveAt(5),或者你可以調用RemoveAt(5)和' RemoveAt(5)'按順序。 – phoog 2012-03-28 13:44:00

+0

@phoog在這種情況下你可能是正確的,另一方面,最好不要在列舉相同列表的foreach循環中修改枚舉。 – daryal 2012-03-28 13:50:57

+0

我已更新。 – daryal 2012-03-28 13:51:14

4
for (int i = 0; i < indices.Count; i++) 
{ 
    items.RemoveAt(indices[i] - i); 
} 
+1

這假設列表按升序排序。在這種情況下,執行'for(int i = indices.Count - 1; i> = 0; i - )'會更高效。這是爲什麼?因爲索引之後的項*必須複製到之前的位置。如果從頭開始的話,如果從頭開始的話,複製的次數會減少,因爲如果從頭開始,您還要複製即將刪除的項目。 – phoog 2012-03-28 13:41:17

+0

這是很好的答案。但是列表應該被排序。謝謝Yorye Nathan – meorfi 2012-03-28 13:44:59

+0

這些方法之間的重新分配不會有區別,但是你的循環確實更具可讀性。當然,你少做一次原子計算,所以完美主義要求你贏。 – SimpleVar 2012-03-28 13:45:06

1
 var array = lstOriginal.ConvertAll(item => new int?(item)).ToArray(); 
     lstIndices.ForEach(index => array[index] = null); 
     lstOriginal = array.Where(item => item.HasValue).Select(item => item.Value).ToList(); 
5

你是如何填充指數的名單?您可能可以使用更高效的RemoveAll方法。例如,而不是這樣的:

var indices = new List<int>(); 
int index = 0; 
foreach (var item in data) 
    if (SomeFunction(data)) 
     indices.Add(index++); 

//then some logic to remove the items 

你可以這樣做:

data.RemoveAll(item => SomeFunction(item)); 

這最大限度地減少項目陣列中的新位置的複製;每個項目只複製一次。

你也可以使用一個方法組轉換在上面的例子,而不是一個拉姆達:

data.RemoveAll(SomeFunction); 
0

給出的指數爲方便擴展方法我就地刪除。它只複製一次所有項目,所以如果要刪除大量的痕跡,它將更加高效。

如果刪除的索引超出範圍,它也會拋出ArgumentOutOfRangeException

public static class ListExtensions 
{ 
    public static void RemoveAllIndices<T>(this List<T> list, IEnumerable<int> indices) 
    { 
     //do not remove Distinct() call here, it's important 
     var indicesOrdered = indices.Distinct().ToArray(); 
     if(indicesOrdered.Length == 0) 
      return; 

     Array.Sort(indicesOrdered); 

     if (indicesOrdered[0] < 0 || indicesOrdered[indicesOrdered.Length - 1] >= list.Count) 
      throw new ArgumentOutOfRangeException(); 

     int indexToRemove = 0; 
     int newIdx = 0; 

     for (int originalIdx = 0; originalIdx < list.Count; originalIdx++) 
     { 
      if(indexToRemove < indicesOrdered.Length && indicesOrdered[indexToRemove] == originalIdx) 
      { 
       indexToRemove++; 
      } 
      else 
      { 
       list[newIdx++] = list[originalIdx]; 
      } 
     } 

     list.RemoveRange(newIdx, list.Count - newIdx); 
    } 
} 
相關問題