views:

2694

answers:

20

What is the best way to join a list of strings into a combined delimited string. I'm mainly concerned about when to stop adding the delimiter. I'll use C# for my examples but I would like this to be language agnostic.

EDIT: I have not used StringBuilder to make the code slightly simpler.

Use a For Loop

for(int i=0; i < list.Length; i++)
{
    result += list[i];
    if(i != list.Length - 1)
        result += delimiter;
}

Use a For Loop setting the first item previously

result = list[0];
for(int i = 1; i < list.Length; i++)
    result += delimiter + list[i];

These won't work for an IEnumerable where you don't know the length of the list beforehand so

Using a foreach loop

bool first = true;
foreach(string item in list)
{
    if(!first)
        result += delimiter;
    result += item;
    first = false;
}

Variation on a foreach loop

From Jon's solution

StringBuilder builder = new StringBuilder();
string delimiter = "";
foreach (string item in list)
{
    builder.Append(delimiter);
    builder.Append(item);
    delimiter = ",";       
}
return builder.ToString();

Using an Iterator

Again from Jon

using (IEnumerator<string> iterator = list.GetEnumerator())
{
    if (!iterator.MoveNext())
        return "";
    StringBuilder builder = new StringBuilder(iterator.Current);
    while (iterator.MoveNext())
    {
        builder.Append(delimiter);
        builder.Append(iterator.Current);
    }
    return builder.ToString();
}

What other algorithms are there?

A: 
string result = "";
foreach(string item in list)
{
    result += delimiter + item;
}
result = result.Substring(1);

Edit: Of course, you wouldn't use this or any one of your algorithms to concatenate strings. With C#/.NET, you'd probably use a StringBuilder:

StringBuilder sb = new StringBuilder();
foreach(string item in list)
{
    sb.Append(delimiter);
 sb.Append(item);
}
string result = sb.ToString(1, sb.Length-1);

And a variation of this solution:

StringBuilder sb = new StringBuilder(list[0]);
for (int i=1; i<list.Count; i++)
{
 sb.Append(delimiter);
 sb.Append(list[i]);
}
string result = sb.ToString();

Both solutions do not include any error checks.

M4N
That's very inefficient - at least in C# - as it will copy the "string so far" each time.
Jon Skeet
I know this is inefficient (I'd use a StringBuilder). But that was not the question.
M4N
The question was for the *best* way. "Hideously inefficient" and "best" usually don't go together.
Jon Skeet
Why not edit the answer to use StringBuilder? The whole notion of this being language-agnostic is broken IMO, so at least give an answer which is appropriate for C# :)
Jon Skeet
As requested :-)
M4N
That still involves a pointless copy - hence my different approach :)
Jon Skeet
Added a third solution.
M4N
And the third solution is wrong; it puts an extra delimiter at the end.
joel.neely
@joel: No it doesn't!
M4N
@Joel, it doesnt, the starting index is 1 not 0.
codemeit
It fails for an empty list though :)
Jon Skeet
+4  A: 

There's little reason to make it language-agnostic when some languages provide support for this in one line, e.g., Python's

",".join(sequence)

See the join documentation for more info.

Hank Gay
A: 

that's how python solves the problem:

','.join(list_of_strings)

I've never could understand the need for 'algorithms' in trivial cases though

SilentGhost
No, that's how you _write_ it in Python. To see how Python _solves_ the problem, you will have to look into the implementation of `join`, and there, you will find an algorithm.
Svante
no, that's how python solves this problem for me. i don't need to invent no algorithms because python provides an idiomatic built-in method for me. what algorithm particular python implementation uses has no relevance here.
SilentGhost
The question was for the algorithm. As the original poster, I wouldn't be interested in "I call Bob, who can code it in Blub" either.
Svante
By the way, I don't understand why you rolled back to broken and wrong grammar and spelling. Is this an ego issue? You will have to live with it now.
Svante
well, this is algorithm in python. it's not my fault that some languages are daft. there is nothing wrong with my grammar and spelling, thankyouverymuch.
SilentGhost
+11  A: 

It's impossible to give a truly language-agnostic answer here as different languages and platforms handle strings differently, and provide different levels of built-in support for joining lists of strings. You could take pretty much identical code in two different languages, and it would be great in one and awful in another.

In C#, you could use:

StringBuilder builder = new StringBuilder();
string delimiter = "";
foreach (string item in list)
{
    builder.Append(delimiter);
    builder.Append(item);
    delimiter = ",";       
}
return builder.ToString();

This will prepend a comma on all but the first item. Similar code would be good in Java too.

EDIT: Here's an alternative, a bit like Ian's later answer but working on a general IEnumerable<string>.

