8

I'm triyng to do a quick Haskell-like aggregation in Linq (C#), to turn a List into a string in the format "i^j^k..." etc.

is this possible in one query, or should I just do it the old-fasioned

foreach (int i in list)
{
     string+= i + "^"
}

(p.s. yes, that's pseudocode and wouldn't compile.)

Timothy Carter
  • 15,459
  • 7
  • 44
  • 62
Ed James
  • 10,385
  • 16
  • 71
  • 103

4 Answers4

30

Use string.Join:

string.Join("^", list.Select(x => x.ToString()).ToArray())

In this particular case it may be more efficient to use StringBuilder directly as Append(int) may avoid creating the temporary strings. Unless this turns out to be a bottleneck, however, I'd stick to this simple single expression.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • A fine answer, as Join uses a StringBuilder under the hood. – Steven Sudit Sep 15 '09 at 16:59
  • A good answer, but I was actually looking for the functional-style syntax! – Ed James Sep 15 '09 at 17:10
  • 2
    What's not functional about it? You *could* use Aggregate if you really wanted to, but it would be uglier and perform worse. – Jon Skeet Sep 15 '09 at 17:15
  • I was specifically looking for the linq implementation using aggregate, you original answer was rather less functional, and that's when I posted my comment. – Ed James Sep 15 '09 at 17:17
  • 5
    Again, I don't see it as non-functional. It doesn't mutate anything, it just projects the list of ints into a list of strings, projects that to an array, and then uses a standard library method - which again, has no side effects. The solution is simpler to understand, IMO - and highlights how it's worth knowing the simplest/best ways to do things in your target platform instead of hanging on to idioms from other platforms. – Jon Skeet Sep 15 '09 at 17:24
8

You can use the Aggregate extension:

string s = list.Aggregate<int, string>(String.Empty, (x, y) => (x.Length > 0 ? x + "^" : x) + y.ToString());
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    While it may be elegant in some sense, the performance will be poor. I endorse Jon's answer here. – Steven Sudit Sep 15 '09 at 17:13
  • A fair enough point, however I am only concatenating at most a few sub-1000 integers, that kind of micro-optimisation is not needed! Also, I was interested in the syntax, I'm used to Haskell for functional stuff, so Linq sometimes confuses me! – Ed James Sep 15 '09 at 17:15
  • +1 for getting it all working - but I still don't think it's as simple/readable as using the built-in Join method :) You can get away without the final `ToString()` call if you want to make it shorter, although obviously it'll still be called really. – Jon Skeet Sep 15 '09 at 17:39
  • Yes, a String.Join or a StringBuilder performs better, the aggregate performs the same as the pseudo code in the question. Regarding the ToString, it does save a boxing to call it explicity instead of having the String.Concat method calling it. – Guffa Sep 15 '09 at 18:03
  • @Guffa: Just to clarify, I think you're saying that, since the compiler can't match on +(string,string), it upcasts to +(string,object), then calls ToString on the object. Right? – Steven Sudit Sep 16 '09 at 02:46
  • 1
    @Steven: The + operator for strings compiles into String.Concat calls. If all operators are strings, String.Concat(params string, string) is called, otherwise String.Concat(object, object) is called. – Guffa Sep 16 '09 at 05:22
  • @Guffa: Thanks for the clarification. There's a similar optimization for String.Format, where there are a few overloads that takes strings, plus one that takes a params object[]. – Steven Sudit Sep 17 '09 at 14:48
  • @Steven: There are no overloads for String.Format that takes anything other than Object for the values. You must be thinking of some other method. – Guffa Sep 17 '09 at 15:42
  • @Guffa: You are correct. Now I'm going to kill myself trying to remember what that method was. – Steven Sudit Sep 20 '09 at 23:29
1

It's definitely possible in LINQ (see other answer for syntax).

However, you might consider using a StringBuilder instead.

StringBuilder sb = new StringBuilder();
foreach (int i in list) sb.Append(i.ToString());

In you case, better is:

String.Join("^", list.ToArray());

that uses StringBuilder to accomplish the same work.

Stuart Thompson
  • 1,839
  • 2
  • 15
  • 17
0

It's much more efficient to use a StringBuilder instance here.

Steven Sudit
  • 19,391
  • 1
  • 51
  • 53
  • The list is going to have around 10 things in it. It's really not, stringbuilders are only more efficient by a small factor when using a small amount of variables. If it had a few thousand, yes. – Ed James Sep 15 '09 at 17:01
  • sorry, that's true, but it's still not a relevant answer as I'm basically concatenating together 1 2 3 4 and 5, not huge amounts of text. – Ed James Sep 15 '09 at 17:06
  • That's not entirely true. The size of the strings being concatenated is important also, not just the number of times the loop is firing. – kemiller2002 Sep 15 '09 at 17:06
  • @Kevin: That's correct. Each concatenation creates a new string, requiring that the entirety of the old string be copied. In contrast, StringBuilder only needs to recopy when it has to expand its internal buffer, which can be limited by initializing with a capacity and which self-limits (I believe) by doubling the size when expanding. When ToString is called, it may need to copy one more time, depending. – Steven Sudit Sep 15 '09 at 17:16
  • @Ed: For sufficiently small quantities, it would be hard to find a method that will cause a measurable performance hit. Having said that, I don't feel comfortable intentionally writing inefficient code when the simple and efficient option of Join is available. – Steven Sudit Sep 15 '09 at 17:17
  • @Ed: Furthermore, the encapsulation of Join allows for an improved implementation in a future version of the framework. Join is a metaphor for the action that is being performed and is a very logical way to express the intention of joining strings. – Stuart Thompson Sep 15 '09 at 18:55