Program Tip

foreach 루프의 현재 반복 인덱스를 어떻게 얻습니까?

programtip 2020. 9. 28. 09:56
반응형

foreach 루프의 현재 반복 인덱스를 어떻게 얻습니까?


foreach 루프의 현재 반복을 나타내는 값을 얻기 위해 C #에서 접하지 않은 드문 언어 구조 (예 : 최근에 배운 몇 가지, Stack Overflow에서 일부)가 있습니까?

예를 들어, 현재 상황에 따라 다음과 같이합니다.

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

foreach구현 컬렉션을 통해 반복입니다 IEnumerable. GetEnumerator컬렉션 을 호출 하여이를 수행하며 Enumerator.

이 열거 자에는 메서드와 속성이 있습니다.

  • MoveNext ()
  • 흐름

CurrentEnumerator가 현재 켜져있는 개체를 반환하고 다음 개체로 MoveNext업데이트 Current합니다.

인덱스의 개념은 열거의 개념과는 다르며 수행 할 수 없습니다.

이 때문에 대부분의 컬렉션은 인덱서와 for 루프 구조를 사용하여 순회 할 수 있습니다.

나는 지역 변수로 인덱스를 추적하는 것보다이 상황에서 for 루프를 사용하는 것을 매우 선호합니다.


Ian Mercer는 Phil Haack의 블로그 에 이와 유사한 솔루션을 게시했습니다 .

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

이렇게하면 Linq의 오버로드를 사용하여 항목 ( item.value)과 인덱스 ( item.i)를 얻을 수 있습니다 .Select

[inside Select] 함수의 두 번째 매개 변수는 소스 요소의 색인을 나타냅니다.

new { i, value }새로운 창조하고 익명의 개체를 .

ValueTupleC # 7.0 이상을 사용 하는 경우 다음을 사용 하여 힙 할당을 피할 수 있습니다 .

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

item.자동 구조 분해를 사용하여 제거 할 수도 있습니다 .

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

다음과 같이 할 수 있습니다.

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}

마지막으로 C # 7에는 foreach루프 (예 : 튜플) 내에서 인덱스를 가져 오는 적절한 구문이 있습니다 .

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

약간의 확장 방법이 필요합니다.

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 

for대부분의 경우 루프가 더 나은 선택 이라는 의견에 동의하지 않습니다 .

foreach유용한 구조이며 for모든 상황에서 루프로 대체 할 수 없습니다 .

예를 들어 DataReader가 있고를 사용하여 모든 레코드를 반복하는 foreach경우 Dispose 메서드를 자동으로 호출 하고 판독기를 닫습니다. 그러면 자동으로 연결을 닫을 수 있습니다. 따라서 리더를 닫는 것을 잊더라도 연결 누출을 방지하므로 더 안전합니다.

(물론 독자를 항상 닫는 것이 좋지만 그렇지 않으면 컴파일러가 그것을 포착하지 않을 것입니다. 모든 독자를 닫았다 고 보장 할 수는 없지만 연결이 누출되지 않도록 할 수 있습니다. foreach를 사용하는 습관.)

Dispose메서드 의 암시 적 호출 이 유용한 다른 예가있을 수 있습니다 .


리터럴 답변-경고, 성능은 int인덱스를 추적 하는 사용하는 것만 큼 좋지 않을 수 있습니다 . 적어도 사용하는 것보다 낫습니다 IndexOf.

Select의 인덱싱 오버로드를 사용하여 컬렉션의 각 항목을 인덱스를 알고있는 익명 개체로 래핑하면됩니다. 이것은 IEnumerable을 구현하는 모든 것에 대해 수행 할 수 있습니다.

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}

@FlySwat의 답변을 사용하여 다음 솔루션을 생각해 냈습니다.

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

열거자를 사용하여 얻은 GetEnumerator다음 루프를 사용하여 for반복합니다. 그러나 트릭은 루프의 조건을 만드는 것 listEnumerator.MoveNext() == true입니다.

MoveNext열거 자의 메서드는 다음 요소가 있고 액세스 할 수있는 경우 true를 반환하므로 반복 할 요소가 부족할 때 루프 조건으로 인해 루프가 중지됩니다.


LINQ, C # 7 및 System.ValueTupleNuGet 패키지를 사용하여 다음을 수행 할 수 있습니다.

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

일반 foreach구문을 사용하고 개체의 구성원이 아닌 값과 인덱스에 직접 액세스 할 수 있으며 두 필드를 루프 범위에서만 유지합니다. 이러한 이유로 C # 7 및 .NET을 사용할 수 있다면 이것이 최상의 솔루션이라고 생각합니다 System.ValueTuple.


인덱스 정보를 포함하는 다른 열거 자로 원래 열거자를 래핑 할 수 있습니다.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

다음은 ForEachHelper수업 코드입니다 .

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

