Another Linqification question - c#

Say I have a List of objects, and the object has a string property. I want to get a single comma-separated list of the value of each string property of each object in the list.
Here's 1 way to do it (sans linq)
StringBuilder result = new StringBuilder()
foreach(myObject obj in myList)
{
result.Append(obj.TheString);
result.Append(", ");
}
// then trim the trailing ", " and call ToString() on result, etc, etc...
Here's my first shot at linqification. Is there a better way?
string result = string.Join(", ", myList.Select(myObj => myObj.TheString).ToArray());
That's one line of code, but it doesn't look very efficient to me -- iterate the list just to build an array, just to iterate the array and build a string... whew!
Is there a better way?

If you want efficient, use Enumerable.Aggregate with StringBuilder:
string result = myList.Aggregate(new StringBuilder(),
(sb, o) => sb.Append(o.TheString).Append(", "))
.ToString();
The original problem is that String.Join wants an array. In .NET 4, there will be an overload that takes IEnumerable<string> (and I expect it will be implemented like above).

I like this extension method for joining strings. It's basically the same technique you are using, but wrapped in an extension method. I wouldn't recommend it for large sets since efficiency was not the goal. The benefit to me is expressiveness (very linqy) and convenient for small sets:
[Test]
public void Should_make_comma_delimited_list()
{
var colors = new List<HasColor>
{
new HasColor { Color = "red" },
new HasColor { Color = "green" },
new HasColor { Color = "blue" }
};
var result = colors.Implode(x => x.Color, ", ");
Assert.That(result, Is.EqualTo("red, green, blue"));
}
public class HasColor
{
public string Color { get; set; }
}
public static class LinqExtensions
{
public static string Implode<T>(this IEnumerable<T> list, Func<T, string> func, string separator)
{
return string.Join(separator, list.Select(func).ToArray());
}
}

Use string.Join, it's good enough.
Optimize when profiler will tell you to do so.
Readability of the version with StringBuilder is poor and you are still getting your trailing comma.

Here's another way (result is a StringBuilder):
myList.ForEach(
myObj =>
{
if (result.Length != 0)
result.Append(",");
result.Append(myObj.TheString);
}
);

Related

Best way of concatenating one value of objects by comma separation? C#

I found a way to do this but I just wonder if there is actually a straight way to do it.
So I have list of Artist objects and I would like to print out all the artists name as one string with comma separated.
here is my code to achieve this
string.Join(",",Artists.Select(a => a.ArtistName).ToArray());
Or I should be using ToString method?
From .NET framework version 4 onwards, this should be enough
string s = string.Join(",",Artists.Select(a => a.ArtistName));
It depends upon what one means by "best".
Generally, the way you have it is best prior to .NET 4.0, while from 4.0 on the best way is to leave out the .ToArray() because from that version on there is a form of Join that takes an IEnumerable<string> and hence you don't need to spend time and memory creating an array.
Now, if we are working prior to .NET 4.0, we can create our own version of those added with that version:
public static class StringMore
{
//You might want to use conditional statements to mark this as obsolete in 4.0 or higher
public static string Join(string separator, IEnumerable<string> values)
{
if (values == null)
throw new ArgumentNullException("values");
if (separator == null)
separator = string.Empty;
using (IEnumerator<string> en = values.GetEnumerator())
{
if (!en.MoveNext())
return "";
StringBuilder sb = new StringBuilder(en.Current);
while(en.MoveNext())
{
stringBuilder.Append(separator);
stringBuilder.Append(en.Current);
}
return sb.ToString();
}
}
public static string Join<T>(string separator, IEnumerable<T> values)
{
if (values == null)
throw new ArgumentNullException("values");
if (separator == null)
separator = string.Empty;
using (IEnumerator<string> en = values.GetEnumerator())
{
if (!en.MoveNext())
return "";
T cur = en.Current;
StringBuilder sb = cur == null ? new StringBuilder() : new StringBuilder(cur.ToString());
while(en.MoveNext())
{
stringBuilder.Append(separator);
cur = en.Current;
if(cur != null)
stringBuilder.Append(cur.ToString());
}
return sb.ToString();
}
}
}
Then you could use StringMore.Join(",",Artists.Select(a => a.ArtistName)) to get results in .NET 3.5 that are almost (not quite as we lack some caching of StringBuilders that .NET does internally) as efficient as 4.0. Whether this is "better" or not depends on whether the performance gain is worth the extra work and extra complexity to bug-checking of adding more methods, which depends on how heavy the calls are in practice (how many elements) and how often they are hit.
Your solution is the correct way. You "could" override the ToString method of your object Artists, but string.Join is the preferred way.
separateor could be ","
public static string Join<T>(this IEnumerable<T> items, string separator)
{
return items.Join(separator, i => i.ToString());
}
I'd prefer to create my own method here, in order to make my code "look better". So I would make an extension to the list of artists.
public static class ArtistListExtensions
{
public static String unionNames(this List<Artist> artists, String seperator)
{
return string.Join(seperator, artists.Select(a => a.ArtistName));
}
}
I would now simply use this extension as such:
artists.unionNames(", ");

