tags:

views:

110

answers:

7

if i have:

 List<Car>

where car is:

 public class Car
 {
      public int Year;
      public string Name;
 }

and i want to take this array and create a concatenated string by ","

so it would return:

"Toyota, Ford, Chevy"

i can do it manually like this:

  private static string CreateConcatenatedList(List<Car> parts_)
    {
        StringBuilder b = new StringBuilder();
        foreach (Car bp in parts_)
        {
            b.Append(bp.Name + ", ");
        }
        b.Remove(b.Length - 2, 2);
        return b.ToString();
    }

but i thought there might be a more elegant way

+4  A: 
List<Car> cars = ....
var result = string.Join(",", cars.Select(car => car.Name).ToArray());
Roger Alsing
Also note that in .NET 4.0 you don't need `.ToArray()`, as `String.Join` now has an overload with `IEnumerable<string>`: http://msdn.microsoft.com/en-us/library/dd783876.aspx
Lasse V. Karlsen
+5  A: 
List<Car> cars = //whatever;
string concat = String.Join(",", cars.Select(c => c.Name).ToArray());

EDIT: you could also use Aggregate if you're worried about creating the intermediate array:

string concat = cars.Select(c => c.Name).Aggregate(new StringBuilder(), (sb, current) =>
{
    return sb.Length == 0 ? sb.Append(current) : sb.AppendFormat(",{0}", current);
}).ToString();
Lee
@Lee - is this faster than my solution or just less code?
ooo
@oo - It creates an intermediate array which is a little inefficient, although you probably won't notice unless you have a lot of elements in the list. You can use Aggregate to avoid this.
Lee
.NET 4.0 contains a string.join overload accepting an IEnumberable: http://msdn.microsoft.com/en-us/library/dd783876.aspx
Dykam
Using aggregate doesnt pay off, as one could expect (I expected it to be much faster than the 'first create array then join it' method, too). See the update in my answer.
Philip Daubmeier
@Dykam: thats great. That will make even more of an improvement in those cases like we have here. I dont have .net 4.0 yet. Unfortunatelly I cant compare it with the other methods.
Philip Daubmeier
A: 

I think that what you really want is:

"Toyota, Ford, Chevy" 

and not:

"Toyota", "Ford", "Chevy" 

as written in your question. This can be achieved like so:

var cars = new List<Car>();

var delimitedString = string.Join(", ", cars.Select(c => c.Name).ToArray());
klausbyskov
@klausbyskov - you are correct. i have updated the question
ooo
A: 
ArrayList<Car> cars = ...
string finalValue = string.Join(",", cars.Select(c => c.Name).ToArray());
MasterGaurav
Ha ha ha ha! All the same ;)
MasterGaurav
A: 
String.Join(",", cars.ToArray());
Tim Schmelter
-1: This hinges on the fact that you're using .NET 4.0 and that the `Car` class has an appropriate `.ToString()` implementation.
Lasse V. Karlsen
@Lasse: but even if `Car` had a `.ToString()`, `cars.ToArray()` would create an array of cars, not of strings, and `String.Join` expects an `IEnumerable<string>` in .NET 4.0
Philip Daubmeier
Youre right, was over-hasty.But i think the OP has enough good answers now ;-)
Tim Schmelter
@Philip: Not quite, there's an overload for T: `String.Join<T>(String separator, IEnumerable<T> values)`: http://msdn.microsoft.com/en-us/library/dd992421.aspx
Lasse V. Karlsen
@Lasse: Ah right, yeah thats even better :)
Philip Daubmeier
A: 

I wrote the following extention method for this type of case. It uses a string builder and Aggregate instead of string.Join and an array for a slight improvement in performance.

public static string Concatenate(
    this IEnumerable<string> collection, 
    string separator)
{
    return collection
        .Skip(1)
        .Aggregate(
            new StringBuilder().Append(collection.First()),
            (b, s) => b.Append(separator).Append(s))
        .ToString();
}

Then in your case it's just

cars.Select(c=>Name).Concatenate(", ");
juharr
+4  A: 

