在为每个使用时识别最后一个循环

时间:2009-07-01 09:09:26

标签: c# java ruby loops foreach

我想在对象上执行'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循环,但每个解决方案的感觉都更优雅。在循环期间编写特殊情况的最优雅方法是什么?可以用foreach完成吗?

25 个答案:

答案 0 :(得分:37)

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

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(",");
}

答案 1 :(得分:37)

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

基本上:不,foreach结构不应该以这种方式使用。

答案 2 :(得分:23)

如何首先获得参考 最后一项,然后在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);
}

我修改了以下代码,通过引用而不是值进行比较(只能使用引用类型而不是值类型)。以下代码应该支持包含相同字符串(但不是相同的字符串对象)的多个对象,因为MattChurcy的示例没有指定字符串必须是不同的,我使用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)它只适用于字符串或引用类型而不是值类型。 (2)同一对象只能在列表中出现一次。您可以拥有包含相同内容的不同对象。文字字符串不能重复使用,因为C#不会为具有相同内容的字符串创建唯一对象。

我并不傻。我知道基于索引的循环是要使用的循环。当我第一次发布初步答案时,我已经说过了。我在问题的上下文中提供了最佳答案。我太累了,不能继续解释这个,所以你们都投票删除我的答案。如果这个消失,我会很高兴的。谢谢

答案 3 :(得分:22)

这个够优雅吗?它假定一个非空列表。

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

答案 4 :(得分:13)

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

答案 5 :(得分:9)

您可以在班级中定义一个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 } )

答案 6 :(得分:5)

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

答案 7 :(得分:4)

只有在处理每个一个时,才应使用 foreach 。而是使用基于索引的交互。否则,你必须在项目周围添加一个不同的结构,你可以用它来区分foreach调用中的正常和最后一个(看看谷歌为背景减少的地图的好文章:http://labs.google.com/papers/mapreduce.html,map == foreach,reduced ==例如sum或filter)。

Map不知道结构(尤其是项目的位置),它只能逐个项目转换(一个项目的知识不能用于转换另一个项目!),但是reduce可以使用内存来例如,计算位置并处理最后一项。

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

答案 8 :(得分:3)

Foreach很优雅,因为它不关心列表中的项目数量并平等对待每个元素,我认为你唯一的解决方案是使用for循环,在itemcount-1停止然后你呈现你的最后一个循环外的项或处理该特定条件的循环中的条件,即if(i == itemcount){...} else {...}

答案 9 :(得分:3)

你可以这样做(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);

答案 10 :(得分:2)

如果您正在使用一个公开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);
}

如果我们另外假设可以通过索引直接访问集合中的项目 - the currently accepted answer假设这个 - 那么普通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's answer的错误修复版本可能会尽可能接近。


以下是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);
}

答案 11 :(得分:2)

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

答案 12 :(得分:2)

你想要做的事情似乎对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);
}

答案 13 :(得分:1)

我认为我更喜欢 kgiannakakis 的解决方案,但你可以随时做这样的事情;

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

答案 14 :(得分:1)

我注意到一些建议假设您可以在开始循环之前找到列表中的最后一项,然后将每个项目与此项目进行比较。如果您可以有效地执行此操作,那么底层数据结构可能是一个简单的数组。如果是这样的话,为什么还要为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或类似的结构,以便您可以这样做。

听起来像是在问你,“用螺丝钉拧螺丝的最佳方法是什么?”,当然更好的问题是,“用什么方法来装螺丝是正确的?“

答案 15 :(得分:1)

在每次运行“一般”之前,从阵列中取出第一个/最后一个元素是不是一个可行的解决方案?

像这样:

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

答案 16 :(得分:1)

这个问题可以通过函数式编程语言(如F#:

)中的模式匹配以优雅的方式解决
let rec printList (ls:string list) = 
    match ls with
        | [last] -> "Last " + last
        | head::rest -> "Looping " + head + "\n" + printList (rest)
        | [] -> ""

答案 17 :(得分:1)

我不知道for-each循环如何在其他语言中工作但是java。 在java for-each中,使用for-each使用的Iterable接口来获取Iterator并使用它进行循环。 Iterator有一个方法hasNext,如果你可以在循环中看到迭代器,你可以使用它。 实际上你可以通过将已经获得的迭代器封装在一个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);
  }
}

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

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

答案 18 :(得分:1)

至少在C#中,没有常规for循环是不可能的。

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

答案 19 :(得分:0)

尽可能使用impl Deref for FooMap { type Target = HashMap<String, String>; fn deref(&self) -> &Self::Target { &self.data } }

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

Ruby示例,

join

这应该覆盖所有情况的99%,如果没有将数组拆分为头尾。

Ruby示例,

puts array.join(", ")

答案 20 :(得分:0)

另一种有效的模式,无需重写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指的是其他内容,而延迟的使用仅在循环开始时,在循环结束后不需要进行任何操作。

答案 21 :(得分:0)

从列表中删除最后一个并保留其值。

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

}

答案 22 :(得分:0)

这个怎么样?刚刚学到了一点Ruby。呵呵呵

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

答案 23 :(得分:0)

类似于kgiannakakis的回答:

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

答案 24 :(得分:-5)

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

作为建模符号,OMT符号的影响占主导地位(例如,对类和对象使用矩形)。虽然Booch“云”符号被删除,但Booch能够指定较低级别的设计细节。来自Objectory和component notation from Booch的用例表示法与其他表示法集成在一起,但语义集成在UML 1.1中相对较弱,并且在UML 2.0主要修订版之前并未真正修复。