识别每个使用的最后一个循环

在对对象执行“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循环使用'for i in 1..list.length' ,但每个解决scheme感觉更优雅。 在循环中编写特殊情况的最优雅的方法是什么? 这可以用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); } 

我修改了下面的代码,通过引用来比较不是值(只能使用引用types而不是值types)。 下面的代码应该支持包含相同string(但不是相同的string对象)的多个对象,因为MattChurcy的示例没有指定string必须是不同的,我使用LINQ Last方法而不是计算索引。

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

上述代码的局限性。 (1)它只能用于string或引用types而不是值types。 (2)同一个对象只能在列表中出现一次。 你可以有不同的对象包含相同的内容。 由于C#不会为具有相同内容的string创build唯一的对象,所以不能重复使用文字string。

而我并不愚蠢。 我知道一个基于索引的循环是使用的。 当我第一次发布最初的答案时,我已经这么说了。 我在这个问题的背景下提供了最好的答案。 我太累了,不能继续解释,所以你们都可以投票删除我的答案。 如果这个消失,我会非常高兴。 谢谢

foreach结构(在Java中肯定也可能在其他语言中)是用来表示最常用的types,如果迭代包括对没有有意义迭代次序的集合的迭代。 例如,基于散列的集合没有sorting,因此没有 “最后一个元素”。 每次迭代时,最后一次迭代可能会产生一个不同的元素。

基本上:不,foreach结构并不意味着被这样使用。

我在这里看到很多复杂的,难以理解的代码…为什么不简单:

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

这只是一个额外的声明!

另一个常见的情况是,你想用最后一个项目做一些额外的事情,比如在项目之间加一个分隔符:

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

这是否够优雅? 它假定一个非空的列表。

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

在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 } 

你可以在你的类中定义一个each最后一个方法,除了最后一个元素, 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的build议:

 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 。 改用基于索引的interation。 否则,您必须在项目周围添加一个不同的结构,您可以使用它来区分foreach调用中的常规和最后一个结构(请参阅有关从Google为后台减less的地图的好论文: http : //labs.google.com /papers/mapreduce.html,map == foreach,reduced == eg sum or filter)。

Map没有关于结构的知识(尤其是一个项目的哪个位置),它只能转换一个项目(没有一个项目的知识可以用来转换另一个项目),但是reduce可以使用一个存储器来计数位置并处理最后一个项目。

一个常见的技巧是颠倒列表并处理第一个(现在已知索引= 0),然后再次应用相反。 (这是优雅的,但不是很快;))

Foreach是优雅的,它不必关心列表中的项目数量,并平等地对待每个元素,我认为你唯一的解决scheme将使用for循环,或者在itemcount-1处停止,然后将最后一个项目呈现在循环或条件在处理该特定条件的循环,即如果(i == itemcount){…}其他{…}

你可以做这样的事情(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解决scheme):

 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}" } 

对于foreach循环来说,你所要做的似乎有点太高级了。 但是,您可以显式使用Iterator 。 例如,在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); } 

如果你使用的是一个暴露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); } 

如果我们另外假定集合中的项目可以通过索引直接访问 – 目前接受的答案假定这个 – 那么一个普通的for循环将比foreach更优雅/可读:

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

如果集合不公开一个Count属性,并且不能被索引访问,那么没有任何优雅的方法可以做到这一点,至less在C#中是这样。 托马斯·列维斯克 ( Thomas Levesque)答案的一个错误修正的变体可能就像你会得到的一样。


这是Thomas的答案的错误修正版本:

 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 ,代码也不是特别简洁,但是与几乎任何可枚举的集合相比,它都会有不错的performance。)

 // 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); } 

至less在C#中,这是不可能的,没有一个常规的循环。

集合的枚举器决定是否存在下一个元素(MoveNext方法),循环不知道这一点。

我想我更喜欢kgiannakakis的解决scheme,但是你总是可以这样做;

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

我注意到一些build议,假设您可以在开始循环之前find列表中的最后一个项目,然后将每个项目与此项目进行比较。 如果你可以有效地做到这一点,那么底层的数据结构可能是一个简单的数组。 如果是这样的话,为什么还要麻烦这个习惯呢? 写吧:

 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)); 

如果你不能有效地从任意位置检索一个项目,就像下面的结构是一个链表一样,那么获取最后一个项目可能涉及整个列表的顺序search。 根据列表的大小,这可能是一个性能问题。 如果这是一个经常执行的函数,那么您可能需要考虑使用数组或ArrayList或类似的结构,以便您可以这样做。

听起来像你在问:“用锤子拧螺丝的最好方法是什么?”,当然,要问的更好的问题是“用螺丝钉放正确的工具是什么?

对于你的情况来说,在每一次运行“通用”之前,只要将第一个/最后一个元素排除在arrays之外,是否可行?

喜欢这个:

 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}" 

这个问题可以通过使用F#等函数式编程语言中的模式匹配的优雅方式来解决:

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

我不知道如何为每个循环工作在其他语言,但Java。 在java for中,每个使用for-each使用的Iterable接口来获得一个Iterator并循环它。 迭代器有一个方法hasNext,你可以使用,如果你可以看到循环内的迭代器。 你可以通过将一个已经获得的Iterator封装在一个Iterable对象中来实现这个诀窍,这样for循环得到了它所需要的东西,并且你可以在循环中获得一个hasNext方法。

 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); } } 

你可以创build一个包装所有这些迭代器和迭代器的类,所以代码如下所示:

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

类似于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是指其他的,延迟的使用只在循环开始,循环结束后不需要做任何事情。

尽可能使用join

大多数情况下,所有元素之间的分隔符是相同的,只要将它们与您的语言中的相应函数一起join即可。

Ruby例子,

 puts array.join(", ") 

这应该涵盖所有情况的99%,如果不将数组分为头部和尾部。

Ruby例子,

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

<hello>我们在这里想什么?

 public static void main(String[] args) { // TODO Auto-generated method stub String str = readIndex(); String comp[] = str.split("}"); StringBuffer sb = new StringBuffer(); for (String s : comp) { sb.append(s); sb.append("}\n"); } System.out.println (sb.toString()); } 

作为build模符号,OMT符号的影响占主导地位(例如,对类和对象使用矩形)。 虽然Booch的“云”符号被删除了,但是Booch能够指定更低层次的devise细节。 来自Objectory的用例符号和来自Booch的组件符号与其余的符号集成在一起,但UML1.1中的语义集成相对较弱,并且直到UML2.0的主要修订之前并没有被真正固定。