Because you asked in a comment to Lees answer whether it is faster/slower or just less code. I tried a bit, and wrote a small car class:

public class Car
{
    public string Name { get; set; }
    public Car(string name) { Name = name; }
}

Tested it with randomly generated strings of length 5-10:

private static Random random = new Random((int)DateTime.Now.Ticks);
private static string RandomString(int min, int max)
{
    string str = "";
    int size = random.Next(min, max + 1);
    for (int i = 0; i < size; i++)
        str += Convert.ToChar(Convert.ToInt32(
                       Math.Floor(26 * random.NextDouble() + 65)));
    return str;
}

public static void MeassureTicks(int numberCars, int minLength, int maxLength)
{
    // Generate random list
    List<Car> cars = Enumerable.Range(0, numberCars)
                     .Select(x => new Car(RandomString(
                             minLength, maxLength))).ToList();

    Stopwatch sw1 = new Stopwatch(), sw2 = new Stopwatch(),
              sw3 = new Stopwatch(), sw4 = new Stopwatch();

    sw1.Start();
    string concat1 = CreateConcatenatedList(cars);
    sw1.Stop();
    sw2.Start();
    string concat2 = String.Join(",", cars.Select(c => c.Name).ToArray());
    sw2.Stop();
    sw3.Start();
    if (numberCars <= 5000)
    {
        string concat3 = cars.Select(c => c.Name).Aggregate("",
                (str, current) =>
                {
                    return str.Length == 0 ? str = current :
                           str += "," + current;
                }).ToString();
    }
    sw3.Stop();
    sw4.Start();
    string concat4 = cars.Select(c => c.Name).Aggregate(
            new StringBuilder(), (sb, current) =>
            {
                return sb.Length == 0 ? sb.Append(current) :
                       sb.AppendFormat(",{0}", current);
            }).ToString();
    sw4.Stop();

    Console.WriteLine(string.Format("{0} car strings joined:\n" +
                "\tYour method:                  {1} ticks\n" + 
                "\tLinq+String.Join:             {2} ticks\n" + 
                "\tLinq+Aggregate+String.Concat: {3} ticks\n" + 
                "\tLinq+Aggregate+StringBuilder: {4} ticks\n",
                cars.Count, sw1.ElapsedTicks, sw2.ElapsedTicks, 
                numberCars <= 5000 ? sw3.ElapsedTicks.ToString() : "-", 
                sw4.ElapsedTicks));

Update: I am now trying both methods that are using aggregate, too.

The outputs are on my pc for some different number of cars:

5 car strings joined:
        Your method:                  14 ticks
        Linq+String.Join:             20 ticks
        Linq+Aggregate+String.Concat: 11 ticks
        Linq+Aggregate+StringBuilder: 15 ticks

50 car strings joined:
        Your method:                  50 ticks
        Linq+String.Join:             45 ticks
        Linq+Aggregate+String.Concat: 70 ticks
        Linq+Aggregate+StringBuilder: 73 ticks

500 car strings joined:
        Your method:                  355 ticks
        Linq+String.Join:             348 ticks
        Linq+Aggregate+String.Concat: 5365 ticks
        Linq+Aggregate+StringBuilder: 619 ticks

5000 car strings joined:
        Your method:                  3584 ticks
        Linq+String.Join:             3357 ticks
        Linq+Aggregate+String.Concat: 379635 ticks
        Linq+Aggregate+StringBuilder: 6078 ticks

50000 car strings joined:
        Your method:                  33705 ticks
        Linq+String.Join:             34082 ticks
        Linq+Aggregate+String.Concat: - ticks
        Linq+Aggregate+StringBuilder: 92839 ticks

500000 car strings joined:
        Your method:                  508439 ticks
        Linq+String.Join:             376339 ticks
        Linq+Aggregate+String.Concat: - ticks
        Linq+Aggregate+StringBuilder: 616048 ticks

The Linq+String.Join method is indeed a bit faster and less code. Aggregate together with the StringBuilter scales very well (not like the string concatenation), but is a bit slower. So either use your method, or Linq+String.Join, which is a nice oneliner and also easily readable.

Philip Daubmeier