it-roy-ru.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 с помощью 'for i in 1..list.length', но для каждого решения он выглядит более изящным. Каков наиболее изящный способ кодирования особого случая во время цикла? Можно ли это сделать с помощью 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);
}

Я пересмотрел следующий код для сравнения по ссылке, а не по значению (можно использовать только ссылочные типы, а не типы значений). следующий код должен поддерживать несколько объектов, содержащих одну и ту же строку (но не один и тот же строковый объект), поскольку в примере 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 # не создает уникальный объект для строк с одинаковым содержимым.

И я не глупый. Я знаю, что используется цикл на основе индекса. Я уже говорил об этом, когда впервые опубликовал первоначальный ответ. Я дал лучший ответ, который я могу в контексте вопроса. Я слишком устал, чтобы продолжать объяснять это, так что вы можете просто проголосовать, чтобы удалить мой ответ. Я буду так счастлив, если этот уйдет. Спасибо 

23
anonymous

Конструкция foreach (определенно в Java, вероятно, также и в других языках) предназначена для представления наиболее общего вида итерации if, которая включает в себя итерации по коллекциям, которые не имеют значимого порядка итераций. Например, основанный на хэше набор не имеет порядка, и, следовательно, отсутствует «последний элемент». Последняя итерация может давать новый элемент при каждой итерации.

По сути: нет, конструкция foreach не предназначена для такого использования.

37
Michael Borgwardt

Я вижу много сложного, трудно читаемого кода ... почему бы не сделать его простым:

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

Это достаточно элегантно? Предполагается непустой список.

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

В 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

Затем вы можете назвать это так (foo является экземпляром MyColl или его подклассом):

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

Правка: Следуя предложению Мольфа:

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
bohdan_trotsenko

Вы должны использовать foreach, только если вы обрабатываете каждый один и тот же. Вместо этого используйте индексное взаимодействие. В противном случае вы должны добавить другую структуру вокруг элементов, которую вы можете использовать, чтобы отличать нормаль от последней в вызове foreach (посмотрите на хорошие документы о карте, уменьшенной от google для фона: http://labs.google .com /apers/mapreduce.html , карта == foreach, уменьшенная == например, сумма или фильтр).

Карта не знает о структуре (особенно, в какой позиции находится элемент), она преобразует только один элемент за элемент (никакие знания из одного элемента не могут быть использованы для преобразования другого!), Но Редуктор может использовать память, например, для подсчета положение и обрабатывать последний элемент.

Обычный трюк состоит в том, чтобы перевернуть список и обработать первый (который теперь имеет известный индекс = 0), а затем снова применить реверс. (Что элегантно, но не быстро;))

4
H2000

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

Если мы дополнительно предполагаем, что элементы в коллекции могут быть доступны напрямую по индексу - текущий принятый ответ предполагает это - тогда простой цикл 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 #. Исправленная ошибка вариация ответ Томаса Левеска , вероятно, настолько близка, насколько вы получите.


Вот исправленная ошибка версия ответ Томаса :

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

И вот как я это сделал бы в C #, если коллекция не предоставляет свойство Count, а элементы не доступны напрямую по индексу. (Обратите внимание, что нет 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 . В Java for-each использует интерфейс Iterable, который используется for-each, чтобы получить итератор и выполнить цикл с ним. Итератор имеет метод 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);
  }
}
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

Аналогично ответу Кгианнакакиса:

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

Как насчет этого? только что выучил немного Руби. хехехе

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, когда это возможно.

Чаще всего разделитель одинаков для всех элементов, просто соедините их вместе с соответствующей функцией на вашем языке.

Рубиновый пример,

puts array.join(", ")

Это должно охватывать 99% всех случаев, и если не разбить массив на голову и хвост.

Рубиновый пример,

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