web-dev-qa-db-ja.com

ループごとに、どこがループの最後の繰り返しかを判断

私はforeachループを持っていて、最後の項目がListから選ばれた時にロジックを実行する必要があります。例えば:

 foreach (Item result in Model.Results)
 {
      //if current result is the last item in Model.Results
      //then do something in the code
 }

Forループとカウンタを使わずに、どのループが最後であるかを知ることはできますか?

201
mishap

最後の要素で何か異なったことをするのではなく、最後の要素で何かをする必要がある場合は、LINQを使用すると役立ちます。

Item last = Model.Results.Last();
// do something with last

最後の要素で何か別のことをする必要がある場合は、次のようなものが必要です。

Item last = Model.Results.Last();
foreach (Item result in Model.Results)
{
    // do something with each item
    if (result.Equals(last))
    {
        // do something different with the last item
    }
    else
    {
        // do something different with every item but the last
    }
}

ただし、その項目がLast()によって返される項目と同じであることを確認できるようにするには、おそらくカスタムの比較子を作成する必要があります。

Lastはコレクション全体を反復処理する必要がある可能性があるため、このアプローチは慎重に使用する必要があります。これは小さなコレクションでは問題にならないかもしれませんが、大きくなるとパフォーマンスに影響を与える可能性があります。リストに重複した項目が含まれている場合も失敗します。この場合、このようなものがより適切かもしれません:

int totalCount = result.Count();
for (int count = 0; count < totalCount; count++)
{
    Item result = Model.Results[count];
    count++;
    // do something with each item
    if (count == totalCount)
    {
        // do something different with the last item
    }
    else
    {
        // do something different with every item but the last
    }
}
250
ChrisF

古き良きforループはどうですか。

for (int i = 0; i < Model.Results.Count; i++) {

     if (i == Model.Results.Count - 1) {
           // this is the last item
     }
}

あるいはLinqとforeachを使う:

foreach (Item result in Model.Results)   
{   
     if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
             // this is the last item
     }
}

Chrisが示すように、Linqはうまくいくでしょう。列挙型の最後のものへの参照を取得するには、Last()を使用します。その参照を使用していない場合は通常のコードを使用しますが、その参照を使用している場合は追加作業を行います。その欠点は、それが常にO(N) - 複雑さになるということです。

代わりにCount()(IEnumerableもICollectionである場合はO(1)を使用できます。これはほとんどの一般的な組み込みIEnumerableに当てはまります)。

var i=0;
var count = Model.Results.Count();
foreach (Item result in Model.Results)
 {
      if(++i==count) //this is the last item
 }
36
KeithS

特定の型に対してLast()を使用すると、コレクション全体をループします。
foreachを作成してLast()を呼び出す場合、を2回ループしたことを意味します。大コレクション。

それでは解決策はdo whileループを使うことです:

using (var enumerator = collection.GetEnumerator())
{

  var last = !enumerator.MoveNext();
  T current;

  while(!last)
  {
    current = enumerator.Current;        

    //process item

    last = !enumerator.MoveNext();        

    //process item extension according to flag; flag means item

  }
}

テスト

コレクション型がIList<T>型でない限り、Last()関数はすべてのコレクション要素を通して繰り返します。

34
Shimmy
foreach (var item in objList)
{
  if(objList.LastOrDefault().Equals(item))
  {

  }
}
18

Shimmyが指摘したように、例えばコレクションがLINQ式の実際の結果である場合、Last()の使用はパフォーマンスの問題になる可能性があります。複数の繰り返しを防ぐために、次のように "ForEach"拡張メソッドを使用することができます。

var elements = new[] { "A", "B", "C" };
elements.ForEach((element, info) => {
    if (!info.IsLast) {
        Console.WriteLine(element);
    } else {
        Console.WriteLine("Last one: " + element);
    }
});

拡張メソッドは次のようになります(追加のボーナスとして、インデックスと最初の要素を見ている場合にもわかります)。

public static class EnumerableExtensions {
    public delegate void ElementAction<in T>(T element, ElementInfo info);

    public static void ForEach<T>(this IEnumerable<T> elements, ElementAction<T> action) {
        using (IEnumerator<T> enumerator = elements.GetEnumerator())
        {
            bool isFirst = true;
            bool hasNext = enumerator.MoveNext();
            int index = 0;
            while (hasNext)
            {
                T current = enumerator.Current;
                hasNext = enumerator.MoveNext();
                action(current, new ElementInfo(index, isFirst, !hasNext));
                isFirst = false;
                index++;
            }
        }
    }

