List to String

  • Thread starter Thread starter shapper
  • Start date Start date
S

shapper

Hello,

I have a List<Subject> Subjects where each subject has an Id and a
Name.
I want to create a String from that list as follows:

"Subject1, Subject2 and Subject3" or "Subject1" if only one exists.

I tried the following
:
String subjects = String.Join(", ", source.Select(s =>
s.Name).ToArray());
Int32 index = subjects.LastIndexOf(",");
subjects.Remove(index).Insert(index, " and ");
return subjects;

The "and" is being added but the last subject removed. What is the
correct way to do this?

And if there is only one subject I get an error ...
.... Well I think I can solve this by using:

if (source.Count == 1)
return source.FirstOrDefault().Name.ToString();

I am not sure if this is the best way ...

In fact, source can be empty or null ...

Thanks,
Miguel
 
shapper said:
Hello,

I have a List<Subject> Subjects where each subject has an Id and a
Name.
I want to create a String from that list as follows:

"Subject1, Subject2 and Subject3" or "Subject1" if only one exists.

I tried the following
:
String subjects = String.Join(", ", source.Select(s =>
s.Name).ToArray());
Int32 index = subjects.LastIndexOf(",");
subjects.Remove(index).Insert(index, " and ");
return subjects;

The "and" is being added but the last subject removed. What is the
correct way to do this?

And if there is only one subject I get an error ...
... Well I think I can solve this by using:

if (source.Count == 1)
return source.FirstOrDefault().Name.ToString();

I am not sure if this is the best way ...

In fact, source can be empty or null ...

Thanks,
Miguel

I have a method lying around for that:

public static string Join(string[] items, string separator, string
lastSeparator) {
int len = separator.Length * (items.Length - 2) + lastSeparator.Length;
foreach (string s in items) len += s.Length;
StringBuilder builder = new StringBuilder(len);
for (int i = 0; i < items.Length; i++) {
builder.Append(items);
switch (items.Length - i) {
case 1: break;
case 2: builder.Append(lastSeparator); break;
default: builder.Append(separator); break;
}
}
return builder.ToString();
}

Just feed it an array of subjects:

string subjects = Join(source.Select(s => s.Name).ToArray(), ", ", " and ");
 
Göran Andersson said:
shapper said:
Hello,

I have a List<Subject> Subjects where each subject has an Id and a
Name.
I want to create a String from that list as follows:

"Subject1, Subject2 and Subject3" or "Subject1" if only one exists.

I tried the following
:
String subjects = String.Join(", ", source.Select(s =>
s.Name).ToArray());
Int32 index = subjects.LastIndexOf(",");
subjects.Remove(index).Insert(index, " and ");
return subjects;

The "and" is being added but the last subject removed. What is the
correct way to do this?

And if there is only one subject I get an error ...
... Well I think I can solve this by using:

if (source.Count == 1)
return source.FirstOrDefault().Name.ToString();

I am not sure if this is the best way ...

In fact, source can be empty or null ...

Thanks,
Miguel

I have a method lying around for that:

public static string Join(string[] items, string separator, string
lastSeparator) {
int len = separator.Length * (items.Length - 2) + lastSeparator.Length;
foreach (string s in items) len += s.Length;
StringBuilder builder = new StringBuilder(len);
for (int i = 0; i < items.Length; i++) {
builder.Append(items);
switch (items.Length - i) {
case 1: break;
case 2: builder.Append(lastSeparator); break;
default: builder.Append(separator); break;
}
}
return builder.ToString();
}

Just feed it an array of subjects:

