29

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?

Ed Schwehm
  • 2,163
  • 4
  • 32
  • 55
John Oxley
  • 14,698
  • 18
  • 53
  • 78

25 Answers25

43

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();
}

EDIT nearly 5 years after the original answer...

In .NET 4, string.Join was overloaded pretty significantly. There's an overload taking IEnumerable<T> which automatically calls ToString, and there's an overload for IEnumerable<string>. So you don't need the code above any more... for .NET, anyway.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Now my solution is even shorter than yours :-) – M4N Feb 24 '09 at 11:56
  • 1
    Shorter but less efficient - taking a substring at the end still means an inelegant copy. – Jon Skeet Feb 24 '09 at 12:00
  • It is a rather old topic, but I feel the need to point to a different solution if you could work with a StringBuilder. The class has a property named Length that you could change at your will. So add always the delimiter, and after the loop decrease the Length property for the length of the separator. However, the difference in performance is minimal – Steve Jun 18 '13 at 10:24
  • For VB.Net you can add the delimiter as sBuilder.append("text" & ","). Then when you're done, sBuilder.toString.TrimEnd(","c). – Thomas B Aug 13 '13 at 03:02
  • True, it depends on the situation. Advantage of trimming it is you don't have to assign a value to the variable for each list item so it's a cheaper for larger lists. It depend on your application. I deal with it mostly in SQL and JavaScript so it's rarely an issue. – Thomas B Aug 25 '13 at 15:32
  • @ThomasB: I can't see that it would be likely to come down to a matter of performance: either you want multiple trailing commas to be trimmed, or you don't. Correctness is more important. – Jon Skeet Aug 25 '13 at 15:37
  • Only thing that sucks about using string builder is that you have to define the seperator logic, if you just use an array as a backing store at the end you can just do `string.Join(" ", myStringArray) – The Muffin Man Feb 24 '14 at 19:51
  • @TheMuffinMan: Actually in more recent releases `string.Join` has been overloaded anyway. Will add that :) – Jon Skeet Feb 24 '14 at 19:51
25

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
  • 57,123
  • 20
  • 76
  • 103
  • variable of IEnumerable can not use ToArray. However the generic one does. – Ray Lu Feb 24 '09 at 11:52
  • @CodeMelt: IEnumerable can use ToArray via the Enumerable.ToArray extension method in .NET 3.5. – Jon Skeet Feb 24 '09 at 12:02
  • @Jon, my first comment says the generic one can do ToArray. IEnumerable is the one does not have the extension method. – Ray Lu Feb 24 '09 at 12:11
  • Okay, I'm with you. Where does anyone mention the non-generic IEnumerable, out of interest? – Jon Skeet Feb 24 '09 at 12:31
  • @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" – Ray Lu Feb 24 '09 at 12:33
  • @CodeMelt: Ah, I see. In that case, I suggest the foo.Cast().ToArray() – Jon Skeet Feb 24 '09 at 12:43
  • Thanks, I see the performance vs convenience are going on within this thread. – Ray Lu Feb 24 '09 at 12:54
  • 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 Feb 24 '09 at 13:05
  • string.Join is good one, could you please have a look at post of CodeMeIt and give some input? thanks in advance. – Ray Lu Feb 24 '09 at 13:16
8

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
  • 70,339
  • 36
  • 160
  • 222
5

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
  • 11,375
  • 1
  • 33
  • 46
5

In PHP's implode():

$string = implode($delim, $array);
Jeremy L
  • 7,686
  • 4
  • 29
  • 36
4
List<string> aaa = new List<string>{ "aaa", "bbb", "ccc" };
string mm = ";";
return aaa.Aggregate((a, b) => a + mm + b);

and you get

aaa;bbb;ccc

lambda is pretty handy

eponie
  • 1
  • 1
  • WATCH OUT: this leads to Schlemiel the Painter's Algorithm due to copying the resulting string over and over again. Better to use the overload using a StringBuilder or string.Join. – Pieter Sep 30 '14 at 13:45
4

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)
}
4

I would express this recursively.

  • Check if the number of string arguments is 1. If it is, return it.
  • Otherwise recurse, but combine the first two arguments with the delimiter between them.

Example in Common Lisp:

(defun join (delimiter &rest strings)
  (if (null (rest strings))
      (first strings)
      (apply #'join
             delimiter
             (concatenate 'string
                          (first strings)
                          delimiter
                          (second strings))
             (cddr strings))))

The more idiomatic way is to use reduce, but this expands to almost exactly the same instructions as the above:

(defun join (delimiter &rest strings)
  (reduce (lambda (a b)
            (concatenate 'string a delimiter b))
          strings))
Svante
  • 50,694
  • 11
  • 78
  • 122
3

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

vartec
  • 131,205
  • 36
  • 218
  • 244
3

The problem is that computer languages rarely have string booleans, that is, methods that are of type string that do anything useful. SQL Server at least has is[not]null and nullif, which when combined solve the delimiter problem, by the way: isnotnull(nullif(columnvalue, ""),"," + columnvalue))

The problem is that in languages there are booleans, and there are strings, and never the twain shall meet except in ugly coding forms, e.g.

concatstring = string1 + "," + string2; if (fubar) concatstring += string3 concatstring += string4 etc

I've tried mightily to avoid all this ugliness, playing comma games and concatenating with joins, but I'm still left with some of it, including SQL Server errors when I've missed one of the commas and a variable is empty.

Jonathan

1

In Java 8 we can use:

List<String> list = Arrays.asList(new String[] { "a", "b", "c" });
System.out.println(String.join(",", list)); //Output: a,b,c

To have a prefix and suffix we can do

StringJoiner joiner = new StringJoiner(",", "{", "}");
list.forEach(x -> joiner.add(x));
System.out.println(joiner.toString()); //Output: {a,b,c}

Prior to Java 8 you can do like Jon's answer

StringBuilder sb = new StringBuilder(prefix);
boolean and = false;
for (E e : iterable) {        
    if (and) {
        sb.append(delimiter);
    }
    sb.append(e);
    and = true;
}
sb.append(suffix);
gladiator
  • 722
  • 1
  • 9
  • 16
1

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
  • 307,395
  • 66
  • 306
  • 293
  • 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 Feb 24 '09 at 12:52
  • 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 Feb 24 '09 at 13:05
  • 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 Feb 24 '09 at 22:27
  • 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 Feb 24 '09 at 22:29
  • 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 Feb 25 '09 at 09:29
1

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
  • 1,693
  • 2
  • 11
  • 14
1

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.

Ray Lu
  • 26,208
  • 12
  • 60
  • 59
1

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".

Federico Zancan
  • 4,846
  • 4
  • 44
  • 60
1

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
  • 639
  • 1
  • 8
  • 14
0

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

user159313
  • 51
  • 1
  • 1
0

Java (from Jon's solution):

    StringBuilder sb = new StringBuilder();
    String delimiter = "";
    for (String item : items) {
        sb.append(delimiter).append(item);
        delimeter = ", ";
    }
    return sb.toString();
ceklock
  • 6,143
  • 10
  • 56
  • 78
0
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
  • 94,805
  • 45
  • 217
  • 260
0

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
  • 10,189
  • 5
  • 27
  • 31
0

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
  • 533
  • 2
  • 5
0

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
  • 958
  • 3
  • 13
  • 22
0

Here is my humble try;

public static string JoinWithDelimiter(List<string> words, string delimiter){
    string joinedString = "";
    if (words.Count() > 0)
    {
        joinedString = words[0] + delimiter;
        for (var i = 0; i < words.Count(); i++){
            if (i > 0 && i < words.Count()){
                if (joinedString.Length > 0)
                {
                    joinedString += delimiter + words[i] + delimiter;
                } else {
                    joinedString += words[i] + delimiter;
                }
            }
        }
    }
    return joinedString;
}

Usage;

List<string> words = new List<string>(){"my", "name", "is", "Hari"};
Console.WriteLine(JoinWithDelimiter(words, " "));
Hari
  • 25
  • 7
0

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, ", ");
Community
  • 1
  • 1
Ethan Heilman
  • 16,347
  • 11
  • 61
  • 88
0

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)