将分隔符添加到要显示的项目列表中

时间:2009-05-12 14:38:47

标签: c# algorithm

我有一个项目列表,我希望在C#中显示它们之间的分隔符。使用普通的迭代器,我最终会在开头或结尾处添加一个额外的分隔符:

string[] sa = {"one", "two", "three", "four"};
string ns = "";
foreach(string s in sa)
{
    ns += s + " * ";
}
// ns has a trailing *:
// one * two * three * four * 

现在我可以使用for循环来解决这个问题:

ns = "";
for(int i=0; i<sa.Length; i++)
{
    ns += sa[i];
    if(i != sa.Length-1)
        ns += " * ";
}
// this works:
// one * two * three * four

虽然第二种解决方案有效但看起来并不优雅。有更好的方法吗?

4 个答案:

答案 0 :(得分:19)

您需要内置的String.Join方法:

string ns = string.Join(" * ", sa);

如果你想对其他集合类型做同样的事情,那么如果你首先使用LINQ的ToArray方法创建一个数组,你仍然可以使用String.Join

string ns = string.Join(" * ", test.ToArray());

答案 1 :(得分:3)

除了优雅之外,您可能还需要考虑除String之外的其他类型的速度和可重用性。为了优雅,我建议使用扩展方法来抽象细节,以便常见用法看起来像:

ns = sa.Join(" * ");

对于速度,请考虑以下变体测试,包括已回答问题的其他人提出的一些解决方案:

public void Test_variants()
{
    const string item = "a";
    const int numberOfTimes = 100000;
    const string delimiter = ", ";
    string[] items = new List<string>(Enumerable.Repeat(item, numberOfTimes)).ToArray();
    string expected = String.Join(delimiter, items);

    Time(StringJoin, items, delimiter, expected);
    Time(Aggregate, items, delimiter, expected);
    Time(CheckForEndInsideLoop_String, items, delimiter, expected);
    Time(CheckForBeginningInsideLoop_String, items, delimiter, expected);
    Time(RemoveFinalDelimiter_String, items, delimiter, expected);
    Time(CheckForEndInsideLoop_StringBuilder, items, delimiter, expected);
    Time(RemoveFinalDelimiter_StringBuilder, items, delimiter, expected);
}

private static void Time(Func<string[], string, string> func, string[] items, string delimiter, string expected)
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    string result = func(items, delimiter);
    stopwatch.Stop();
    bool isValid = result == expected;
    Console.WriteLine("{0}\t{1}\t{2}", stopwatch.Elapsed, isValid, func.Method.Name);
}

private static string CheckForEndInsideLoop_String(string[] items, string delimiter)
{
    string result = "";
    for (int i = 0; i < items.Length; i++)
    {
        result += items[i];
        if (i != items.Length - 1)
        {
            result += delimiter;
        }
    }
    return result;
}

private static string RemoveFinalDelimiter_String(string[] items, string delimiter)
{
    string result = "";
    for (int i = 0; i < items.Length; i++)
    {
        result += items[i] + delimiter;
    }
    return result.Substring(0, result.Length - delimiter.Length);
}

private static string CheckForBeginningInsideLoop_String(string[] items, string delimiter)
{
    string result = "";
    foreach (string s in items)
    {
        if (result.Length != 0)
        {
            result += delimiter;
        }
        result += s;
    }
    return result;
}

private static string CheckForEndInsideLoop_StringBuilder(string[] items, string delimiter)
{
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < items.Length; i++)
    {
        result.Append(items[i]);
        if (i != items.Length - 1)
        {
            result.Append(delimiter);
        }
    }
    return result.ToString();
}

private static string RemoveFinalDelimiter_StringBuilder(string[] items, string delimiter)
{
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < items.Length; i++)
    {
        result.Append(items[i]);
        result.Append(delimiter);
    }
    result.Length = result.Length - delimiter.Length;
    return result.ToString();
}

private static string StringJoin(string[] items, string delimiter)
{
    return String.Join(delimiter, items);
}

private static string Aggregate(string[] items, string delimiter)
{
    return items.Aggregate((c, s) => c + delimiter + s);
}

我的方框上的结果如下:

00:00:00.0027745    True    StringJoin
00:00:24.5523967    True    Aggregate
00:00:47.8091632    True    CheckForEndInsideLoop_String
00:00:47.4682981    True    CheckForBeginningInsideLoop_String
00:00:23.7972864    True    RemoveFinalDelimiter_String
00:00:00.0076439    True    CheckForEndInsideLoop_StringBuilder
00:00:00.0052803    True    RemoveFinalDelimiter_StringBuilder

这意味着你最好的选择,如果你只使用字符串数组,String.Join紧跟StringBuilder变种。请注意,检查循环内的最后一项在处理字符串时会比使用StringBuilder时产生更大的差异。当要分隔的项目列表很小时,基于字符串的实现的性能也会提高很多。我将numberOfItems设置为10运行相同的测试,并收到以下结果:

00:00:00.0001788    True    StringJoin
00:00:00.0014983    True    Aggregate
00:00:00.0001666    True    CheckForEndInsideLoop_String
00:00:00.0002202    True    CheckForBeginningInsideLoop_String
00:00:00.0002061    True    RemoveFinalDelimiter_String
00:00:00.0002663    True    CheckForEndInsideLoop_StringBuilder
00:00:00.0002278    True    RemoveFinalDelimiter_StringBuilder

您可能要考虑的下一件事是可重用性。如果你想从一个由分隔符String.Join分隔的整数列表中构建一个字符串,那么在每个整数上运行.ToString()并创建一个字符串数组之后它只会是一个选项(因为String.Join不能对IEnumerable&lt; ;串GT;)

因此,总而言之,您可以考虑使用以下几行的扩展方法来获得优雅,速度和可重用性的良好组合:

public static string Join<T>([CanBeNull] this IEnumerable<T> items, [CanBeNull] string delimiter)
{
    StringBuilder result = new StringBuilder();
    if (items != null && items.Any())
    {
        delimiter = delimiter ?? "";
        foreach (T item in items)
        {
            result.Append(item);
            result.Append(delimiter);
        }
        result.Length = result.Length - delimiter.Length;
    }
    return result.ToString();
}

用法:

ns = sa.Join(" * ");

答案 2 :(得分:1)

这种方法的优点是你可以在任何类型的序列上使用它,而不仅仅是字符串数组。

var ns = sa.Aggregate( (c, s) => c + " * " + s);

答案 3 :(得分:0)

我更喜欢卢克的解决方案。

string ns = string.Join(" * ", sa);

或者,如果您的集合不可索引但只能枚举,则可以执行此操作:

string ns = "";
foreach(string s in sa)
{
    if (ns.Length != 0)
    {
        ns += " * ";
    }

    ns += s;
}

这就像你的第二个例子,但是它将测试置于循环的开始,并且不太可能像第二个例子中那样遇到一次性错误。数组显然是可索引的,但在某些情况下,你会获得不可索引的容器(即System.Collections.Generic.Dictionary&lt; T,K&gt; .Values),你会想要这样的东西。