迭代IEnumerable,特殊的外壳最后一个元素

时间:2016-10-14 06:33:14

标签: c# ienumerable stringbuilder

我根据IEnumerable构建字符串,并执行以下操作:

public string BuildString()
{
    var enumerable = GetEnumerableFromSomewhere(); // actually an in parameter,
                                                   // but this way you don't have to care
                                                   // about the type :)

    var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();

    stringBuilder.Append("This is it: ");

    foreach(var part in interestingParts)
    {
        stringBuilder.AppendPart(part);

        if (part != interestingParts.Last())
        {
            stringBuilder.Append(", ");
        }
    }
}

private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
    stringBuilder.Append("[");
    stringBuilder.Append(part.Something");
    stringBuilder.Append("]");

    if (someCondition(part)) 
    {
         // this is in reality done in another extension method,
         // similar to the else clause
         stringBuilder.Append(" = @");
         stringBuilder.Append(part.SomethingElse");
    }
    else
    {
         // this is also an extension method, similar to this one
         // it casts the part to an IEnumerable, and iterates over
         // it in much the same way as the outer method.
         stringBuilder.AppendInFilter(part);
    }
}

我对这个成语并不完全满意,但我正在努力制定更简洁的东西。

当然,这是更大的字符串构建操作的一部分(其中有几个类似于此的块,以及其间的其他内容) - 否则我可能会删除StringBuilder并直接使用string.Join(", ", ...)

尽管如此,我最简单的尝试是为每个迭代器构建这样的结构:

stringBuilder.Append(string.Join(", ", propertyNames.Select(prop => "[" + prop + "]")));

但在这里我仍然使用+连接字符串,这使得感觉StringBuilder并没有真正贡献很多。

如何在保持高效的同时简化此代码?

4 个答案:

答案 0 :(得分:2)

你可以替换它:

string.Join(", ", propertyNames.Select(prop => "[" + prop + "]"))

使用c#6字符串插值:

string.Join(", ", propertyNames.Select(prop => $"[{prop}]"))

在这两种情况下,区别仅在于语义并且它并不重要。字符串连接就像在你的情况下选择一样不是问题。编译器仍然只为它创建一个新字符串(而不是4,每个段一个,联合字符串第四个)。

全部放在一起:

var result = string.Join(", ", enumerable.Select(v => $"[{v.TheInterestingStuff}]"));

因为foreach的主体更复杂,以适应字符串插值范围,你可以删除字符串的最后N个字符,如KooKiz建议的那样。

string separator = ", ";
foreach(var part in interestingParts)
{
    stringBuilder.Append("[");
    stringBuilder.Append(part);
    stringBuilder.Append("]");

    if (someCondition(part)) 
    {
        // Append more stuff
    }
    else
    {
        // Append other thingd
    }
    stringBuilder.Append(separator);
}
stringBuilder.Length = stringBuilder.Lenth - separator;

在任何情况下,我认为为了更好的封装,循环范围的内容应该位于一个单独的函数中,该函数将接收partseparator并将返回输出字符串。根据user734028

的建议,它也可以是StringBuilder的扩展方法

答案 1 :(得分:0)

汇总解决方案:

Making new env: CartPole-v0
You are calling 'step()' even though this environment has already returned done = True. You should always call 'reset()' once you receive 'done = True' -- any further steps are undefined behavior

序列化解决方案:

var answer = interestingParts.Select(v => "[" + v + "]").Aggregate((a, b) => a + ", " + b);

答案 2 :(得分:0)

Aggregate使用StringBuilder扩展名方法 如果您的集合很大,那么将更有效地连接字符串

        var builder = new StringBuilder();
        list.Aggregate(builder, (sb, person) =>
        {
            sb.Append(",");
            sb.Append("[");
            sb.Append(person.Name);
            sb.Append("]");
            return sb;
        });
        builder.Remove(0, 1); // Remove first comma

由于纯foreach总是比LINQ更高效,所以只需更改分隔符逗号的逻辑

var builder = new StringBuilder();
foreach(var part in enumerable.Select(v => v.TheInterestingStuff))
{
    builder.Append(", ");
    builder.Append("[");
    builder.Append(part);
    builder.Append("]");
}

builder.Remove(0, 2); //Remove first comma and space

答案 3 :(得分:0)

代码:

public string BuildString()
{
    var enumerable = GetEnumerableFromSomewhere();
    var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();

    stringBuilder.Append("This is it: ");

    foreach(var part in interestingParts)
    {
        stringBuilder.AppendPart(part)

    }
    if (stringBuilder.Length>0)
        stringBuilder.Length--;
}

private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
    if (someCondition(part)) 
    {
        stringBuilder.Append(string.Format("[{0}] = @{0}", part.Something));         

    }
    else
    {
         stringBuilder.Append(string.Format("[{0}]", part.Something));
         stringBuilder.AppendInFilter(part); //
    }
}
现在IMO好多了。

现在进行一些讨论,让它变得非常快。我们可以使用Parallel.For。但你会想(如果你认为)Append都发生在一个可共享的资源上,也就是StringBuilder,然后你必须把它锁定到Append to it,效率不高!好吧,如果我们可以说外部函数中for循环的每次迭代创建一个单独的字符串工件,那么我们可以有一个字符串数组,在Parallel开始之前分配给interestingPart的计数,并且每个索引都是Parallel for将其字符串存储到其各自的索引中。

类似的东西:

string[] iteration_buckets = new string[interestingParts.Length];
System.Threading.Tasks.Parallel.For(0, interestingParts.Length,
    (index) =>
    {
        iteration_buckets[index] = AppendPart(interestingParts[index]);
    });

你的函数AppendPart必须进行调整,使其成为非扩展名,只取一个字符串并返回一个字符串。 循环结束后你可以做一个string.Join得到一个字符串,这也是你可能用stringBuilder.ToString()做的。