// Change to IEnumerator for the non-generic IEnumerable
using (IEnumerator<string> iterator = list.GetEnumerator())
{
    if (!iterator.MoveNext())
    {
        return "";
    }
    StringBuilder builder = new StringBuilder(iterator.Current);
    while (iterator.MoveNext())
    {
        builder.Append(delimiter);
        builder.Append(iterator.Current);
    }
    return builder.ToString();
}
Jon Skeet
Now my solution is even shorter than yours :-)
M4N
Shorter but less efficient - taking a substring at the end still means an inelegant copy.
Jon Skeet
I've been using that technique for about 30 years, and am amazed at the clumsy alternatives that some people produce.
joel.neely
A: 

You could write your own method AppendTostring(string, delimiter) that appends the delimiter if and only if the string is not empty. Then you just call that method in any loop without having to worry when to append and when not to append.

Edit: better yet of course to use some kind of StringBuffer in the method if available.

tehvan
+2  A: 

I'd always add the delimeter and then remove it at the end if necessary. This way, you're not executing an if statement for every iteration of the loop when you only care about doing the work once.

StringBuilder sb = new StringBuilder();

foreach(string item in list){
    sb.Append(item);
    sb.Append(delimeter);
}

if (list.Count > 0) {
    sb.Remove(sb.Length - delimter.Length, delimeter.Length)
}
+9  A: 

In .NET, you can use the String.Join method:

string concatenated = String.Join(",", list.ToArray());

Using .NET Reflector, we can find out how it does it:

public static unsafe string Join(string separator, string[] value, int startIndex, int count)
{
    if (separator == null)
    {
        separator = Empty;
    }
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }
    if (startIndex < 0)
    {
        throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
    }
    if (startIndex > (value.Length - count))
    {
        throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
    }
    if (count == 0)
    {
        return Empty;
    }
    int length = 0;
    int num2 = (startIndex + count) - 1;
    for (int i = startIndex; i <= num2; i++)
    {
        if (value[i] != null)
        {
            length += value[i].Length;
        }
    }
    length += (count - 1) * separator.Length;
    if ((length < 0) || ((length + 1) < 0))
    {
        throw new OutOfMemoryException();
    }
    if (length == 0)
    {
        return Empty;
    }
    string str = FastAllocateString(length);
    fixed (char* chRef = &str.m_firstChar)
    {
        UnSafeCharBuffer buffer = new UnSafeCharBuffer(chRef, length);
        buffer.AppendString(value[startIndex]);
        for (int j = startIndex + 1; j <= num2; j++)
        {
            buffer.AppendString(separator);
            buffer.AppendString(value[j]);
        }
    }
    return str;
}
Ian Nelson
variable of IEnumerable can not use ToArray. However the generic one does.
codemeit
@CodeMelt: IEnumerable<T> can use ToArray via the Enumerable.ToArray extension method in .NET 3.5.
Jon Skeet
@Jon, my first comment says the generic one can do ToArray. IEnumerable is the one does not have the extension method.
codemeit
Okay, I'm with you. Where does anyone mention the non-generic IEnumerable, out of interest?
Jon Skeet
@Jon, in the question, it asked "These won't work for an IEnumerable where you don't know the length of the list beforehand so"
codemeit
@CodeMelt: Ah, I see. In that case, I suggest the foo.Cast<string>().ToArray()
Jon Skeet
Thanks, I see the performance vs convenience are going on within this thread.
codemeit
There's definitely a balance between the two, yes. Actually, using ToArray may be faster in some cases, as the String.Join code is optimised because it knows the exact size to allocate ahead of time.
Jon Skeet
string.Join is good one, could you please have a look at post of CodeMeIt and give some input? thanks in advance.
codemeit
+2  A: 

In C# you can just use String.Join(separator,string_list)

vartec
variable of IEnumerable can not use ToArray
codemeit
@CodeMelt: See my response to Ian's answer.
Jon Skeet
@Jon, please see Ian's answer again.
codemeit
@CodeMelt: ditto :)
Jon Skeet
+1  A: 

Since you tagged this language agnostic,

This is how you would do it in python

# delimiter can be multichar like "| trlalala |"
delimiter = ";"
# sequence can be any list, or iterator/generator that returns list of strings
result = delimiter.join(sequence)
#result will NOT have ending delimiter

Edit: I see I got beat to the answer by several people. Sorry for dupication

Luka Marinko
A: 

In .NET, I would use the String.join method if possible, which allows you to specify a separator and a string array. A list can be converted to an array with ToArray, but I don't know what the performance hit of that would be.

The three algorithms that you mention are what I would use (I like the second because it does not have an if statement in it, but if the length is not known I would use the third because it does not duplicate the code). The second will only work if the list is not empty, so that might take another if statement.

