web-dev-qa-db-ja.com

それぞれに使用する場合の最後のループの特定

オブジェクトに対して「foreach」を実行するときに、最後のループの反復で何か別のことを行いたいと思います。私はRubyを使用していますが、C#についても同じことが言えます、Javaなど。

  list = ['A','B','C']
  list.each{|i|
    puts "Looping: "+i # if not last loop iteration
    puts "Last one: "+i # if last loop iteration
  }

必要な出力は次と同等です。

  Looping: 'A'
  Looping: 'B'
  Last one: 'C'

明らかな回避策は、'for i in 1..list.length'を使用してforループにコードを移行することですが、for eachソリューションはより優雅です。ループ中に特別なケースをコーディングする最も優雅な方法は何ですか? foreachで実行できますか?

48
Matt

参照最後の項目に取得してから、foreachループ内の比較に使用するのはどうですか? 私は自分でKlauseMeierが言及したインデックスベースのループを使用するので、これを行うべきだとは言いません。申し訳ありませんが、Rubyなので、次のサンプルはC#にあります!気にしないでください:-)

string lastItem = list[list.Count - 1];
foreach (string item in list) {
    if (item != lastItem)
        Console.WriteLine("Looping: " + item);
    else    Console.Writeline("Lastone: " + item);
}

値ではなく参照で比較するように次のコードを修正しました(値型ではなく参照型のみを使用できます)。次のコードは、同じ文字列を含む複数のオブジェクトをサポートする必要があります(ただし、同じ文字列オブジェクトではありません)。

string lastItem = list.Last();
foreach (string item in list) {
    if (!object.ReferenceEquals(item, lastItem))
        Console.WriteLine("Looping: " + item);
    else        Console.WriteLine("Lastone: " + item);
}

上記のコードの制限。 (1)値型ではなく、文字列または参照型に対してのみ機能します。 (2)同じオブジェクトはリストに一度しか表示できません。同じコンテンツを含む異なるオブジェクトを持つことができます。 C#は同じコンテンツを持つ文字列に対して一意のオブジェクトを作成しないため、リテラル文字列を繰り返し使用することはできません。

そして、私は愚かではありません。インデックスベースのループを使用することを知っています。最初の回答を最初に投稿したとき、私はすでにそう言っていました。質問のcontextでできる限り最高の回答を提供しました。私はこれを説明し続けるのにうんざりしているので、あなたは私の答えを削除するために投票するだけです。これがなくなったらとても嬉しいです。ありがとう

23
anonymous

ここには、複雑で読みにくいコードがたくさんあります...単純にしないでください:

var count = list.Length;

foreach(var item in list)
    if (--count > 0)
        Console.WriteLine("Looping: " + item);
    else
        Console.Writeline("Lastone: " + item);

それはたった1つの追加ステートメントです!

もう1つの一般的な状況は、アイテム間にセパレーターを置くなど、最後のアイテムで何か余分なことをしたいということです。

var count = list.Length;

foreach(var item in list)
{
    Console.Write(item);
    if (--count > 0)
        Console.Write(",");
}
38
Bigjim

Foreachコンストラクト(Java間違いなく、おそらく他の言語でも))は、反復の場合に最も一般的な種類を表すことを目的としています。ハッシュベースのセットには順序がないため、is no "last element"。最後の反復では、反復するたびに異なる要素が生成される場合があります。

基本的に:いいえ、foreachコンストラクトはそのように使用されることを意図していません。

37

これで十分ですか?空でないリストを想定しています。

  list[0,list.length-1].each{|i|
    puts "Looping:"+i # if not last loop iteration
  }
  puts "Last one:" + list[list.length-1]
22
kgiannakakis

In Rubyを使用しますeach_with_indexこの状況では

list = ['A','B','C']
last = list.length-1
list.each_with_index{|i,index|
  if index == last 
    puts "Last one: "+i 
  else 
    puts "Looping: "+i # if not last loop iteration
  end
}
13
David Burrows

クラス内でeachwithlastメソッドを定義して、最後を除くすべての要素でeachと同じことを行うことができますが、最後の要素は別のものです。

class MyColl
  def eachwithlast
    for i in 0...(size-1)
      yield(self[i], false)
    end
    yield(self[size-1], true)
  end
end

次に、このように呼び出すことができます(fooMyCollのインスタンスまたはそのサブクラスです):

foo.eachwithlast do |value, last|
  if last
    puts "Last one: "+value
  else
    puts "Looping: "+value
  end
end

編集: molfの提案に従う:

class MyColl
  def eachwithlast (defaultAction, lastAction)
    for i in 0...(size-1)
      defaultAction.call(self[i])
    end
    lastAction.call(self[size-1])
  end
end

foo.eachwithlast(
    lambda { |x| puts "looping "+x },
    lambda { |x| puts "last "+x } )
9
Svante

C#3.0以降