Compare a string to multiple strings

Is there a way to compare two strings to another string at once?
Example:
string1 == string2 && string3;
(I know this isn't the way, just a representation of what I mean)
Generally, no, there is no way to do this that resembles the way you asked to do it.
However, if the strings to compare with are in a collection of some kind, you can do this:
if (stringCollection.All(s => s == string1) )
If you don't want to deal with putting your values into a collection or list, you could do an extension method like this:
static class extensions
{
private static bool InSet(this string s, params string[] p )
{
return p.Contains(s);
}
}
Then when you want to test if a string matches a value in a set you can do this:
if (String1.InSet(String2, String3))
{
// do something.
}
Besides Linq's All method, you can do also with List methods, like TrueForAll
string searchString = "hi";
var listStrings = new List<string>() { "hi", "there" };
bool allOfThem = listStrings.TrueForAll(x => x == searchString);
bool atLeastOne = listStrings.Contains(searchString);

StreamInsight -- Convert a string csv PointEvent to multiple int PointEvent's

I can do it in a UDSO :
public sealed class PromoIdsToEvents : CepPointStreamOperator<string, int>
{
public override IEnumerable<int> ProcessEvent(PointEvent<string> inputEvent)
{
if (inputEvent.Payload.Length > 0)
{
string[] separator = new[] { "," };
string[] idStrings = inputEvent.Payload.Split(separator, StringSplitOptions.RemoveEmptyEntries);
foreach (var p in idStrings)
{
yield return int.Parse(p);
}
}
}
public override bool IsEmpty
{
get { return false; }
}
}
Is it possible to do it all in a query? All this does is make an IEnumberable :
var multipleEventsAsInts = from c in csvEvents
let split = c.Split(',').Select(int.Parse)
select split;
You might be able to do this work in a subject, but I think the way you are doing it, through a UDSO is the proper way to work with a StreamInsight event in a procedural manner.
By creating that UDSO, you now have a reusable chunk of code. If you just did that work in the query logic, you wouldn't have the ability to re-use it as easily.
TXPower is absolutely correct. Additionally, the UDSO will be more efficient than using a UDF (static method) in a query.
However, your sample UDSO could be a bit better ... note that the return value from ProcessEvent is an IEnumerable. You don't need to yield return one at a time; create your enumerable (an array would be fine) and then simply return it.

C#/LINQ: Concatenating strings

Is there a better—more functional, succinct, or elegant—way to write this? A reduce/fold function, perhaps?
var key = String.Join(String.Empty,
new[] {
keyRoot,
controllerName,
actionName
}.Concat(
from param in params
select param.Key + param.Value
)
);
The input is a few variables that are strings, as well as an enumerable of concatenated keys/values from a Dictionary<string, string>.
The output should be all of these strings concatenated.
It sounds like you could use the LINQ Aggregate function:
Using LINQ to concatenate strings
More readable to me would be something like this:
string key = string.Format("{0}{1}{2}{3}",
keyRoot,
controllerName,
actionName,
string.Join(string.Empty, parameters.Select( p => p.Key + p.Value)));
This might not be as "functional" but certainly as succinct and clear as I can come up with.
This doesn't improve it much...
var key = string.Concat(
new[] {
keyRoot,
controllerName,
actionName
}.Concat(
params.Select(kvp) => param.Key + param.Value)
).ToArray ()
);
This is 2 lines shorter if it doesn't have to be a single statement.
var list = new List<String> {
keyRoot,
controllerName,
actionName
};
list.AddRange (params.Select(kvp) => param.Key + param.Value));
var key = string.Concat(list.ToArray ());
I think for concatenating the sequence of all strings, your construct is already as functional as you can get.
Instead of using String.Join with an empty string, I'd probably use a StringBuilder together with a ForEach extenstion method like
public static class MyExtensions {
public static void ForEach(this IEnumerable<T> enumerable, Action<T> action) {
foreach (var entry in enumerable)
action(entry);
}
}
I also would define a local variable for the sequence like
var seq = new[] {
keyRoot,
controllerName,
actionName
}.Concat(
from param in params select param.Key + param.Value
);
var sb = new StringBuilder();
seq.ForEach(s=>sb.Append(s));
Of course, using the Aggregate function would be more "functional", but it is not more readable in my opinion plus it has performance penalties, because you need to construct intermediate strings...
With an extension to StringBuilder:
public static class StringBuilderExtensions {
public static StringBuilder AppendAll(this StringBuilder builder, IEnumerable<string> strings) {
foreach (string s in strings) builder.Append(s);
return builder;
}
}
it gets rather short and efficient:
string key =
new StringBuilder()
.Append(keyRoot)
.Append(controllerName)
.Append(actionName)
.AppendAll(parameters.Select(p => p.Key + p.Value))
.ToString();
This will build the string without creating any intermediate arrays.
One thing to improve would be to avoid the intermittent strings p.Key + p.Value by adding the key and value directly to the StringBuilder, but then the code gets less reusable.
Another thing to improve would be to set the capacity of the StringBuilder, but then you would need to loop though the dictionary and add upp the length of the strings first.
(Note: I used parameters for the name of the dictionary instead of params, as that is a keyword.)
Here is a solution in one expressions using Aggregate (effectively a fold):
var key = params.Aggregate(new StringBuilder()
.Append(keyRoot)
.Append(controllerName)
.Append(actionName),
(sb, p) => sb.Append(p.Key).Append(p.Value))
.ToString();