A fourth variant might be to put a seperator in front of every element that is concatenated and then remove the first separator from the result.

If you do concatenate strings in a loop, note that for non trivial cases the use of a stringbuilder will vastly outperform repeated string concatenations.

Renze de Waal
+1  A: 

This is a Working solution in C#, in Java, you can use similar for each on iterator.

        string result = string.Empty; 

        // use stringbuilder at some stage.
        foreach (string item in list)
            result += "," + item ;

        result = result.Substring(1);
        // output:  "item,item,item"

If using .NET, you might want to use extension method so that you can do list.ToString(",") For details, check out Separator Delimited ToString for Array, List, Dictionary, Generic IEnumerable

// contains extension methods, it must be a static class.
public static class ExtensionMethod
{
    // apply this extension to any generic IEnumerable object.
    public static string ToString<T>(this IEnumerable<T> source,
      string separator)
    {
        if (source == null)
           throw new ArgumentException("source can not be null.");

        if (string.IsNullOrEmpty(separator))
           throw new ArgumentException("separator can not be null or empty.");

        // A LINQ query to call ToString on each elements
        // and constructs a string array.
        string[] array =
         (from s in source
          select s.ToString()
          ).ToArray();

        // utilise builtin string.Join to concate elements with
        // customizable separator.
        return string.Join(separator, array);
    }
}

EDIT:For performance reasons, replace the concatenation code with string builder solution that mentioned within this thread.

codemeit
Using string concatenation like the first example is horribly inefficient. In Java and .NET, use a StringBuilder.
Jon Skeet
@Jon, you are right, maybe at some stage use chars.
codemeit
@Jon, btw there is comment above the first example.
codemeit
+2  A: 

For python be sure you have a list of strings, else ','.join(x) will fail. For a safe method using 2.5+

delimiter = '","'
delimiter.join(str(a) if a else '' for a in list_object)

The "str(a) if a else ''" is good for None types otherwise str() ends up making then 'None' which isn't nice ;)

Christian Witts
+1  A: 

I thint the best way to do something like that is (I'll use pseudo-code, so we'll make it truly language agnostic):

function concat(<array> list, <boolean> strict):
  for i in list:
    if the length of i is zero and strict is false:
      continue;
    if i is not the first element:
      result = result + separator;
    result = result + i;
  return result;

the second argument to concat(), strict, is a flag to know if eventual empty strings have to be considered in concatenation or not.

I'm used to not consider appending a final separator; on the other hand, if strict is false the resulting string could be free of stuff like "A,B,,,F", provided the separator is a comma, but would instead present as "A,B,F".

Scarlet
+2  A: 
Svante
A: 

From http://dogsblog.softwarehouse.co.zw/post/2009/02/11/IEnumerable-to-Comma-Separated-List-(and-more).aspx

A pet hate of mine when developing is making a list of comma separated ids, it is SO simple but always has ugly code.... Common solutions are to loop through and put a comma after each item then remove the last character, or to have an if statement to check if you at the begining or end of the list. Below is a solution you can use on any IEnumberable ie a List, Array etc. It is also the most efficient way I can think of doing it as it relies on assignment which is better than editing a string or using an if.

public static class StringExtensions
{
    public static string Splice<T>(IEnumerable<T> args, string delimiter)
    {
        StringBuilder sb = new StringBuilder();
        string d = "";
        foreach (T t in args)
        {
            sb.Append(d);
            sb.Append(t.ToString());
            d = delimiter;
        }
        return sb.ToString();
    }
}

Now it can be used with any IEnumerable eg.

StringExtensions.Splice(billingTransactions.Select(t => t.id), ",")

to give us 31,32,35

Matthew Hood
+3  A: 

In PHP's implode():

$string = implode($delim, $array);
Nerdling
A: 

Seen the Python answer like 3 times, but no Ruby?!?!?

the first part of the code declares a new array. Then you can just call the .join() method and pass the delimiter and it will return a string with the delimiter in the middle. I believe the join method calls the .to_s method on each item before it concatenates.

["ID", "Description", "Active"].join(",")
>> "ID, Description, Active"

this can be very useful when combining meta-programming with with database interaction.

does anyone know if c# has something similar to this syntax sugar?

taelor
A: 

For java a very complete answer has been given in this question or this question.

That is use StringUtils.join in Apache Commons

String result = StringUtils.join(list, ", ");
e5
A: 

In Clojure, you could just use clojure.contrib.str-utils/str-join:

(str-join ", " list)

But for the actual algorithm:

(reduce (fn [res cur] (str res ", " cur)) list)
A: 

Groovy also has a String Object.join(String) method.