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();
Related
I have a list that I traverse as follows:
foreach (var obj in mylist)
{
return += obj.Value.ToString() + ";";
}
This doesn't seem to work. Do I need a StringBuilder?
You could just use string.Join
Concatenates the elements of a specified array or the members of a
collection, using the specified separator between each element or
member.
and Enumerable.Select
Projects each element of a sequence into a new form.
return string.Join(";", mylist.Select(x => x.Value));
Your solution is not working because you are concatenating your string with return which is keyword in C#.
Initialize StringBuilder append your value to it with semicolon.
Like,
StringBuilder sb = new StringBuilder();
foreach (var obj in mylist)
{
sb.AppendFormat("{0};", obj.Value);
}
return sb.ToString();
Elegant way is to use string.Join() where you pass string with an IEnumerable #TheGeneral gave you solution to use string.Join() i.e.
var result = string.Join(";", mylist.Select(x => x.Value));
where ; is your separator and mylist.Select() will return IEnumerable of Values which will return same result as your first approach.
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);
Say that for debugging purposes, I want to quickly get the contents of an IEnumerable into one-line string with each string item comma-separated. I can do it in a helper method with a foreach loop, but that's neither fun nor brief. Can Linq be used? Some other short-ish way?
using System;
using System.Collections.Generic;
using System.Linq;
class C
{
public static void Main()
{
var a = new []{
"First", "Second", "Third"
};
System.Console.Write(string.Join(",", a));
}
}
string output = String.Join(",", yourEnumerable);
String.Join Method (String, IEnumerable
Concatenates the members of a constructed IEnumerable collection of
type String, using the specified separator between each member.
collection.Aggregate("", (str, obj) => str + obj.ToString() + ",");
(a) Set up the IEnumerable:
// In this case we are using a list. You can also use an array etc..
List<string> items = new List<string>() { "WA01", "WA02", "WA03", "WA04", "WA01" };
(b) Join the IEnumerable Together into a string:
// Now let us join them all together:
string commaSeparatedString = String.Join(", ", items);
// This is the expected result: "WA01, WA02, WA03, WA04, WA01"
(c) For Debugging Purposes:
Console.WriteLine(commaSeparatedString);
Console.ReadLine();
IEnumerable<string> foo =
var result = string.Join( ",", foo );
to join large array of strings to a string, do not directly use +, use StringBuilder to iterate one by one, or String.Join in one shot.
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.
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);
}
);