Есть ли более элегантный способ действия с первым и последним элементами перечисления foreach, чем count ++? - PullRequest
2 голосов
/ 16 июня 2010

Есть ли более элегантный способ воздействовать на первый и последний элементы при итерации цикла foreach, чем приращивать отдельный счетчик и проверять его каждый раз?

Например, выводится следующий код:

>>> [line1], [line2], [line3], [line4] <<<

, который требует знания того, когда вы действуете с первым и последним элементом.Есть ли более элегантный способ сделать это сейчас в C # 3 / C # 4?Кажется, я мог бы использовать .Last () или .First () или что-то в этом роде.

using System;
using System.Collections.Generic;
using System.Text;

namespace TestForNext29343
{
    class Program
    {
        static void Main(string[] args)
        {
            StringBuilder sb = new StringBuilder();
            List<string> lines = new List<string>
            {
                "line1",
                "line2",
                "line3",
                "line4"
            };
            int index = 0;
            foreach (var line in lines)
            {
                if (index == 0)
                    sb.Append(">>> ");

                sb.Append("[" + line + "]");

                if (index < lines.Count - 1)
                    sb.Append(", ");
                else
                    sb.Append(" <<<");

                index++;
            }

            Console.WriteLine(sb.ToString());
            Console.ReadLine();
        }
    }
}

Ответы [ 11 ]

7 голосов
/ 16 июня 2010

Ваш текущий пример может быть выполнен без итерации.

Console.WriteLine(">>> " + String.Join(lines, ", ") + " <<<);

Если вы просто выполняете итерации, мне проще заменить его обычным циклом for и проверить границы.

for(int i=0; i<list.count; i++)
{
  if(i == 0)
   //First one
  else if(i == list.count -1)
   //Last one
}

Это будет намного быстрее, чем при использовании методов расширения .First () и .Last (). Кроме того, если в вашем списке есть два элемента с одинаковыми (строковыми) значениями по сравнению с Last или First, они не будут работать.

4 голосов
/ 16 июня 2010

Для общего вопроса о том, как по-разному обрабатывать первый и последний случаи, когда у вас есть только IEnumerable<T>, один из способов сделать это - напрямую использовать перечислитель:

    public static void MyForEach<T>(this IEnumerable<T> items, Action<T> onFirst, Action<T> onMiddle, Action<T> onLast)
    {
        using (var enumerator = items.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                onFirst(enumerator.Current);
            }
            else
            {
                return;
            }

            //If there is only a single item in the list, we treat it as the first (ignoring middle and last)
            if (!enumerator.MoveNext())
                return;

            do
            {
                var current = enumerator.Current;
                if (enumerator.MoveNext())
                {
                    onMiddle(current);
                }
                else
                {
                    onLast(current);
                    return;
                }
            } while (true);
        }
    }
2 голосов
/ 16 июня 2010

Не отвечаю на ваш вопрос, но для вашей цели я бы использовал

return String.Format(">>> {0} <<<",String.Join(lines.ToArray(),","));
1 голос
/ 16 июня 2010

Попробуйте следующий код.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

Вот код для ForEachHelper класса.

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}
0 голосов
/ 16 июня 2010

List использует массив для хранения элементов (называемых _items), поэтому lines [i] по сути так же быстр, как и доступ к члену массива.Enumerable.First () и Enumerable.Last () получают доступ к первым и последним членам списка с помощью индексатора списков, поэтому lines.First () - это по существу, строки [0] и линии.], плюс некоторая проверка диапазона.

Это означает, что стоимость строки == lines.First () составляет ссылку на элемент массива плюс сравнение ссылок.Если вы не выполняете много итераций, это не должно вас беспокоить.

Если вам нужно что-то более быстрое, вы можете использовать LinkedList.В этом случае First () и Last () возвращают первый и последний элементы напрямую, но это кажется излишним.

0 голосов
/ 16 июня 2010

Мой C # немного ржавый, но должен выглядеть примерно так:

StringBuilder sb;
List<string> lines = ....;
sb.Append(">>> [").Append(lines[0]);
for (int idx = 1; idx < lines.Count; idx++)
    sb.Append("], [").Append(lines[idx]);
sb.Append("] <<<");

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

0 голосов
/ 16 июня 2010

Почему бы просто не добавить в конец цикла foreach, если StringBuilder не пуст?

...
for (int i=0; i<lines.Count; i++)
{
    sb.Append("[" + lines[i] + "]");

    if (i < lines.Count - 1)
        sb.Append(", ");

}

if (sb.Length != 0)
{
    sb.Insert(0, ">>> ");
    sb.Append(" >>>");
}
0 голосов
/ 16 июня 2010

Вы можете сделать следующее:

Console.WriteLine(">>>> [" + String.Join("], [", lines.ToArray()) + "] <<<<");

Я знаю, что это не отвечает на ваш вопрос, но решает вашу проблему ...

0 голосов
/ 16 июня 2010

Может быть более "элегантный" способ кодирования с использованием First и Last, но неэффективность делает это не стоящим.

Я кодировал свой собственный оператор Join для IEnumerable<string> с (из Nito.KitchenSink ).Это полностью повторно (в .NET 3.5 или 4.0):

/// <summary>
/// Concatenates a separator between each element of a string enumeration.
/// </summary>
/// <param name="source">The string enumeration.</param>
/// <param name="separator">The separator string. This may not be null.</param>
/// <returns>The concatenated string.</returns>
public static string Join(this IEnumerable<string> source, string separator)
{
    StringBuilder ret = new StringBuilder();
    bool first = true;
    foreach (string str in source)
    {
        if (first)
        {
            first = false;
        }
        else
        {
            ret.Append(separator);
        }

        ret.Append(str);
    }

    return ret.ToString();
}

/// <summary>
/// Concatenates a sequence of strings.
/// </summary>
/// <param name="source">The sequence of strings.</param>
/// <returns>The concatenated string.</returns>
public static string Join(this IEnumerable<string> source)
{
    return source.Join(string.Empty);
}
0 голосов
/ 16 июня 2010

Я бы порекомендовал поместить ваши >>> и <<< вне цикла.

StringBuilder sb = new StringBuilder();
sb.Append(">>> ");

bool first = true;
foreach(var line in lines)
{
    if (!first) sb.Append(", ");
    sb.Append("[" + line + "]");
    first = false;
}
sb.Append(" <<<");

Вы также можете использовать String.Join вместо цикла foreach.

String.Join(", ", lines);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...