    public struct ElementInfo {
        public ElementInfo(int index, bool isFirst, bool isLast)
            : this() {
            Index = index;
            IsFirst = isFirst;
            IsLast = isLast;
        }

        public int Index { get; private set; }
        public bool IsFirst { get; private set; }
        public bool IsLast { get; private set; }
    }
}
10
Daniel Wolf

イテレータ実装はそれを提供しません。あなたのコレクションはO(1)のインデックスを通してアクセス可能なIListかもしれません。その場合は、通常のfor-ループを使用できます。

for(int i = 0; i < Model.Results.Count; i++)
{
  if(i == Model.Results.Count - 1) doMagic();
}

数を知っていても索引を介してアクセスできない場合(したがって、結果はICollectionになります)、iの本体のforeachをインクリメントして長さと比較することで、自分自身を数えることができます。

これらすべてが完璧ではありません。クリスの解決策は私がこれまで見た中で一番いいかもしれません。

6
Matthias Meid

少し単純なアプローチはどうでしょうか。

Item last = null;
foreach (Item result in Model.Results)
{
    // do something with each item

    last = result;
}

//Here Item 'last' contains the last object that came in the last of foreach loop.
DoSomethingOnLastElement(last);
5
faisal

改善 Daniel Wolfの回答 さらに、あなたは別のIEnumerableの上に重ねることができます。

var elements = new[] { "A", "B", "C" };
foreach (var e in elements.Detailed())
{
    if (!e.IsLast) {
        Console.WriteLine(e.Value);
    } else {
        Console.WriteLine("Last one: " + e.Value);
    }
}

拡張メソッドの実装

public static class EnumerableExtensions {
    public static IEnumerable<IterationElement<T>> Detailed<T>(this IEnumerable<T> source)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));

        using (var enumerator = source.GetEnumerator())
        {
            bool isFirst = true;
            bool hasNext = enumerator.MoveNext();
            int index = 0;
            while (hasNext)
            {
                T current = enumerator.Current;
                hasNext = enumerator.MoveNext();
                yield return new IterationElement<T>(index, current, isFirst, !hasNext);
                isFirst = false;
                index++;
            }
        }
    }

    public struct IterationElement<T>
    {
        public int Index { get; }
        public bool IsFirst { get; }
        public bool IsLast { get; }
        public T Value { get; }

        public IterationElement(int index, T value, bool isFirst, bool isLast)
        {
            Index = index;
            IsFirst = isFirst;
            IsLast = isLast;
            Value = value;
        }
    }
}
4
Fabricio Godoy

最善のアプローチはおそらくループの後にそのステップを実行することです。

foreach(Item result in Model.Results)
{
   //loop logic
}

//Post execution logic

最後の結果に対して何かをする必要がある場合

foreach(Item result in Model.Results)
{
   //loop logic
}

Item lastItem = Model.Results[Model.Results.Count - 1];

//Execute logic on lastItem here
4
Dustin Hodges

承認された回答は、コレクション内の重複するものについては機能しません。 foreachに設定している場合は、独自のインデックス変数を追加するだけで済みます。

int last = Model.Results.Count - 1;
int index = 0;
foreach (Item result in Model.Results)
{
    //Do Things

    if (index == last)
        //Do Things with the last result

    index++;
}
2
Ehryk

".Last()"は私にはうまくいきませんでしたので、私はこのようなことをしなければなりませんでした:

Dictionary<string, string> iterativeDictionary = someOtherDictionary;
var index = 0;
iterativeDictionary.ForEach(kvp => 
    index++ == iterativeDictionary.Count ? 
        /*it's the last item */ :
        /*it's not the last item */
);
1
itcropper

Jon Skeetの優れたコードを少し調整して、前後の項目へのアクセスを許可することで、よりスマートにすることもできます。もちろん、これは実装の1項目を先読みしなければならないことを意味します。パフォーマンス上の理由から、前の項目と次の項目は現在の繰り返し項目に対してのみ保持されます。こんなふうになります:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Based on source: http://jonskeet.uk/csharp/miscutil/

namespace Generic.Utilities
{
    /// <summary>
    /// Static class to make creation easier. If possible though, use the extension
    /// method in SmartEnumerableExt.
    /// </summary>
    public static class SmartEnumerable
    {
        /// <summary>
        /// Extension method to make life easier.
        /// </summary>
        /// <typeparam name="T">Type of enumerable</typeparam>
        /// <param name="source">Source enumerable</param>
        /// <returns>A new SmartEnumerable of the appropriate type</returns>
        public static SmartEnumerable<T> Create<T>(IEnumerable<T> source)
        {
            return new SmartEnumerable<T>(source);
        }
    }

