Идентификация последнего цикла при использовании для каждого

Я хочу сделать что-то другое с последней итерацией цикла при выполнении 'foreach' объекта. Я использую Ruby, но то же самое относится к С#, 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?

Ответ 1

Как получить сначала ссылку в последний элемент, а затем использовать ее для сравнения внутри цикла foreach? Я не говорю, что вы должны это сделать, поскольку я сам буду использовать цикл, основанный на индексе, как упомянуто KlauseMeier. И жаль, что я не знаю Ruby, поэтому следующий образец находится на С#! Надеюсь, что вы не возражаете: -)

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

Является ли это достаточно элегантным? Он принимает непустой список.

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

Ответ 5

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

Ответ 6

Вы можете определить метод 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 } )

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

Foreach элегантен тем, что он не заботится о количестве элементов в списке и одинаково относится к каждому элементу, я думаю, что ваше единственное решение будет использовать цикл for, который либо останавливается на itemcount-1, а затем вы представляете свой последний элемент за пределами цикла или условный элемент в цикле, который обрабатывает это конкретное условие, то есть if (i == itemcount) {...} else {...}

Ответ 10

Вы можете сделать что-то подобное (С#):

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

Ответ 11

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

EDIT:

Или используя каждый (исправленное решение 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

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


Здесь исправлена ​​ошибка версии Томаса:

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, и элементы не доступны напрямую по индексу. (Обратите внимание, что нет 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);
}

Ответ 13

То, что вы пытаетесь сделать, кажется просто слишком продвинутым для цикла 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);
}

Ответ 14

По крайней мере, в С#, который невозможен без регулярного цикла.

Перечислитель коллекции решает, существуют ли следующие элементы (метод MoveNext), цикл не знает об этом.

Ответ 15

Я думаю, что предпочитаю kgiannakakis решение, но вы всегда можете сделать что-то подобное:

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

Ответ 16

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

Звучит так, как будто вы спрашиваете: "Какой лучший способ вставить винт в использование молотка?", когда, конечно, лучше задать вопрос: "Какой правильный инструмент использовать, чтобы вставить винт?"

Ответ 17

Было ли жизнеспособным решением для вашего дела просто взять первые/последние элементы из вашего массива, прежде чем делать "общий" каждый прогон?

Вот так:

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

Ответ 18

Эта проблема может быть решена элегантным способом с использованием сопоставления с образцом в функциональном языке программирования, таком как F #:

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

Ответ 19

Я не знаю, как для каждого цикла работает на других языках, кроме java. В java for-each используется интерфейс Iterable, который используется for-each для получения Iterator и цикла с ним. У 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);
  }
}

Вы можете создать класс, который обертывает все эти итерационные и итераторные файлы, чтобы код выглядел следующим образом:

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

Ответ 20

Как и на kgiannakakis ответ:

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

Ответ 21

Как насчет этого? просто узнал немного Руби. хехехе

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

Ответ 22

Удалите последний из списка и сохраните его avlue.

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

}

Ответ 23

Другой шаблон, который работает, без необходимости переписывать цикл 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 относится к чему-то другому, а использование задержанного - только в начале цикла и ничего не нужно делать после завершения цикла.

Ответ 24

Используйте join, когда это возможно.

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

Пример Ruby,

puts array.join(", ")

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

Пример Ruby,

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

Ответ 25

<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 и компонентная нотация от Booch были интегрированы с остальной частью нотации, но семантическая интеграция была относительно слабой в UML 1.1, и на самом деле не был исправлен до основной версии UML 2.0.