Iterating over IEnumerable, special casing last element - c#

I'm building a string based on an IEnumerable, and doing something like this:
public string BuildString()
{
var enumerable = GetEnumerableFromSomewhere(); // actually an in parameter,
// but this way you don't have to care
// about the type :)
var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();
stringBuilder.Append("This is it: ");
foreach(var part in interestingParts)
{
stringBuilder.AppendPart(part);
if (part != interestingParts.Last())
{
stringBuilder.Append(", ");
}
}
}
private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
stringBuilder.Append("[");
stringBuilder.Append(part.Something");
stringBuilder.Append("]");
if (someCondition(part))
{
// this is in reality done in another extension method,
// similar to the else clause
stringBuilder.Append(" = #");
stringBuilder.Append(part.SomethingElse");
}
else
{
// this is also an extension method, similar to this one
// it casts the part to an IEnumerable, and iterates over
// it in much the same way as the outer method.
stringBuilder.AppendInFilter(part);
}
}
I'm not entirely happy with this idiom, but I'm struggling to formulate something more succinct.
This is, of course, part of a larger string building operation (where there are several blocks similar to this one, as well as other stuff in between) - otherwise I'd probably drop the StringBuilder and use string.Join(", ", ...) directly.
My closest attempt at simplifying the above, though, is constructs like this for each iterator:
stringBuilder.Append(string.Join(", ", propertyNames.Select(prop => "[" + prop + "]")));
but here I'm still concatenating strings with +, which makes it feel like the StringBuilder doesn't really contribute much.
How could I simplify this code, while keeping it efficient?

You can replace this:
string.Join(", ", propertyNames.Select(prop => "[" + prop + "]"))
With c# 6 string interpolation:
string.Join(", ", propertyNames.Select(prop => $"[{prop}]"))
In both cases the difference is semantic only and it doesn't really matter. String concatenation like in your case in the select isn't a problem. The compiler still creates only 1 new string for it (and not 4, one for each segment and a 4th for the joint string).
Putting it all together:
var result = string.Join(", ", enumerable.Select(v => $"[{v.TheInterestingStuff}]"));
Because body of foreach is more complex that to fit in a String Interpolation scope you can just remove the last N characters of the string once calculated, as KooKiz suggested.
string separator = ", ";
foreach(var part in interestingParts)
{
stringBuilder.Append("[");
stringBuilder.Append(part);
stringBuilder.Append("]");
if (someCondition(part))
{
// Append more stuff
}
else
{
// Append other thingd
}
stringBuilder.Append(separator);
}
stringBuilder.Length = stringBuilder.Lenth - separator;
In any case I think that for better encapsulation the content of the loop's scope should sit in a separate function that will receive a part and the separator and will return the output string. It can also be an extension method for StringBuilder as suggested by user734028

Use Aggregate extension method with StringBuilder.
Will be more efficiently then concatenate strings if your collection are big
var builder = new StringBuilder();
list.Aggregate(builder, (sb, person) =>
{
sb.Append(",");
sb.Append("[");
sb.Append(person.Name);
sb.Append("]");
return sb;
});
builder.Remove(0, 1); // Remove first comma
As pure foreach is always more efficient then LINQ then just change logic for delimeter comma
var builder = new StringBuilder();
foreach(var part in enumerable.Select(v => v.TheInterestingStuff))
{
builder.Append(", ");
builder.Append("[");
builder.Append(part);
builder.Append("]");
}
builder.Remove(0, 2); //Remove first comma and space

Aggregate solution:
var answer = interestingParts.Select(v => "[" + v + "]").Aggregate((a, b) => a + ", " + b);
Serialization solution:
var temp = JsonConvert.SerializeObject(interestingParts.Select(x => new[] { x }));
var answer = temp.Substring(1, temp.Length - 2).Replace(",", ", ");

the code:
public string BuildString()
{
var enumerable = GetEnumerableFromSomewhere();
var interestingParts = enumerable.Select(v => v.TheInterestingStuff).ToArray();
stringBuilder.Append("This is it: ");
foreach(var part in interestingParts)
{
stringBuilder.AppendPart(part)
}
if (stringBuilder.Length>0)
stringBuilder.Length--;
}
private static void AppendPart(this StringBuilder stringBuilder, InterestingPart part)
{
if (someCondition(part))
{
stringBuilder.Append(string.Format("[{0}] = #{0}", part.Something));
}
else
{
stringBuilder.Append(string.Format("[{0}]", part.Something));
stringBuilder.AppendInFilter(part); //
}
}
much better now IMO.
Now a little discussion on making it very fast. We can use Parallel.For. But you would think (if you would think) the Appends are all happening to a single shareable resource, aka the StringBuilder, and then you would have to lock it to Append to it, not so efficient! Well, if we can say that each iteration of the for loop in the outer function creates one single string artifact, then we can have a single array of string, allocated to the count of interestingParts before the Parallel for starts, and each index of the Parallel for would store its string to its respective index.
Something like:
string[] iteration_buckets = new string[interestingParts.Length];
System.Threading.Tasks.Parallel.For(0, interestingParts.Length,
(index) =>
{
iteration_buckets[index] = AppendPart(interestingParts[index]);
});
your function AppendPart will have to be adjusted to make it a non-extension to take just a string and return a string.
After the loop ends you can do a string.Join to get a string, which is what you may be doing with the stringBuilder.ToString() too.

Related

String small replacement [duplicate]

This question already has answers here:
Fastest way to trim a string and convert it to lower case
(6 answers)
Closed 6 years ago.
I am searching for a simple way to remove underscores from strings and replacing the next character with its upper case letter.
For example:
From: "data" to: "Data"
From: "data_first" to: "DataFirst"
From: "data_first_second" to: "DataFirstSecond"
Who needs more than one line of code?
var output = Regex.Replace(input, "(?:^|_)($|.)", m => m.Groups[1].Value.ToUpper());
This approach is known as a "finite-state machine" that iterates through the string - in that it has a finite set of states ("is the first letter of a word following an underscore" vs "character inside a word"). This represents the minimal instructions needed to perform the task. You can use a Regular Expression for the same effect, but it would generate at least the same number of instructions at runtime. Writing the code out manually guarantees a minimal runtime.
The advantage of this approach is sheer performance: there is no unnecessary allocation of intermediate strings being performed, and it iterates through the input string only once, giving a time complexity of O(n) and a space complexity of O(n). This cannot be improved upon.
public static String ConvertUnderscoreSeparatedStringToPascalCase(String input) {
Boolean isFirstLetter = true;
StringBuilder output = new StringBuilder( input.Length );
foreach(Char c in input) {
if( c == '_' ) {
isFirstLetter = true;
continue;
}
if( isFirstLetter ) {
output.Append( Char.ToUpper( c ) );
isFirstLetter = false;
}
else {
output.Append( c );
}
}
return output.ToString();
}
You can use String.Split and following LINQ query:
IEnumerable<string> newStrings = "data_first_second".Split('_')
.Select(t => new String(t.Select((c, index) => index == 0 ? Char.ToUpper(c) : c).ToArray()));
string result = String.Join("", newStrings);
All other answers valid... for a culture-aware way:
var textInfo = CultureInfo.CurrentCulture.TextInfo;
var modifiedString = textInfo.ToTitleCase(originalString).Replace("_","")
I've made a fiddle: https://dotnetfiddle.net/NAr5PP
I would do something like this:
string test = "data_first_second";
string[] testArray=test.Split('_');
StringBuilder modifiedString = new StringBuilder();
foreach (string t in testArray)
{
modifiedString.Append(t.First().ToString().ToUpper() + t.Substring(1));
}
test=modifiedString.toString();
Use LINQ and Split method like this:
var result = string.Join("",str.Split('_')
.Select(c => c.First().ToString()
.ToUpper() + String.Join("", c.Skip(1))));

Proper way in C# to combine an arbitrary number of strings into a single string

I breezed through the documentation for the string class and didn't see any good tools for combining an arbitrary number of strings into a single string. The best procedure I could come up with in my program is
string [] assetUrlPieces = { Server.MapPath("~/assets/"),
"organizationName/",
"categoryName/",
(Guid.NewGuid().ToString() + "/"),
(Path.GetFileNameWithoutExtension(file.FileName) + "/")
};
string assetUrl = combinedString(assetUrlPieces);
private string combinedString ( string [] pieces )
{
string alltogether = "";
foreach (string thispiece in pieces) alltogether += alltogether + thispiece;
return alltogether;
}
but that seems like too much code and too much inefficiency (from the string addition) and awkwardness.
If you want to insert a separator between values, string.Join is your friend. If you just want to concatenate the strings, then you can use string.Concat:
string assetUrl = string.Concat(assetUrlPieces);
That's marginally simpler (and possibly more efficient, but probably insignificantly) than calling string.Join with an empty separator.
As noted in comments, if you're actually building up the array at the same point in the code that you do the concatenation, and you don't need the array for anything else, just use concatenation directly:
string assetUrl = Server.MapPath("~/assets/") +
"organizationName/" +
"categoryName/" +
Guid.NewGuid() + "/" +
Path.GetFileNameWithoutExtension(file.FileName) + "/";
... or potentially use string.Format instead.
I prefer using string.Join:
var result = string.Join("", pieces);
You can read about string.Join on MSDN
You want a StringBuilder, I think.
var sb = new StringBuilder(pieces.Count());
foreach(var s in pieces) {
sb.Append(s);
}
return sb.ToString();
Update
#FiredFromAmazon.com: I think you'll want to go with the string.Concat solution offered by others for
Its sheer simplicity
Higher performance. Under the hood, it uses FillStringChecked, which does pointer copies, whereas string.Join uses StringBuilder. See http://referencesource.microsoft.com/#mscorlib/system/string.cs,1512. (Thank you to #Bas).
string.Concat is the most appropriate method for what you want.
var result = string.Concat(pieces);
Unless you want to put delimiters between the individual strings. Then you'd use string.Join
var result = string.Join(",", pieces); // comma delimited result.
A simple way to do this with a regular for loop:
(since you can use the indices, plus I like these loops better than foreach loops)
private string combinedString(string[] pieces)
{
string alltogether = "";
for (int index = 0; index <= pieces.Length - 1; index++) {
if (index != pieces.Length - 1) {
alltogether += string.Format("{0}/" pieces[index]);
}
}
return alltogether;

How do I put the contents of a list in a single MessageBox?

Basically, I have a list with multiple items in it and I want a single message box to display them all.
The closest I have got is a message box for each item (using foreach).
What I want is the equivalent of:
MessageBox.Show ("List contains:"+
Foreach (string str in list)
{ str + Environment.Newline + }
)
But obviously this won't work! What is the correct way of doing this?
You can join everything into a single string with string.Join:
var message = string.Join(Environment.NewLine, list);
MessageBox.Show(message);
However, if you don't have access to .NET 4, you don't have that overload that takes an IEnumerable<string>. You will have to fallback on the one that takes an array:
var message = string.Join(Environment.NewLine, list.ToArray());
MessageBox.Show(message);
If you have .Net 4.0
string s = String.Join(",", list);
If you don't but have 3.5
string s = String.Join(",", list.ToArray());
You can also use Stringbuilder:
StringBuilder builder = new StringBuilder();
foreach(string str in list)
{
builder.Append(str.ToString()).AppendLine();
}
Messagebox.Show(builder.ToString());
Regards
Just for fun and in case you need to do something like that with non-string collections one time - a LINQ version using Aggregate, which is the closest to your example syntax. Don't use it here, do indeed use String.Join in this case, but keep in mind that you have something in LINQ that can do what you need.
MessageBox.Show("List contains: " +
list.Aggregate((str,val) => str + Environment.NewLine + val);
EDIT: also, like Martinho Fernandes pointed out, it's better to use the StringBuilder class in cases like that, so:
MessageBox.Show("List contains: " + list.Aggregate(new StringBuilder(),
(sb,val) => sb.AppendLine(val),
sb => sb.ToString()));
simply you need to make a for loop for example:
string total ="";
for(int i =0; i<x.count ;i++)
{
total =total+x[i] +"\n";
}
MessageBox.Show(total);

Generating Comma Separated Values

Suppose I have a collection of strings:
"foo"
"bar"
"xyz"
And I would like to generate a comma separated values from the list into something like:
"foo, bar, xyz"
Notice the lack of ", " at the end.
I am aware that there are dozens of ways to generate this:
use for-loop and string.Format() or StringBuilder.
use integer counter and remove the ending ", " if the value > 0
don't put ", " on the first run
etc.
Sample code of what I have right now:
if (strs.Count() > 0)
{
var sb = new StringBuilder();
foreach (var str in strs)
sb.AppendFormat("{0}, ", str);
return sb.Remove(0, 2).ToString();
}
What is the best code that is highly reusable for the above scenario, and why?
You want to use the string.Join method, which exists in the BCL for this purpose.
Example:
var myArray = new string[] { "one", "two", "three" };
var output = string.Join(", ", myArray);
Or if you're using .NET 3.5, you can do this with any IEnumerable<string> as such:
var output = string.Join(", ", myEnumerable.ToArray());
(Note that this doesn't give the best performance as it requires, although it is clearly still 'O(n)', and should be suitable for almost all cases).
Now, if your enumerable is not of type string (generically an IEnumerable<T>), you can just use the Select method to convert the result into a string, e.g.
var output = string.Join(", ", myEnumerable.Select(e => e.ToString()).ToArray());
I'm not sure if you're dealing with values that may potentially contains commas in themselves, but this can be worked around by enclosing them in quotes (") and escaping the quotes, similarly to the CSV format.
var output = string.Join(", ", items.Select(x => x.Contains(",") ?
"\"" + x.Replace("\"", "\"\"") + "\"" : x);
Of course, splitting them back up again is a slightly triciker task, which requires a bit of regex.
String.Join is the right answer, but in the case of an IEnumerable, Linq is often shorter than a for loop:
someStringCollection.Aggregate((first, second) => first + ", " + second);
As others have said: String.Join is normally the best way to do this. But what if you have just have an IEnumerable rather than an array? Perhaps you have code to enumerate these as you read them from a file using deferred execution. In this case String.Join isn't quite as nice, because you have to loop over the strings twice — once to create the array and once to join it. In that case, you want something more like this:
public static string ToDelimitedString(this IEnumerable<string> source, string delimiter)
{
string d = "";
var result = new StringBuilder();
foreach( string s in source)
{
result.Append(d).Append(s);
d = delimiter;
}
return result.ToString();
}
This will perform almost as well as String.Join, and works in the more general case. Then, given a string array or any other IEnumerable you can call it like this:
string finalStr = MyStrings.ToDelimitedString(",");
string finalstr = String.Join(",", strs);
Use
string s = string.Join (",", new string[] {"aaa", "bbb", "ccc"});
Use String.Join
If you have an array of strings, go with Noldorin's solution.
But if it's some other collection type, I might do this:
if (strs.Count() > 0)
{
var sb = new StringBuilder();
foreach (var str in strs)
sb.AppendFormat("{0} {1}", (0 == sb.Length ? "" : ","), str);
return sb.ToString();
}

C# IEnumerable<Object> to string

For logging purposes, I would like to call the .ToString() method of every object on an object[] array. How can I do this in the simplest way?
Say I have :
myArray = new Object[]{"astring",1, Customer}
Log(????);
How can I pass a string such as its value is equal to:
"astring".ToString()+1.ToString()+Customer.ToString()
Or better, with comma between each value.
Like this:
Log(String.Join(", ", myArray.Select(o => o.ToString()).ToArray()));
Update:
From framework 4 the Join method can also take an IEnumerable<string>, so you don't need the ToArray:
Log(String.Join(", ", myArray.Select(o => o.ToString())));
MoreLINQ has a ToDelimitedString method for precisely this purpose.
It uses a StringBuilder rather than using String.Join (from what I remember from previous questions, the efficiency of the two approaches depends heavily on what the input is) but it's simple enough. Here's the core code (there are a couple of wrappers to allow a default delimiter):
private static string ToDelimitedStringImpl<TSource>
(IEnumerable<TSource> source, string delimiter)
{
Debug.Assert(source != null);
Debug.Assert(delimiter != null);
var sb = new StringBuilder();
foreach (var value in source)
{
if (sb.Length > 0) sb.Append(delimiter);
sb.Append(value);
}
return sb.ToString();
}
I regularly use...
String.Join(", ", Array.ConvertAll<object, string>(myArray, Convert.ToString))
A simple old fashion way :
string myString = "";
foreach(Object o in myArray)
myString += o.ToString() + ", ";
// Remove the extra comma
if(myString.Length >=2)
myString.Remove(myString.Length - 2);

Categories