Recursively execute funcs in list

Given a list of Func<string, string>, is it possible to write a statement that iterates through the list and returns the result like so:
string result = f1(f2(f..(input));
I have the following code (that works), but I'm not satisfied with the temporary variable.
public static string WrapEachElementWith<T>
( this IEnumerable<T> target,
params Func<string, string>[] func )
{
string result = string.Empty;
target.Each(s =>
{
var tmp = s.ToString();
func.Reverse().Each(x => tmp = x(tmp));
result += tmp;
});
return result;
}
How to simplify / refactor?
UPDATE:
I should have provided more background. I'm playing around with Functional programming in c# after seeing higher order JavaScript session and John's abusive c# session at Oredev.
The aim is to generate html.
var TABLE = WrapWith("TABLE");
var TR = WrapWith("TR");
var TD = WrapWith("TD");
const string expected = "<TABLE><TR><TD>1</TD></TR><TR><TD>2</TD></TR></TABLE>";
var result = TABLE(stringArray.WrapEachWith(TR, TD));
result.ShouldEqual(expected);
static Func<String, String> WrapWith(string element)
{
var startTag = '<' + element + '>';
var endTag = "</" + element + '>';
return s => startTag + s + endTag;
}
It looks to me like you're doing four things:
Converting each item to a string
Applying the functions in turn
Applying that composite function to each string in a sequence
Joining the results together (inefficiently)
I would separate out those four aspects - in particular, string.Join works well enough for the fourth part, and Enumerable.Select does the third one.
I would also avoid reversing the order of the operations - I would expect the first operation I specify to be the first one applied, personally.
So, I would rewrite this method to return a Func<string, string> which could then be used with Select and Join. For example:
public static Func<string, string> Compose(params Func<string, string> funcs)
{
return input => {
string current = input;
foreach (var func in funcs)
{
current = func(current);
}
return current;
};
}
You could, of course, make this generic itself, with a signature of:
public static Func<T, T> Compose(params Func<T, T> funcs)
You would then call it with something like:
var composite = Compose<string>(FirstFunction, SecondFunction, ThirdFunction);
var query = string.Join("", items.Select(x => x.ToString())
.Select(composite));
public static string WrapEachElementWith
( string input,
params Func<string, string>[] func )
{
foreach (var f in func.Reverse())
input = f(input);
return input;
}
Not sure why you need template parameter, all the functions map string to string, right?
Note that there's no Each extension of IEnumerable, so you'll have to resort to foreach or write your own Each.
Edit:
your code actually applies this function to all the values from the list, so the actual code would be something like:
public static string F<T>
( this IEnumerable<T> target,
params Func<string, string>[] func )
{
target.Select(item => WrapEachElementWith(item.ToString(), func))
.Aggregate((sum, cur) => sum + cur);
}
As #Jon already mentioned, summing up this way is pretty inefficient, therefore you perhaps would like to put it this way:
string.Join("", target.Select(
item => WrapEachElementWith(item.ToString(), func)));
This guy wrote an entire ray tracer using LINQ. I haven't looked at his code that closely, but he describes using a technique called a "Y-combinator" to create recursion in a LINQ statement. He references this blog posting, which gives a detailed description of these recursive lambda expressions.
I don't know if that's quite what you're looking for, but it might get you off on the right footing.

Categories