string subjects = Join(source.Select(s => s.Name).ToArray(), ", ", " and
");


Note: If the case where there is only one item in the list is common,
you should just check for that first in the method so that you can
simply return that string instead of going through the string builder.
 
I'm suprised your code works at all. Strings are immutable.

If you are calling this a lot you would be better implementing your own
function using a stringbuilder.

The below works for me.
List<string> source = new List<string>();

source.Add("Subject1");

source.Add("Subject2");

source.Add("Subject3");

String subjects = String.Join(",", source.ToArray());



Int32 index = subjects.LastIndexOf(",");

if (index > 0)

{

subjects = subjects.Insert(index + 1, " and ");

subjects = subjects.Remove(index, 1);

}





String.Join

Please, see my code ... I am already using String.Join ...
 
shapper said:
I have a List<Subject> Subjects where each subject has an Id and a
Name.
I want to create a String from that list as follows:
"Subject1, Subject2 and Subject3" or "Subject1" if only one exists.
I tried the following
:
      String subjects = String.Join(", ", source.Select(s =>
s.Name).ToArray());
      Int32 index = subjects.LastIndexOf(",");
      subjects.Remove(index).Insert(index, " and ");
      return subjects;
The "and" is being added but the last subject removed. What is the
correct way to do this?
And if there is only one subject I get an error ...
... Well I think I can solve this by using:
      if (source.Count == 1)
        return source.FirstOrDefault().Name.ToString();
I am not sure if this is the best way ...
In fact, source can be empty or null ...
Thanks,
Miguel

I have a method lying around for that:

public static string Join(string[] items, string separator, string
lastSeparator) {
        int len = separator.Length * (items.Length - 2) + lastSeparator.Length;
        foreach (string s in items) len += s.Length;
        StringBuilder builder = new StringBuilder(len);
        for (int i = 0; i < items.Length; i++) {
                builder.Append(items);
                switch (items.Length - i) {
                        case 1: break;
                        case 2: builder.Append(lastSeparator); break;
                        default: builder.Append(separator); break;
                }
        }
        return builder.ToString();

}

Just feed it an array of subjects:

string subjects = Join(source.Select(s => s.Name).ToArray(), ", ", " and ");


Hi,

I found a really interesting blog post about this:
http://blogs.msdn.com/ericlippert/archive/2009/04/15/comma-quibbling.aspx

What do you think?

I am reading all the code examples ... Uff ... I was paying more
attention to these two:

public static string Quibble(this IEnumerable<string> items)
{
var backwards = new LinkedList<string>();
var forwards = new LinkedList<string>();
foreach (var s in items)
backwards.AddFirst(s);
// "}" is always the last character in the result string.
push it

forwards.AddFirst("}");
// Initially no counter was used. However the choice
between " and " and ", ",
// and when to pop the final seperator was based on
unintuative comparisons
// to stack.Count so a counter was introduced.

var count = 0;
foreach (string s in backwards)
{
forwards.AddFirst(s);
// push " and " the first time, ", " every other time.
forwards.AddFirst(count == 0 ? " and " : ", ");
count++;
}

// if we've pushed at least one pair of items, the top of
the stack
// is a garbage seperator. Pop it.

if (count > 0)
forwards.RemoveFirst();
// The first item is always a "{". Push it.
forwards.AddFirst("{");

StringBuilder sb = new StringBuilder();
foreach (string s in forwards)
sb.Append(s);
return sb.ToString();
}

And:

static string buildString(IEnumerable<string> mlist) {

var count = mlist.Count();
var res = mlist.Select((mitem, index) => new { str =
mitem + ((count - 1) == index ? "" : ( (count - 2) == index ? " and
" : "," )) });
return res.Aggregate(new StringBuilder("{"), (builder,
pitem) => builder.Append(pitem.str)).Append("}").ToString();
}

Anyway, did you ever used or advice any of the code examples on that
post?

These two caught my atention as they use StringBuilder ...
.... And the first a for loop (Because Pete says to me often to use
Loops and not always Lambda expressions)

Thanks,
Miguel
 
I have a method lying around for that:

public static string Join(string[] items, string separator, string
lastSeparator) {
        int len = separator.Length * (items.Length - 2) + lastSeparator.Length;
        foreach (string s in items) len += s.Length;
        StringBuilder builder = new StringBuilder(len);
        for (int i = 0; i < items.Length; i++) {
                builder.Append(items);
                switch (items.Length - i) {
                        case 1: break;
                        case 2: builder.Append(lastSeparator); break;
                        default: builder.Append(separator); break;
                }
        }
        return builder.ToString();

}

Just feed it an array of subjects:

string subjects = Join(source.Select(s => s.Name).ToArray(), ", ", " and ");


Thank You Goran for the code.

In fact I was looking at a lot of code examples for that to ... Check
my previous post.
You might consider it interesting ...
 
Paul said:
I'm suprised your code works at all. Strings are immutable.

The posted code does of course not work. He must have forgotten
something when pasting it...
If you are calling this a lot you would be better implementing your own
function using a stringbuilder.

The below works for me.
List<string> source = new List<string>();

source.Add("Subject1");

source.Add("Subject2");

source.Add("Subject3");

String subjects = String.Join(",", source.ToArray());



Int32 index = subjects.LastIndexOf(",");

if (index > 0)

{

subjects = subjects.Insert(index + 1, " and ");

subjects = subjects.Remove(index, 1);

}

However, if the last item contains a comma, it replaces that comma
instead...
 
shapper said:
Hello,

I have a List<Subject> Subjects where each subject has an Id and a
Name.
I want to create a String from that list as follows:

"Subject1, Subject2 and Subject3" or "Subject1" if only one exists.

I tried the following
:
String subjects = String.Join(", ", source.Select(s =>
s.Name).ToArray());
Int32 index = subjects.LastIndexOf(",");
subjects.Remove(index).Insert(index, " and ");
return subjects;

The "and" is being added but the last subject removed. What is the
correct way to do this?

And if there is only one subject I get an error ...
.... Well I think I can solve this by using:

if (source.Count == 1)
return source.FirstOrDefault().Name.ToString();

I am not sure if this is the best way ...

In fact, source can be empty or null ...

Thanks,
Miguel

Hi, (just can't resist the temptation)

static string FormatOutput1(ICollection<Subject> source)
{
if (source == null || source.Count < 1) return String.Empty;

var nonEmptyNames = source.Where(x => x != null &&
!String.IsNullOrEmpty(x.Name)).ToList();
var formattedNames = nonEmptyNames.Select((x, index) => index < 1 ?
x.Name : (index == nonEmptyNames.Count - 1 ? " and " : ", ") + x.Name);

var sb = new StringBuilder();

foreach (var item in formattedNames) sb.Append(item);

return sb.ToString();
}

It handles below condition
- list is null or empty
- Subject is null
- Subject.Name is null or empty

Regards.
 
Paul said:
I'm suprised your code works at all. Strings are immutable.

If you are calling this a lot you would be better implementing your own
function using a stringbuilder.
Hi Paul,

I did a timing and found that your method is quite simple and reasonably
fast. Upon examining through the Reflector, String.Join is using pointer
to speed up the operation.
 
shapper said:
shapper said:
Hello,
I have a List<Subject> Subjects where each subject has an Id and a
Name.
I want to create a String from that list as follows:
"Subject1, Subject2 and Subject3" or "Subject1" if only one exists.
I tried the following
:
String subjects = String.Join(", ", source.Select(s =>
s.Name).ToArray());
Int32 index = subjects.LastIndexOf(",");
subjects.Remove(index).Insert(index, " and ");
return subjects;
The "and" is being added but the last subject removed. What is the
correct way to do this?
And if there is only one subject I get an error ...
... Well I think I can solve this by using:
if (source.Count == 1)
return source.FirstOrDefault().Name.ToString();
I am not sure if this is the best way ...
In fact, source can be empty or null ...
Thanks,
Miguel
I have a method lying around for that:

public static string Join(string[] items, string separator, string
lastSeparator) {
int len = separator.Length * (items.Length - 2) + lastSeparator.Length;
foreach (string s in items) len += s.Length;
StringBuilder builder = new StringBuilder(len);
for (int i = 0; i < items.Length; i++) {
builder.Append(items);
switch (items.Length - i) {
case 1: break;
case 2: builder.Append(lastSeparator); break;
default: builder.Append(separator); break;
}
}
return builder.ToString();

}

Just feed it an array of subjects:

string subjects = Join(source.Select(s => s.Name).ToArray(), ", ", " and ");


Hi,

I found a really interesting blog post about this:
http://blogs.msdn.com/ericlippert/archive/2009/04/15/comma-quibbling.aspx

What do you think?

I am reading all the code examples ... Uff ... I was paying more
attention to these two:

public static string Quibble(this IEnumerable<string> items)
{
var backwards = new LinkedList<string>();
var forwards = new LinkedList<string>();
foreach (var s in items)
backwards.AddFirst(s);
// "}" is always the last character in the result string.
push it

forwards.AddFirst("}");
// Initially no counter was used. However the choice
between " and " and ", ",
// and when to pop the final seperator was based on
unintuative comparisons
// to stack.Count so a counter was introduced.

var count = 0;
foreach (string s in backwards)
{
forwards.AddFirst(s);
// push " and " the first time, ", " every other time.
forwards.AddFirst(count == 0 ? " and " : ", ");
count++;
}

// if we've pushed at least one pair of items, the top of
the stack
// is a garbage seperator. Pop it.

if (count > 0)
forwards.RemoveFirst();
// The first item is always a "{". Push it.
forwards.AddFirst("{");

StringBuilder sb = new StringBuilder();
foreach (string s in forwards)
sb.Append(s);
return sb.ToString();
}

And:

static string buildString(IEnumerable<string> mlist) {

var count = mlist.Count();
var res = mlist.Select((mitem, index) => new { str =
mitem + ((count - 1) == index ? "" : ( (count - 2) == index ? " and
" : "," )) });
return res.Aggregate(new StringBuilder("{"), (builder,
pitem) => builder.Append(pitem.str)).Append("}").ToString();
}

Anyway, did you ever used or advice any of the code examples on that
post?


1. LinkList is not a correct data structure for this problem.
2. Linq is okay, but their performance cannot beat a simple loop

Overall, I found that Goran's method is the fastest on my system.
The compiler/CLR optimize the switch statement and make the code faster.
 
Back
Top