카운터 변수를 사용하는 데 아무런 문제가 없습니다. 사실, 사용 여부 for, foreach while또는 do, 카운터 변수 어딘가에 선언 증가해야합니다.

따라서 적절하게 색인 된 컬렉션이 있는지 확실하지 않은 경우이 관용구를 사용하십시오.

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

다른 사용이 하나의 당신이 경우 알고 당신의 것을 색인 컬렉션 인덱스 액세스에 대한 O (1)이다 (이것은이 될 것이다 Array아마를 위해 List<T>(설명서를 말하지 않는다), 그러나 반드시 (예 : 다른 유형 LinkedList)) :

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

그것은 '수동'할 필요가 없다한다 운영 IEnumerator호출하여 MoveNext()및 심문하는 것은 Current- foreach특히 당신이 항목을 생략해야하는 경우, ... 귀찮게 단지를 사용하는 것이 당신을 저장 continue루프의 본문에.

완전성 을 위해 인덱스 수행 한 작업 에 따라 (위의 구성은 충분한 유연성을 제공함) Parallel LINQ를 사용할 수 있습니다.

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

우리는 AsParallel()이미 2014 년이 되었기 때문에 위를 사용하고 있으며, 속도를 높이기 위해 여러 코어를 잘 활용하고자합니다. 또한 '순차적'LINQ의 경우 ForEach()확장 메서드 만 사용 List<T>하고Array ... 그렇지 않은 foreach구문에 대해 단일 스레드를 실행하고 있기 때문에 이를 사용하는 것이 단순한을 수행하는 것보다 낫다는 것이 명확하지 않습니다 .


이 문제에 대해 방금 생각해 낸 해결책이 있습니다.

원래 코드 :

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

업데이트 된 코드

enumerable.ForEach((item, index) => blah(item, index));

연장 방법 :

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }

C # 7은 마침내이 작업을 수행하는 우아한 방법을 제공합니다.

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

자신의 색인을 추가하기 만하면됩니다. 단순하게 유지하십시오.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

IEnumerable이 아닌 List에서만 작동하지만 LINQ에는 다음이 있습니다.

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@Jonathan 나는 그것이 좋은 대답이라고 말하지 않았고, 단지 그가 요청한 것을 할 수 있다는 것을 보여주고 있다고 말했습니다 :)

@Graphain 나는 그것이 빠르다고 기대하지 않을 것입니다-그것이 어떻게 작동하는지 완전히 확신하지 못합니다. 매번 전체 목록을 반복하여 일치하는 객체를 찾을 수 있습니다. 이것은 많은 비교가 될 것입니다.

즉, List는 개수와 함께 각 개체의 인덱스를 유지할 수 있습니다.

조나단이 자세히 설명하면 더 좋은 생각이있는 것 같습니까?

그러나 더 간단하고 적응력이 뛰어나면서도 당신이 어디에 있는지 계산하는 것이 더 낫습니다.


int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

이것은 지원하는 컬렉션에서 작동합니다 IList.


왜 foreach ?!

가장 간단한 방법은 List를 사용하는 경우 foreach 대신 for사용 하는 것입니다 .

for(int i = 0 ; i < myList.Count ; i++)
{
    // Do Something...
}

또는 foreach를 사용하려면 :

foreach (string m in myList)
{
     // Do something...       
}

이것을 사용하여 각 루프의 인덱스를 khow 할 수 있습니다.

myList.indexOf(m)

이것이 제가하는 방법입니다. 단순성 / 간결성 때문에 좋지만, 루프 바디에서 많은 일을한다면 obj.Value꽤 빨리 늙어 갈 것입니다.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

컬렉션이 목록이면 다음과 같이 List.IndexOf를 사용할 수 있습니다.

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

주요 답변은 다음과 같습니다.

"분명히 인덱스의 개념은 열거의 개념과는 다르며 수행 할 수 없습니다."

이것은 현재 C # 버전에 해당되지만 개념적인 제한은 아닙니다.

MS가 새로운 C # 언어 기능을 생성하면 새로운 인터페이스 IIndexedEnumerable에 대한 지원과 함께이 문제를 해결할 수 있습니다.

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

foreach가 IEnumerable에 전달되고 IIndexedEnumerable을 확인할 수 없지만 var 인덱스로 요청되는 경우 C # 컴파일러는 인덱스 추적을위한 코드에 추가되는 IndexedEnumerable 개체로 소스를 래핑 할 수 있습니다.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

왜:

  • Foreach는 더 멋지게 보이며 비즈니스 애플리케이션에서는 성능 병목 현상이 거의 없습니다.
  • Foreach는 메모리에서 더 효율적일 수 있습니다. 각 단계에서 새 컬렉션으로 변환하는 대신 함수 파이프 라인이 있습니다. CPU 캐시 오류가 적고 GC가 적다면 CPU주기를 몇 개 더 사용하는지 누가 신경 쓰겠습니까?
  • 코더에게 색인 추적 코드를 추가하도록 요구하는 것은 아름다움을 망칩니다.
  • 구현하기가 매우 쉽고 (MS에게 감사) 이전 버전과 호환됩니다.

