Как перебрать «между» элементами в массиве / коллекции / списке? - PullRequest
4 голосов
/ 09 февраля 2010

Эта проблема беспокоила меня годами, и я всегда чувствую, что у меня есть взлом, когда есть намного лучшее решение. Проблема возникает, когда вы хотите что-то сделать со всеми элементами в списке, а затем добавить что-то между этими элементами. Короче хочу:

  • Сделайте что-нибудь для каждого элемента в списке.
  • Сделайте что-нибудь еще для всего, кроме последнего элемента в списке (по сути, сделайте что-то "между" элементами в списке).

Например, допустим, у меня есть класс с именем Equation:

public class Equation
{
    public string LeftSide { get; set; }
    public string Operator { get; set; }
    public string RightSide { get; set; }
}

Я хочу перебрать список Equation с и вернуть строку, которая форматирует эти элементы вместе; что-то вроде следующего:

public string FormatEquationList(List<Equation> listEquations)
{
    string output = string.Empty;
    foreach (Equation e in listEquations)
    {
        //format the Equation
        string equation = "(" + e.LeftSide + e.Operator + e.RightSide + ")";

        //format the "inbetween" part
        string inbetween = " and ";

        //concatenate the Equation and "inbetween" part to the output
        output += equation + inbetween;
    }
    return ouput;
}

Проблема с приведенным выше кодом заключается в том, что он будет включать and в конце возвращаемой строки. Я знаю, что мог бы взломать некоторый код вместе, заменить foreach на цикл for и добавить элемент inbetween, только если это не последний элемент; но это похоже на взлом.

Существует ли стандартная методология для решения этой проблемы?

Ответы [ 7 ]

8 голосов
/ 09 февраля 2010

У вас есть несколько разных стратегий для решения этой проблемы:

  1. Обработка первого (или последнего) элемента вне цикла.
  2. Выполните работу, а затем «отмените» посторонний шаг.
  3. Определите, что вы обрабатываете первый или последний элемент внутри цикла.
  4. Используйте абстракцию более высокого уровня, которая позволяет избежать ситуации.

Любой из этих параметров может быть легитимным способом реализации алгоритма в стиле «между элементами». Какой из них вы выберете, зависит от таких вещей, как:

  • какой стиль тебе нравится
  • как дорого стоит "уничтожающая работа"
  • сколько стоит каждый шаг "соединения"
  • есть ли побочные эффекты

Среди прочего. Для конкретного случая строки я лично предпочитаю использовать string.Join(), так как считаю, что это наиболее четко иллюстрирует цель. Кроме того, в случае строк, если вы не используете string.Join(), вы должны попытаться использовать StringBuilder, чтобы избежать создания слишком большого количества временных строк (следствие того, что строки являются неизменяемыми в .Net).

Используя конкатенацию строк в качестве примера, различные параметры делятся на примеры следующим образом. (Для простоты предположим, что уравнение имеет ToString() как: "(" + LeftSide + Operator + RightSide + ")"

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    StringBuilder sb = new StringBuilder();

    if( listEquations.Count > 0 )
        sb.Append( listEquations[0].ToString() );
    for( int i = 1; i < listEquations.Count; i++ )
        sb.Append( " and " + listEquations[i].ToString() );
    return sb.ToString();
}

Второй вариант выглядит так:

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    StringBuilder sb = new StringBuilder();
    const string separator = " and ";
    foreach( var eq in listEquations )
        sb.Append( eq.ToString() + separator );
    if( listEquations.Count > 1 )
        sb.Remove( sb.Length, separator.Length );
}

Третий будет выглядеть примерно так:

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    StringBuilder sb = new StringBuilder();
    const string separator = " and ";
    foreach( var eq in listEquations )
    {
        sb.Append( eq.ToString() );
        if( index == list.Equations.Count-1 )
            break;
        sb.Append( separator );
    }
}

Последний вариант может принимать несколько форм в .NET, используя либо String.Join, либо Linq:

public string FormatEquation( IEnumerable<Equation> listEquations )
{
    return string.Join( " and ", listEquations.Select( eq => eq.ToString() ).ToArray() );
}

или

public string FormatEquation( IEnumerable<Equation> listEquations ) 
{
    return listEquations.Aggregate((a, b) => a.ToString() + " and " + b.ToString() );
}

Лично я избегаю использования Aggregate() для конкатенации строк, потому что это приводит к множеству промежуточных, отброшенных строк. Это также не самый очевидный способ «объединить» кучу результатов вместе - он в первую очередь предназначен для вычисления «скалярных» результатов из коллекции произвольным, определенным вызывающим способом.

6 голосов
/ 09 февраля 2010

Вы можете использовать String.Join () .

String.Join(" and ",listEquations.Select(e=>String.Format("({0}{1}{2})",e.LeftSide,e.Operator,e.RightSide).ToArray());
3 голосов
/ 09 февраля 2010

Вы можете сделать это с помощью оператора Aggregate LINQ:

public string FormatEquationList(List<Equation> listEquations)
{
    return listEquations.Aggregate((a, b) => 
        "(" + a.LeftSide + a.Operator + a.RightSide + ") and (" + 
              b.LeftSide + b.Operator + b.RightSide + ")");
}
2 голосов
/ 09 февраля 2010

Обычно я добавляю его перед условием и проверяю, является ли это 1-м пунктом.

public string FormatEquationList(List<Equation> listEquations) 
{ 
    string output = string.Empty;
    foreach (Equation e in listEquations) 
    {
        //use conditional to insert your "between" data:
        output += (output == String.Empty) ? string.Empty : " and "; 

        //format the Equation 
        output += "(" + e.LeftSide + e.Operator + e.RightSide + ")"; 

    } 
    return ouput; 
}

Я должен сказать, что посмотрел бы и на функцию string.Join (), +1 для Linqiness. Мой пример - более традиционное решение.

2 голосов
/ 09 февраля 2010

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

Если вы хотите обрабатывать элементы попарно, выполните цикл у оператора LINQ Aggregate.

1 голос
/ 09 февраля 2010

Мне нравится уже опубликованный метод String.Join.

Но когда вы не используете массив, обычно я решил эту проблему:

public string FormatEquationList(List<Equation> listEquations)
{
    string output = string.Empty;
    foreach (Equation e in listEquations)
    {
        // only append " and " when there's something to append to
        if (output != string.Empty)
            output += " and ";

        output += "(" + e.LeftSide + e.Operator + e.RightSide + ")";
    }
    return output;
}

Конечно, обычно быстрее использовать StringBuilder:

public string FormatEquationList(List<Equation> listEquations)
{
    StringBuilder output = new StringBuilder();
    foreach (Equation e in listEquations)
    {
        // only append " and " when there's something to append to
        if (output.Length > 0)
            output.Append(" and ");

        output.Append("(");
        output.Append(e.LeftSide);
        output.Append(e.Operator);
        output.Append(e.RightSide);
        output.Append(")");
    }

    return output.ToString();
}
1 голос
/ 09 февраля 2010

Обычно я стараюсь ставить разделители на основе условий, а не добавлять их в конец.

string output = string.Empty;
for (int i = 0; i < 10; i++)
{
   output += output == string.Empty ? i.ToString() : " and " + i.ToString();
}

0 и 1 и 2 и 3 и 4 и 5 и 6 и 7 и 8 и 9

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