オブジェクトに対して「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で実行できますか?
参照を最後の項目に取得してから、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でできる限り最高の回答を提供しました。私はこれを説明し続けるのにうんざりしているので、あなたは私の答えを削除するために投票するだけです。これがなくなったらとても嬉しいです。ありがとう
ここには、複雑で読みにくいコードがたくさんあります...単純にしないでください:
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(",");
}
Foreachコンストラクト(Java間違いなく、おそらく他の言語でも))は、反復の場合に最も一般的な種類を表すことを目的としています。ハッシュベースのセットには順序がないため、is no "last element"。最後の反復では、反復するたびに異なる要素が生成される場合があります。
基本的に:いいえ、foreachコンストラクトはそのように使用されることを意図していません。
これで十分ですか?空でないリストを想定しています。
list[0,list.length-1].each{|i|
puts "Looping:"+i # if not last loop iteration
}
puts "Last one:" + list[list.length-1]
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
}
クラス内でeachwithlast
メソッドを定義して、最後を除くすべての要素でeach
と同じことを行うことができますが、最後の要素は別のものです。
class MyColl
def eachwithlast
for i in 0...(size-1)
yield(self[i], false)
end
yield(self[size-1], true)
end
end
次に、このように呼び出すことができます(foo
はMyColl
のインスタンスまたはそのサブクラスです):
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 } )
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);
});
}
Foreachはリスト内のアイテムの数に関係なく各要素を平等に扱うという点でエレガントです。あなたの唯一の解決策はforループを使用してitemcount-1で停止し、最後のアイテムを特定の条件を処理するループまたはループ内の条件、つまりif(i == itemcount){...} else {...}
あなたはそのようなことをすることができます(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);
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}"
}
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);
}
あなたがやろうとしていることは、foreachループにとっては少し高度すぎるようです。ただし、Iterator
sを明示的に使用できます。たとえば、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);
}
ループを開始する前にリスト内の最後のアイテムを見つけて、すべてのアイテムをこのアイテムと比較できることを前提とするいくつかの提案があります。これを効率的に行うことができる場合、基礎となるデータ構造はおそらく単純な配列です。もしそうなら、なぜ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または同等の構造の使用を検討することをお勧めします。
「ハンマーを使用してネジを入れる最良の方法は何ですか?」
各ケースで「一般的な」実行を行う前に、配列から最初/最後の要素を取り出すだけで実行可能なソリューションになりますか?
このような:
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}"
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);
}
}
kgiannakakisのソリューションを好むと思いますが、常にこのようなことをすることができます。
list = ['A','B','C']
list.each { |i|
if (i != list.last)
puts "Looping:#{i}"
else
puts "Last one:#{i}"
end
}
少なくともC#では、通常のforループなしでは不可能です。
コレクションの列挙子は、次の要素が存在するかどうかを判断し(MoveNextメソッド)、ループはこれを認識しません。
この問題は、F#などの関数型プログラミング言語のパターンマッチングを使用して、エレガントな方法で解決できます。
let rec printList (ls:string list) =
match ls with
| [last] -> "Last " + last
| head::rest -> "Looping " + head + "\n" + printList (rest)
| [] -> ""
Kgiannakakisの答えと同様:
list.first(list.size - 1).each { |i| puts "Looping: " + i }
puts "Last one: " + list.last
これはどう?少しだけRubyを学んだ。へへへ
list.collect {|x|(x!=list.last ? "Looping:"+x:"Lastone:"+x) }.each{|i|puts i}
リストから最後のものを削除し、その値を保持します。
Spec spec = specs.Find(s=>s.Value == 'C');
if (spec != null)
{
specs.Remove(spec);
}
foreach(Spec spec in specs)
{
}
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の使用はループの開始時のみであり、ループの終了後に何もする必要はありません。
可能な限りjoin
を使用します。
ほとんどの場合、区切り文字はすべての要素間で同じであり、言語の対応する関数と一緒に結合します。
Rubyの例、
puts array.join(", ")
これは、すべてのケースの99%をカバーする必要があり、そうでない場合は、アレイをヘッドとテールに分割します。
Rubyの例、
*head, tail = array
head.each { |each| put "looping: " << each }
puts "last element: " << tail