반복하는 동안 일반 목록에서 요소를 제거하는 방법은 무엇입니까?


451

나는 더 나은 것을 찾고 있습니다 각각 처리 해야하는 요소 목록으로 작업하고 결과에 따라 목록에서 제거되는 패턴 을있습니다.

당신은 사용할 수 없습니다 .Remove(element)돌며 foreach (var element in X)(이 결과 때문에 Collection was modified; enumeration operation may not execute.예외) ... 당신은 또한 사용할 수 없습니다 for (int i = 0; i < elements.Count(); i++).RemoveAt(i) 이에 수집 상대적으로 사용자의 현재 위치를 방해하기 때문 i.

이것을 할 수있는 우아한 방법이 있습니까?

답변:


734

for 루프와 반대로 목록을 반복하십시오.

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}

예:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] > 5)
        list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

또는 술어와 함께 RemoveAll 메소드 를 사용하여 다음 을 테스트 할 수 있습니다 .

safePendingList.RemoveAll(item => item.Value == someValue);

다음은 간단한 예입니다.

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));

19
Java에서 오는 사람들의 경우 C #의 List는 삽입 / 제거가 O (n)이고 색인을 통한 검색이 O (1)이라는 점에서 ArrayList와 같습니다. 이것은 기존의 연결 목록 이 아닙니다 . 불행히도 C #에서는 "List"라는 단어를 사용하여이 데이터 구조를 설명합니다. 고전적인 연결 목록을 염두에두기 때문입니다.
Jarrod Smith

79
'List'라는 이름의 어떤 것도 'LinkedList'라고 말하지 않습니다. Java 이외의 다른 언어를 사용하는 사람들은 링크 된 목록 일 때 혼동 될 수 있습니다.
GolezTrol

5
나는 누군가가 RemoveAll에 대해 vb.net 동등한 구문을 원할 경우를 대비하여 vb.net 검색을 통해 여기에 끝났습니다.list.RemoveAll(Function(item) item.Value = somevalue)
pseudocoder

1
이것은 또한 최소한의 수정으로 자바에서 작동 : .size()대신 .Count하고 .remove(i)대신 .removeAt(i). 영리한-감사합니다!
가을 레너드

2
성능에 대해 약간의 테스트를 수행 RemoveAll()했으며 역방향 for루프 보다 3 배 많은 시간이 소요되었습니다 . 그래서 나는 적어도 중요한 부분에서 루프를 고수하고 있습니다.
Crusha K. Rool

84

간단하고 간단한 솔루션 :

컬렉션 에서 뒤로 실행되는 표준 for-loop RemoveAt(i)를 사용하고 요소를 제거하십시오.


2
목록에 많은 항목이 포함되어 있으면 한 번에 하나씩 항목을 제거하는 것이 효율적 이지 않습니다 . O (n ^ 2) 일 가능성이 있습니다. 처음 10 억 개의 항목이 모두 삭제되는 20 억 개의 항목이있는 목록을 상상해보십시오. 제거 할 때마다 이후의 모든 항목이 강제로 복사되므로 10 억 번마다 10 억 개의 항목이 복사됩니다. 이것은 역 반복 때문이 아니라 한 번에 하나씩 제거되기 때문입니다. RemoveAll은 각 항목이 최대 한 번 복사되어 선형이되도록합니다. 한 번에 하나씩 제거하는 데 10 억 배가 느려질 수 있습니다. O (n) 대 O (n ^ 2).
브루스 도슨

71

컬렉션을 반복하면서 컬렉션에서 요소를 제거하려면 역 반복이 가장 먼저 떠 오릅니다.

운 좋게도, 불필요한 입력을 필요로하고 오류가 발생하기 쉬운 for 루프를 작성하는 것보다 더 우아한 솔루션이 있습니다.

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

foreach (int myInt in test.Reverse<int>())
{
    if (myInt % 2 == 0)
    {
        test.Remove(myInt);
    }
}

7
이것은 나를 위해 완벽하게 작동했습니다. 간단하고 우아하며 코드를 최소한으로 변경해야합니다.
Stephen MacDougall

1
이것은 단지 천재적 천재입니까? 그리고 @StephenMacDougall에 동의합니다. 루프에 C ++을 사용할 필요가 없으며 의도 한대로 LINQ를 사용할 필요가 없습니다.
Piotr Kula