    /// <summary>
    /// Type chaining an IEnumerable&lt;T&gt; to allow the iterating code
    /// to detect the first and last entries simply.
    /// </summary>
    /// <typeparam name="T">Type to iterate over</typeparam>
    public class SmartEnumerable<T> : IEnumerable<SmartEnumerable<T>.Entry>
    {

        /// <summary>
        /// Enumerable we proxy to
        /// </summary>
        readonly IEnumerable<T> enumerable;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="enumerable">Collection to enumerate. Must not be null.</param>
        public SmartEnumerable(IEnumerable<T> enumerable)
        {
            if (enumerable == null)
            {
                throw new ArgumentNullException("enumerable");
            }
            this.enumerable = enumerable;
        }

        /// <summary>
        /// Returns an enumeration of Entry objects, each of which knows
        /// whether it is the first/last of the enumeration, as well as the
        /// current value and next/previous values.
        /// </summary>
        public IEnumerator<Entry> GetEnumerator()
        {
            using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
                bool isFirst = true;
                bool isLast = false;
                int index = 0;
                Entry previous = null;

                T current = enumerator.Current;
                isLast = !enumerator.MoveNext();
                var entry = new Entry(isFirst, isLast, current, index++, previous);                
                isFirst = false;
                previous = entry;

                while (!isLast)
                {
                    T next = enumerator.Current;
                    isLast = !enumerator.MoveNext();
                    var entry2 = new Entry(isFirst, isLast, next, index++, entry);
                    entry.SetNext(entry2);
                    yield return entry;

                    previous.UnsetLinks();
                    previous = entry;
                    entry = entry2;                    
                }

                yield return entry;
                previous.UnsetLinks();
            }
        }

        /// <summary>
        /// Non-generic form of GetEnumerator.
        /// </summary>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Represents each entry returned within a collection,
        /// containing the value and whether it is the first and/or
        /// the last entry in the collection's. enumeration
        /// </summary>
        public class Entry
        {
            #region Fields
            private readonly bool isFirst;
            private readonly bool isLast;
            private readonly T value;
            private readonly int index;
            private Entry previous;
            private Entry next = null;
            #endregion

            #region Properties
            /// <summary>
            /// The value of the entry.
            /// </summary>
            public T Value { get { return value; } }

            /// <summary>
            /// Whether or not this entry is first in the collection's enumeration.
            /// </summary>
            public bool IsFirst { get { return isFirst; } }

            /// <summary>
            /// Whether or not this entry is last in the collection's enumeration.
            /// </summary>
            public bool IsLast { get { return isLast; } }

            /// <summary>
            /// The 0-based index of this entry (i.e. how many entries have been returned before this one)
            /// </summary>
            public int Index { get { return index; } }

            /// <summary>
            /// Returns the previous entry.
            /// Only available for the CURRENT entry!
            /// </summary>
            public Entry Previous { get { return previous; } }

            /// <summary>
            /// Returns the next entry for the current iterator.
            /// Only available for the CURRENT entry!
            /// </summary>
            public Entry Next { get { return next; } }
            #endregion

            #region Constructors
            internal Entry(bool isFirst, bool isLast, T value, int index, Entry previous)
            {
                this.isFirst = isFirst;
                this.isLast = isLast;
                this.value = value;
                this.index = index;
                this.previous = previous;
            }
            #endregion

            #region Methods
            /// <summary>
            /// Fix the link to the next item of the IEnumerable
            /// </summary>
            /// <param name="entry"></param>
            internal void SetNext(Entry entry)
            {
                next = entry;
            }

            /// <summary>
            /// Allow previous and next Entry to be garbage collected by setting them to null
            /// </summary>
            internal void UnsetLinks()
            {
                previous = null;
                next = null;
            }

            /// <summary>
            /// Returns "(index)value"
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return String.Format("({0}){1}", Index, Value);
            }
            #endregion

        }
    }
}
1
Edwin

前の値を保存してループ内で操作するだけです。そして最後に 'previous'の値が最後の項目になるので、それを別の方法で扱うことができます。カウントや特別なライブラリは必要ありません。

bool empty = true;
Item previousItem;

foreach (Item result in Model.Results)
{
    if (!empty)
    {
        // We know this isn't the last item because it came from the previous iteration
        handleRegularItem(previousItem);
    }

    previousItem = result;
    empty = false;
}

if (!empty)
{
    // We know this is the last item because the loop is finished
    handleLastItem(previousItem);
}
1
voltrevo