여기에있는 대부분의 사람들은 MS가 아니지만 이것은 정답이며 MS에 로비하여 이러한 기능을 추가 할 수 있습니다. 이미 확장 함수로 자체 반복기를 구축 하고 튜플을 사용할 수 있지만 MS는 확장 함수를 피하기 위해 구문 설탕을 뿌릴 수 있습니다.


continue이와 같은 키워드 안전 구성 을 사용하는 것이 더 좋습니다.

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

이 문제에 대한 내 해결책은 확장 방법입니다 WithIndex().

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

그것을 사용하십시오

var list = new List<int> { 1, 2, 3, 4, 5, 6 };    

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

관심을 위해 Phil Haack은 Razor Templated Delegate ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx ) 의 맥락에서 이에 대한 예제를 작성했습니다 .

그는 "IteratedItem"클래스 (아래 참조)에서 반복을 래핑하는 확장 메서드를 작성하여 반복 중에 요소와 인덱스에 액세스 할 수 있도록합니다.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

그러나 단일 작업 (즉, 람다로 제공 될 수있는 작업)을 수행하는 경우 Razor가 아닌 환경에서는 괜찮지 만 Razor가 아닌 컨텍스트에서 for / foreach 구문을 확실히 대체하지는 않습니다. .


나는 이것이 매우 효율적이어야한다고 생각하지 않지만 작동합니다.

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

나는 이것을 LINQPad 에서 만들었습니다 .

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

다음을 사용할 수도 있습니다 string.join.

var joinResult = string.Join(",", listOfNames);

다음과 같이 루프를 작성할 수 있습니다.

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

다음 구조체 및 확장 메서드를 추가 한 후.

구조체 및 확장 메서드는 Enumerable.Select 기능을 캡슐화합니다.

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}

foreach 루프의 현재 반복 값을 얻는 방법이 있다고 생각하지 않습니다. 자신을 세는 것이 가장 좋은 방법 인 것 같습니다.

왜 당신이 알고 싶어하는지 물어봐도 될까요?

대부분의 likley는 다음 세 가지 중 하나를 수행하는 것 같습니다.

1) 컬렉션에서 객체를 가져 오지만이 경우 이미 가지고 있습니다.

2) 나중에 사후 처리를 위해 개체 개수 계산 ... 컬렉션에는 사용할 수있는 Count 속성이 있습니다.

3) 루프의 순서에 따라 객체에 속성을 설정합니다 ... 컬렉션에 객체를 추가 할 때 쉽게 설정할 수 있지만.


컬렉션이 일부 메서드를 통해 개체의 인덱스를 반환 할 수없는 경우 유일한 방법은 예제와 같은 카운터를 사용하는 것입니다.

그러나 인덱스로 작업 할 때 문제에 대한 유일한 적절한 대답은 for 루프를 사용하는 것입니다. 다른 것은 시간과 공간의 복잡성은 말할 것도없고 코드 복잡성을 유발합니다.


이런 건 어때? myEnumerable이 비어 있으면 myDelimitedString이 null 일 수 있습니다.

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}

나는 방금이 문제가 있었지만 내 경우의 문제를 생각하면 예상 솔루션과 관련이없는 최상의 솔루션이 제공되었습니다.

기본적으로 하나의 소스 목록에서 읽고 대상 목록에서이를 기반으로 객체를 생성하는 것은 매우 일반적인 경우 일 수 있지만 먼저 소스 항목이 유효한지 확인하고 모든 행을 반환하고 싶습니다. 오류. 언뜻보기에 Current 속성의 개체 열거 자로 인덱스를 가져오고 싶지만 이러한 요소를 복사 할 때 현재 대상에서 현재 인덱스를 암시 적으로 알고 있습니다. 분명히 대상 객체에 따라 다르지만 저에게는 List였으며 대부분 ICollection을 구현할 것입니다.

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

항상 적용 할 수있는 것은 아니지만 언급 할 가치가있는 경우가 많습니다.

어쨌든, 요점은 때때로 당신이 가진 논리에 이미 분명하지 않은 해결책이 있다는 것입니다.


질문에 기반한 색인 정보로 무엇을 하려는지 잘 모르겠습니다. 그러나 C #에서는 일반적으로 IEnumerable.Select 메서드를 조정하여 원하는 항목에서 인덱스를 가져올 수 있습니다. 예를 들어, 값이 홀수인지 짝수인지에 대해 이와 같은 것을 사용할 수 있습니다.

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

이것은 목록에서 항목이 홀수 (1)인지 짝수 (0)인지에 대한 이름으로 사전을 제공합니다.

참고 URL : https://stackoverflow.com/questions/43021/how-do-you-get-the-index-of-the-current-iteration-of-a-foreach-loop

반응형