5
간단한 foreach (int myInt in test.ToList ())에 비해 이점이 없습니다. {if (myInt % 2 == 0) {test.Remove (myInt); }} 여전히 Reverse를 위해 사본을 할당해야하는데 Huh? 순간-왜 반전이 있습니까?
jahav

11
@jedesah 예, Reverse<T>()목록을 거꾸로 반복하는 반복자를 만들지 만 목록 자체와 동일한 크기의 추가 버퍼할당 합니다 ( referencesource.microsoft.com/#System.Core/System/Linq/… ). Reverse<T>여분의 메모리를 할당하지 않고 원래 목록을 역순으로 진행하지 않습니다. 따라서 모두 ToList()Reverse()동일한 메모리 소비를 (모두 복사본을 생성)이 있지만 ToList()데이터에 아무것도하지 않습니다. 을 사용하면 Reverse<int>()왜 어떤 이유로 목록이 반전되는지 궁금합니다.
jahav 2016 년

1
@jahav 당신의 요점을 참조하십시오. 구현이 Reverse<T>()새로운 버퍼 를 생성 한다는 것은 매우 실망 스럽습니다 . 왜 그것이 필요한지 잘 모르겠습니다. 의 기본 구조에 따라 Enumerable선형 메모리를 할당하지 않고 역 반복을 달성하는 것이 가능 해야하는 것으로 보입니다 .
jedesah

68
 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

이 경우 ".ToList ()"를 추가 지칠대로 지친를 빼고 목록 (또는 LINQ 쿼리의 결과), 직접 "목록"에서 "항목"을 제거 할 수 있습니다에 " 컬렉션 수정; 열거 작업이 실행되지 않을 수 있습니다 ." 오류. 컴파일러는 "목록"의 복사본을 만들어서 어레이에서 안전하게 제거를 수행 할 수 있습니다.

하지만 이 패턴은 매우 효율적이지 않습니다, 그것은 자연적인 느낌을 가지고 있습니다 거의 모든 상황에 유연 . 예를 들어 각 "항목"을 DB에 저장하고 DB 저장에 성공한 경우에만 목록에서 제거하십시오.


6
효율성이 중요하지 않은 경우 이것이 가장 좋은 솔루션입니다.
IvanP

2
이것도 빠르고 읽기 쉽습니다 : list.RemoveAll (i => true);
santiago arizti

1
@Greg Little, 내가 당신을 올바르게 이해 했습니까? ToList () 컴파일러를 추가하면 복사 된 컬렉션을 거치지 만 원본에서 제거합니까?
Pyrejkee

목록에 중복 된 항목이 있고 나중에 목록에서 발생하는 항목 만 제거하려는 경우 잘못된 항목이 제거되지 않습니까?
팀 MB

이것은 객체 목록에도 적용됩니까?
Tom el Safadi

23

당신이 요소 선택 않습니다 오히려 당신이 요소를 제거하는 것보다 원하는을 하지 않습니다 싶어합니다. 이것은 요소를 제거하는 것보다 훨씬 쉽습니다 (일반적으로 더 효율적입니다).

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

아래에 Michael Dillon이 남긴 의견에 대한 답변으로 이것을 의견으로 게시하고 싶지만 어쨌든 내 대답에 너무 길어서 아마도 유용합니다.

개인적으로 한 번에 하나씩 항목을 제거하지는 않습니다. 제거가 필요한 경우 호출 RemoveAll하면 술어를 취하고 내부 배열을 한 번만 재정렬하는 반면 제거하는 모든 요소에 Remove대해 Array.Copy작업을 수행합니다. RemoveAll훨씬 더 효율적입니다.

그리고 목록 반복 당신이있는 거 뒤로, 당신은 이미 당신이 전화에 훨씬 더 효율적으로 될 수 있도록, 제거 할 요소의 인덱스가있을 때 RemoveAt, 때문에Remove 첫 번째 요소의 인덱스를 찾기 위해리스트의 탐색을 수행하면 '제거하려고하지만 이미 해당 색인을 알고 있습니다.

결국, 나는 Removefor-loop를 호출 할 이유가 없습니다 . 이상적으로는 가능하다면 위의 코드를 사용하여 필요에 따라 목록에서 요소를 스트리밍하므로 두 번째 데이터 구조를 전혀 만들지 않아도됩니다.


1
이 중 하나를 선택하거나 원하지 않는 요소에 대한 포인터를 두 번째 목록에 추가 한 다음 루프가 끝나면 제거 목록을 반복하고이를 사용하여 요소를 제거하십시오.
Michael Dillon

22

일반 목록에서 ToArray ()를 사용하면 일반 목록에서 Remove (item)를 수행 할 수 있습니다.

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }

2
이것은 잘못된 것이 아니지만 전체 목록을 배열로 복사하는 대신 제거하려는 항목의 두 번째 "스토리지"목록을 생성 할 필요가 없다는 점을 지적해야합니다. 직접 선택한 요소의 두 번째 목록에는 항목이 더 적을 수 있습니다.
Ahmad Mageed

20

.ToList ()를 사용하면이 질문에 설명 된대로 목록을 복사합니다. ToList ()-새 목록을 작성합니까?

ToList ()를 사용하면 실제로 사본을 반복하므로 원래 목록에서 제거 할 수 있습니다.

foreach (var item in listTracked.ToList()) {    

        if (DetermineIfRequiresRemoval(item)) {
            listTracked.Remove(item)
        }

     }

1
그러나 성능 관점에서 목록을 복사하는 데 시간이 걸릴 수 있습니다. 쉽고 좋은 방법이지만 성능이 좋지 않음
Florian K

12

삭제할 항목을 결정하는 함수에 부작용이없고 항목을 변경하지 않는 경우 (순수한 기능) 간단하고 효율적인 (선형 시간) 솔루션은 다음과 같습니다.

list.RemoveAll(condition);

부작용이 있으면 다음과 같이 사용하십시오.

var toRemove = new HashSet<T>();
foreach(var item in items)
{
     ...
     if(condition)
          toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);

해시가 양호하다고 가정하면 여전히 선형 시간입니다. 그러나 해시 세트로 인해 메모리 사용이 증가했습니다.

마지막으로 목록이 단지 IList<T>대신에있는 경우이 특별한 foreach 반복자List<T>어떻게 수행 할 수 있습니까? . 이것은 IList<T>많은 다른 답변의 2 차 런타임과 비교하여 일반적인 구현으로 선형 런타임을 갖습니다 .


11

어떤 조건에서도 제거가 가능하므로

list.RemoveAll(item => item.Value == someValue);

처리가 항목을 변경하지 않고 부작용이없는 경우에 최상의 솔루션입니다.
코드 InChaos

9
List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));

8

foreach를 사용할 수는 없지만 다음과 같이 항목을 제거 할 때 전달을 반복하고 루프 인덱스 변수를 관리 할 수 ​​있습니다.

for (int i = 0; i < elements.Count; i++)
{
    if (<condition>)
    {
        // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation.
        elements.RemoveAt(i--);
    }
}

일반적으로 이러한 모든 기술은 반복되는 컬렉션의 동작에 의존합니다. 여기에 표시된 기술은 표준 List (T)와 함께 작동합니다. (자신의 컬렉션 클래스를 작성하는 꽤 가능 반복자 그 않는 foreach는 루프 동안 항목을 제거 할 수 있습니다.)


6

목록을 반복하는 동안 목록에서 Remove또는 RemoveAt목록을 사용 하는 것은 의도적으로 어려워졌습니다. 왜냐하면 거의 항상 잘못된 일 이기 때문입니다 . 영리한 트릭으로 작동시킬 수는 있지만 매우 느릴 것입니다. 전화를 걸 때마다 Remove전체 목록을 스캔하여 제거하려는 요소를 찾아야합니다. 전화를 걸 때마다 RemoveAt후속 요소를 1 위치 왼쪽으로 이동해야합니다. 이와 같이, 어떠한 용액을 사용 Remove하거나 RemoveAt, 차 시간을 필요로 할 O (n²) .

가능하면 사용하십시오 RemoveAll. 그렇지 않으면 다음 패턴 은 목록 을 선형 시간 O (n) 에서 제자리 로 필터링합니다 .

// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
    // Test whether this is an element that we want to keep.
    if (elements[i] % 3 > 0) {
        // Add it to the list of kept elements.
        elements[kept] = elements[i];
        kept++;
    }
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);

3

나는 소원 은 "패턴"이 같은했다 :