最後の要素に反応するようにforeachを変換する方法:

List<int> myList = new List<int>() {1, 2, 3, 4, 5};
Console.WriteLine("foreach version");
{
    foreach (var current in myList)
    {
        Console.WriteLine(current);
    }
}
Console.WriteLine("equivalent that reacts to last element");
{
    var enumerator = myList.GetEnumerator();
    if (enumerator.MoveNext() == true) // Corner case: empty list.
    {
        while (true)
        {
            int current = enumerator.Current;

            // Handle current element here.
            Console.WriteLine(current);

            bool ifLastElement = (enumerator.MoveNext() == false);
            if (ifLastElement)
            {
                // Cleanup after last element
                Console.WriteLine("[last element]");
                break;
            }
        }
    }
    enumerator.Dispose();
}
1
Contango

あなたは特別にこれに専用の拡張メソッドを作ることができます:

public static class EnumerableExtensions {
    public static bool IsLast<T>(this List<T> items, T item)
        {
            if (items.Count == 0)
                return false;
            T last = items[items.Count - 1];
            return item.Equals(last);
        }
    }

そして、あなたはこのようにそれを使うことができます:

foreach (Item result in Model.Results)
{
    if(Model.Results.IsLast(result))
    {
        //do something in the code
    }
}
0
A. Morel

最後の要素を除いて各要素に追加の何かをするために、関数ベースのアプローチを使用することができます。

delegate void DInner ();

....
    Dinner inner=delegate 
    { 
        inner=delegate 
        { 
            // do something additional
        } 
    }
    foreach (DataGridViewRow dgr in product_list.Rows)
    {
        inner()
        //do something
    }
}

このアプローチには明らかな欠点があります。より複雑なケースではコードの明快さが低下します。電話代議員を呼ぶことはあまり効果的ではないかもしれません。トラブルシューティングはそれほど簡単ではないかもしれません。明るい面 - コーディングは楽しいです!

そうは言っても、あなたのコレクションのカウントがそれほど遅くないことがわかっているならば、私は些細なケースでは単純なforループを使うことをお勧めします。

0
dmitry

Jon Skeetは、この問題を解決するためにしばらく前にSmartEnumerable<T>型を作成しました。あなたはそれがここで実装されているのを見ることができます:

http://codeblog.jonskeet.uk/2007/07/27/smart-enumerations/

ダウンロードするには: http://www.yoda.arachsys.com/csharp/miscutil/

0
Spencer Ruport
     List<int> ListInt = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


                int count = ListInt.Count;
                int index = 1;
                foreach (var item in ListInt)
                {
                    if (index != count)
                    {
                        Console.WriteLine("do something at index number  " + index);
                    }
                    else
                    {
                        Console.WriteLine("Foreach loop, this is the last iteration of the loop " + index);
                    }
                    index++;

                }
 //OR
                int count = ListInt.Count;
                int index = 1;
                foreach (var item in ListInt)
                {
                    if (index < count)
                    {
                        Console.WriteLine("do something at index number  " + index);
                    }
                    else
                    {
                        Console.WriteLine("Foreach loop, this is the last iteration of the loop " + index);
                    }
                    index++;

                }
0
Zoyeb Shaikh

私が投稿したのを見なかったもう一つの方法は、キューを使うことです。これは、必要以上に繰り返すことなくSkipLast()メソッドを実装する方法に似ています。この方法でも、最後のアイテムをいくつでも作成できます。

public static void ForEachAndKnowIfLast<T>(
    this IEnumerable<T> source,
    Action<T, bool> a,
    int numLastItems = 1)
{
    int bufferMax = numLastItems + 1;
    var buffer = new Queue<T>(bufferMax);
    foreach (T x in source)
    {
        buffer.Enqueue(x);
        if (buffer.Count < bufferMax)
            continue; //Until the buffer is full, just add to it.
        a(buffer.Dequeue(), false);
    }
    foreach (T item in buffer)
        a(item, true);
}

これを呼び出すには、次のようにします。

Model.Results.ForEachAndKnowIfLast(
    (result, isLast) =>
    {
        //your logic goes here, using isLast to do things differently for last item(s).
    });
0
rrreee

linqとforeachを使う:

foreach (Item result in Model.Results)   
{   
     if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
             // this is the last item
     }
}

https://code.i-harness.com/en/q/7213ce

0
HanMyintTun

Forループを使用するだけで、if本体内にforを追加する必要はありません。

for (int i = 0; i < Model.Results.Count - 1; i++) {
    var item = Model.Results[i];
}

for条件の-1は、最後の項目をスキップします。

0
Alisson