まず、拡張メソッドを作成します。

public static void ForEachEx<T>(this IEnumerable<T> s, Action<T, bool> act)
{
    IEnumerator<T> curr = s.GetEnumerator();

    if (curr.MoveNext())
    {
        bool last;

        while (true)
        {
            T item = curr.Current;
            last = !curr.MoveNext();

            act(item, last);

            if (last)
                break;
        }
    }
}

次に、新しいforeachを使用するのは非常に簡単です。

int[] lData = new int[] { 1, 2, 3, 5, -1};

void Run()
{
    lData.ForEachEx((el, last) =>
    {
        if (last)
            Console.Write("last one: ");
        Console.WriteLine(el);
    });
}
5

Foreachはリスト内のアイテムの数に関係なく各要素を平等に扱うという点でエレガントです。あなたの唯一の解決策はforループを使用してitemcount-1で停止し、最後のアイテムを特定の条件を処理するループまたはループ内の条件、つまりif(i == itemcount){...} else {...}

3
Lazarus

あなたはそのようなことをすることができます(C#):

string previous = null;
foreach(string item in list)
{
    if (previous != null)
        Console.WriteLine("Looping : {0}", previous);
    previous = item;
}
if (previous != null)
    Console.WriteLine("Last one : {0}", previous);
3
Thomas Levesque

Rubyにはeach_indexメソッドもあります。

list = ['A','B','C']
list.each_index{|i|
  if i < list.size - 1
    puts "Looping:"+list[i]
  else
    puts "Last one:"+list[i]
}

編集:

または、それぞれを使用します(修正されたTomatoGGおよびKirschsteinソリューション):

list = ['A', 'B', 'C', 'A']
list.each { |i|
   if (i.object_id != list.last.object_id)
      puts "Looping:#{i}"
   else
      puts "Last one:#{i}"
   end
}

Looping:A
Looping:B
Looping:C
Last one:A

または

list = ['A', 'B', 'C', 'A']
list.each {|i| 
  i.object_id != list.last.object_id ? puts "Looping:#{i}" : puts "Last one:#{i}"       
}
2
klew

Countプロパティを公開するコレクションを使用している場合-他の多くの答えによって行われた仮定なので、私もそれをします-C#とLINQを使用して、次のようなことができます。

foreach (var item in list.Select((x, i) => new { Val = x, Pos = i }))
{
    Console.Write(item.Pos == (list.Count - 1) ? "Last one: " : "Looping: ");
    Console.WriteLine(item.Val);
}

コレクション内のアイテムにインデックスで直接アクセスできるとさらに仮定する場合-現在受け入れられている答え はthisを想定-単純なforループは、foreachよりもエレガントで読みやすいです:

for (int i = 0; i < list.Count; i++)
{
    Console.Write(i == (list.Count - 1) ? "Last one: " : "Looping: ");
    Console.WriteLine(list[i]);
}

コレクションがCountプロパティを公開せず、インデックスでアクセスできない場合、少なくともC#ではそうではありませんが、実際にこれを行うエレガントな方法はありません。 Thomas Levesqueの答え のバグ修正されたバリエーションは、おそらくあなたが得るものと同じくらい近いでしょう。


Thomas's answer のバグ修正バージョンは次のとおりです。

string previous = null;
bool isFirst = true;
foreach (var item in list)
{
    if (!isFirst)
    {
        Console.WriteLine("Looping: " + previous);
    }
    previous = item;
    isFirst = false;
}
if (!isFirst)
{
    Console.WriteLine("Last one: " + previous);
}

コレクションがCountプロパティを公開せず、アイテムがインデックスによって直接アクセスできない場合、C#でどのように行うかを示します。 (foreachがなく、コードは特に簡潔ではないことに注意してください。ただし、ほとんどすべての列挙可能なコレクションに対して適切なパフォーマンスが得られます。)

// i'm assuming the non-generic IEnumerable in this code
// wrap the enumerator in a "using" block if dealing with IEnumerable<T>
var e = list.GetEnumerator();
if (e.MoveNext())
{
    var item = e.Current;

    while (e.MoveNext())
    {
        Console.WriteLine("Looping: " + item);
        item = e.Current;
    }
    Console.WriteLine("Last one: " + item);
}
2
LukeH

あなたがやろうとしていることは、foreachループにとっては少し高度すぎるようです。ただし、Iteratorsを明示的に使用できます。たとえば、Javaでは次のように書きます。

Collection<String> ss = Arrays.asList("A","B","C");
Iterator<String> it = ss.iterator();
while (it.hasNext()) {
    String s = it.next();
    if(it.hasNext())
        System.out.println("Looping: " + s);
    else 
        System.out.println("Last one: " + s);
}
2
Bruno De Fraine

ループを開始する前にリスト内の最後のアイテムを見つけて、すべてのアイテムをこのアイテムと比較できることを前提とするいくつかの提案があります。これを効率的に行うことができる場合、基礎となるデータ構造はおそらく単純な配列です。もしそうなら、なぜforeachに悩まされるのでしょうか?書くだけ:

for (int x=0;x<list.size()-1;++x)
{
  System.out.println("Looping: "+list.get(x));
}
System.out.println("Last one: "+list.get(list.size()-1));

基になる構造がリンクリストであるように、任意の位置からアイテムを効率的に取得できない場合、最後のアイテムを取得するには、おそらくリスト全体を順番に検索する必要があります。リストのサイズによっては、パフォーマンスの問題になる場合があります。これが頻繁に実行される関数である場合、この方法で実行できるように、配列またはArrayListまたは同等の構造の使用を検討することをお勧めします。

「ハンマーを使用してネジを入れる最良の方法は何ですか?」

1
Jay

各ケースで「一般的な」実行を行う前に、配列から最初/最後の要素を取り出すだけで実行可能なソリューションになりますか?

このような:

list = ['A','B','C','D']
first = list.shift
last = list.pop

puts "First one: #{first}"
list.each{|i|
  puts "Looping: "+i
}
puts "Last one: #{last}"
1
Carlos Lima

For-eachループがJava以外の他の言語でどのように機能するかはわかりません。 In Java for-eachは、for-eachが使用するIterableインターフェイスを使用してIteratorを取得し、ループします。Iteratorには、hasNextメソッドがあり、イテレータが表示された場合に使用できますforループが必要なものを取得し、ループ内でhasNextメソッドを取得できるように、既に取得したイテレーターをIterableオブジェクトで囲むことにより、実際にトリックを行うことができます。

List<X> list = ...

final Iterator<X> it = list.iterator();
Iterable<X> itw = new Iterable<X>(){
  public Iterator<X> iterator () {
    return it;
  }
}

for (X x: itw) {
  doSomething(x);
  if (!it.hasNext()) {
    doSomethingElse(x);
  }
}

この反復可能および反復子のすべてをラップするクラスを作成すると、コードは次のようになります。

IterableIterator<X> itt = new IterableIterator<X>(list);
for (X x: itit) {
  doSomething(x);
  if (!itit.hasNext()) {
    doSomethingElse(x);
  }
}
1
aalku

kgiannakakisのソリューションを好むと思いますが、常にこのようなことをすることができます。

list = ['A','B','C']
list.each { |i|
   if (i != list.last)
      puts "Looping:#{i}"
   else
      puts "Last one:#{i}"
   end
}
1
Kirschstein

少なくともC#では、通常のforループなしでは不可能です。

コレクションの列挙子は、次の要素が存在するかどうかを判断し(MoveNextメソッド)、ループはこれを認識しません。

1
DanielR

この問題は、F#などの関数型プログラミング言語のパターンマッチングを使用して、エレガントな方法で解決できます。

let rec printList (ls:string list) = 
    match ls with
        | [last] -> "Last " + last
        | head::rest -> "Looping " + head + "\n" + printList (rest)
        | [] -> ""
1
eulerfx

Kgiannakakisの答えと同様:

list.first(list.size - 1).each { |i| puts "Looping: " + i }
puts "Last one: " + list.last
0
Firas Assaad

これはどう?少しだけRubyを学んだ。へへへ

list.collect {|x|(x!=list.last ? "Looping:"+x:"Lastone:"+x) }.each{|i|puts i}      
0
anonymous

リストから最後のものを削除し、その値を保持します。

Spec spec = specs.Find(s=>s.Value == 'C');
  if (spec != null)
  {
    specs.Remove(spec);
  }
foreach(Spec spec in specs)
{

}
0
Andy A.

foreachループを書き換えずに機能する別のパターン:

var delayed = null;
foreach (var X in collection)
{
    if (delayed != null)
    {
        puts("Looping");
        // Use delayed
    }
    delayed = X;
}

puts("Last one");
// Use delayed

このようにして、コンパイラーはループをまっすぐに保ち、イテレーター(カウントのないものを含む)は期待どおりに機能し、最後のイテレーターは他のものから分離されます。

また、繰り返しの間に何かを発生させたいときにもこのパターンを使用しますが、最後の繰り返しの後には使用しません。その場合、Xは通常使用され、delayedは別のものを参照し、delayedの使用はループの開始時のみであり、ループの終了後に何もする必要はありません。

0
shipr

可能な限りjoinを使用します。

ほとんどの場合、区切り文字はすべての要素間で同じであり、言語の対応する関数と一緒に結合します。

Rubyの例、

puts array.join(", ")

これは、すべてのケースの99%をカバーする必要があり、そうでない場合は、アレイをヘッドとテールに分割します。

Rubyの例、

*head, tail = array
head.each { |each| put "looping: " << each }
puts "last element: " << tail 
0
akuhn