foreach( thing in thingpile )
{
    if( /* condition#1 */ )
    {
        foreach.markfordeleting( thing );
    }
    elseif( /* condition#2 */ )
    {
        foreach.markforkeeping( thing );
    }
} 
foreachcompleted
{
    // then the programmer's choices would be:

    // delete everything that was marked for deleting
    foreach.deletenow(thingpile); 

    // ...or... keep only things that were marked for keeping
    foreach.keepnow(thingpile);

    // ...or even... make a new list of the unmarked items
    others = foreach.unmarked(thingpile);   
}

이것은 프로그래머의 두뇌에서 진행되는 프로세스와 코드를 정렬합니다.


1
충분히 쉽다. 부울 플래그 배열을 생성하기 만하면됩니다 (예를 들어 Nullable<bool>, 표시를 허용하지 않으려면 예를 들어 , 3 가지 상태 유형 사용).
Dan Bechard

3

술어 가 요소의 부울 특성 이라고 가정하면 true이면 요소를 제거해야합니다.

        int i = 0;
        while (i < list.Count())
        {
            if (list[i].predicate == true)
            {
                list.RemoveAt(i);
                continue;
            }
            i++;
        }

때로는 목록을 순서대로 이동하는 것이 더 효과적 일 수 있기 때문에 이것을 상향 투표했습니다. 목록이 주문되었으므로 제거하지 않는 첫 번째 항목을 찾을 때 중지 할 수 있습니다. (이 예제에서 i ++가있는 '중단'을 상상해보십시오.
FrankKrumnow

3

유지하고 싶지 않은 요소를 필터링 한 LINQ 쿼리에서 목록을 다시 할당합니다.

list = list.Where(item => ...).ToList();

목록이 너무 크지 않으면이 작업을 수행 할 때 심각한 성능 문제가 없어야합니다.


3

목록을 반복하면서 목록에서 항목을 제거하는 가장 좋은 방법은 RemoveAll() 입니다. 그러나 사람들이 작성한 주요 관심사는 루프 내부에서 복잡한 작업을 수행하거나 복잡한 비교 사례를 가져야한다는 것입니다.

해결책은 여전히 ​​사용 RemoveAll()하지만 다음 표기법을 사용하는 것입니다.

var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item => 
{
    // Do some complex operations here
    // Or even some operations on the items
    SomeFunction(item);
    // In the end return true if the item is to be removed. False otherwise
    return item > 5;
});

2
foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

첫 번째 목록에서 완전히 새로운 목록을 작성하십시오. 완전히 새로운 목록을 만들면 아마도 이전 방법에 비해 성능이 뛰어 나기 때문에 "Right"가 아니라 "Easy"라고 말합니다 (벤치 마법에 신경 쓰지 않았습니다). 일반적으로이 패턴을 선호하며 극복에도 유용 할 수 있습니다 Linq-to-Entities 제한 사항.

for(i = list.Count()-1;i>=0;i--)

{

item=list[i];

if (item.Delete) list.Remove(item);

}

이 방법은 일반 오래된 For 루프를 사용하여 목록을 거꾸로 순환합니다. 컬렉션의 크기가 변경되면이 작업을 앞뒤로 수행하는 것이 문제가 될 수 있지만 항상 안전해야합니다.


1

반복하는 목록을 복사하십시오. 그런 다음 사본에서 제거하고 원본을 중재하십시오. 거꾸로 돌아가는 것은 혼란스럽고 병렬로 반복 할 때 제대로 작동하지 않습니다.

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});

1

나는 이것을 좋아할 것이다

using System.IO;
using System;
using System.Collections.Generic;

class Author
    {
        public string Firstname;
        public string Lastname;
        public int no;
    }

class Program
{
    private static bool isEven(int i) 
    { 
        return ((i % 2) == 0); 
    } 

    static void Main()
    {    
        var authorsList = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", no = 2 },
            new Author{ Firstname = "Fred", Lastname = "Jones", no = 3 },
            new Author{ Firstname = "Brian", Lastname = "Brains", no = 4 },
            new Author{ Firstname = "Billy", Lastname = "TheKid", no = 1 }
        };

        authorsList.RemoveAll(item => isEven(item.no));

        foreach(var auth in authorsList)
        {
            Console.WriteLine(auth.Firstname + " " + auth.Lastname);
        }
    }
}

산출

Fred Jones
Billy TheKid

0

나는 주어진 상황에서 모든 n 번째 요소 를 제거 해야하는 비슷한 상황 에 처했다 List<T>.

for (int i = 0, j = 0, n = 3; i < list.Count; i++)
{
    if ((j + 1) % n == 0) //Check current iteration is at the nth interval
    {
        list.RemoveAt(i);
        j++; //This extra addition is necessary. Without it j will wrap
             //down to zero, which will throw off our index.
    }
    j++; //This will always advance the j counter
}

0

목록에서 항목을 제거하는 비용은 제거 할 항목 다음에 오는 항목 수에 비례합니다. 품목의 전반부에서 제거 할 수있는 자격이있는 경우, 품목을 개별적으로 제거하는 방법에 따라 N * N / 4 항목 복사 작업을 수행해야하므로 목록이 크면 비용이 매우 많이들 수 있습니다 .

더 빠른 방법은 목록을 스캔하여 제거 할 첫 번째 항목 (있는 경우)을 찾은 다음 해당 항목부터 해당 항목을 해당 지점에 보관해야합니다. 이 작업이 완료되면 R 항목을 유지해야하는 경우 목록의 첫 번째 R 항목이 해당 R 항목이되고 삭제가 필요한 모든 항목이 끝납니다. 해당 항목을 역순으로 삭제하면 시스템에서 해당 항목을 복사하지 않아도되므로 목록에 첫 번째 F를 포함하여 R 항목이 모두 N 개 보유 된 항목이있는 경우 다음을 수행해야합니다. RF 항목을 복사하고 목록을 한 항목 NR 번 줄입니다. 모든 선형 시간.


0

내 접근 방식은 먼저 인덱스 목록을 만들고 삭제해야합니다. 그런 다음 색인을 반복하고 초기 목록에서 항목을 제거합니다. 이것은 다음과 같습니다

var messageList = ...;
// Restrict your list to certain criteria
var customMessageList = messageList.FindAll(m => m.UserId == someId);

if (customMessageList != null && customMessageList.Count > 0)
{
    // Create list with positions in origin list
    List<int> positionList = new List<int>();
    foreach (var message in customMessageList)
    {
        var position = messageList.FindIndex(m => m.MessageId == message.MessageId);
        if (position != -1)
            positionList.Add(position);
    }
    // To be able to remove the items in the origin list, we do it backwards
    // so that the order of indices stays the same
    positionList = positionList.OrderByDescending(p => p).ToList();
    foreach (var position in positionList)
    {
        messageList.RemoveAt(position);
    }
}

0

C #에서 쉬운 방법은 삭제하려는 항목을 표시 한 다음 반복 할 새 목록을 만드는 것입니다 ...

foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}  

또는 더 간단한 사용 linq ....

list.RemoveAll(p=>p.Delete);

그러나 다른 작업이나 스레드가 제거하는 동안 동시에 동일한 목록에 액세스하고 ConcurrentList를 사용할 수 있는지 고려할 가치가 있습니다.


0

특성으로 제거 할 요소를 추적하고 프로세스 후에 모두 제거하십시오.

using System.Linq;

List<MyProperty> _Group = new List<MyProperty>();
// ... add elements

bool cond = true;
foreach (MyProperty currObj in _Group)
{
    if (cond) 
    {
        // SET - element can be deleted
        currObj.REMOVE_ME = true;
    }
}
// RESET
_Group.RemoveAll(r => r.REMOVE_ME);

0

누군가에게 도움이되는 경우를 대비하여 2 센트를 추가하고 싶었습니다. 비슷한 문제가 있었지만 반복되는 동안 배열 목록에서 여러 요소를 제거해야했습니다. 오류가 발생하여 여러 요소가 제거되었지만 루프의 색인이 유지되지 않았기 때문에 색인이 배열 목록의 크기보다 큽니다. 그 추적. 나는 간단한 점검으로 이것을 고쳤다.

ArrayList place_holder = new ArrayList();
place_holder.Add("1");
place_holder.Add("2");
place_holder.Add("3");
place_holder.Add("4");

for(int i = place_holder.Count-1; i>= 0; i--){
    if(i>= place_holder.Count){
        i = place_holder.Count-1; 
    }

// some method that removes multiple elements here
}

-4
myList.RemoveAt(i--);

simples;

1
무엇 않습니다 simples;여기합니까?
Denny

6
그것의 대리인. 그것은 실행될 때마다 내 대답을 downvotes
SW

SW ROFL! 당신의 의견을 사랑합니다.
Erick Brown
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.