Any suggest to store a value in a lambda expression - c#

I'm trying to write an in-line function for count occurrences of a word in a string using lambda expressions recursively.
The function:
Func<string, string, int> getOccurrences = null;
getOccurrences = (text, searchTerm) =>
text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) == -1
? 0
: getOccurrences(
text.Substring(
text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase)
+ searchTerm.Length),
searchTerm) + 1;
The problem is that I'm call IndexOf method twice,
The first one is for recursive break condition and the second one is to get the value for add it.
Is there any suggest to call it once?
Thanks in advance.

If you don't mind a non-pure-function lambda you can do:-
Func<string, string, int> getOccurrences = null;
getOccurrences = (text, searchTerm) =>
{
int i = text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase);
return i == -1 ? 0 : getOccurrences(i + searchTerm.Length), searchTerm) + 1;
}

You could do it like this:
Func<string, string, int> getOccurrences =
(text, searchTerm) => getOccurrencesInternal(
text,
searchTerm,
text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase));
Func<string, string, int, int> getOccurrencesInternal = null;
getOccurrences = (text, searchTerm, index) =>
index == -1
? 0
: getOccurrencesInternal(
text.Substring(
index + searchTerm.Length),
searchTerm) + 1;

I suggest you make it a separate method
Func<string, string, int> getOccurrences = GetOccurrences;
private int GetOccurrences(string text, string searchTerm)
{
//...
}
or inline
Func<string, string, int> getOccurrences = delegate(string text, string searchTerm)
{
//...
};
with lambda syntax but just another way of writing the above
Func<string, string, int> getOccurrences = (string text, string searchTerm) =>
{
//...
};

You could use an additional, anonymous lambda and invoke it immediately. I'm not certain of the exact C# syntax, but it should look something like:
Func<string, string, int> getOccurrences = null;
getOccurrences = (text, searchTerm) =>
((index) =>
index == -1
? 0
: getOccurrences(text.Substring(index + searchTerm.Length),
searchTerm) + 1
)(text.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase))

My usual solution to these kind of problems, ignoring any optimizations, is just to remove the matching term and check any change in the resulting string's length.
Func<String, String, Int32> getOccurrences = (text, term) =>
(text.Length - text.Replace(term, "").Length) / term.Length;

Related

Func<> add an extra parameter dynamically

how can I add an extra parameter to my Func<> expression ? Something like:
Func<char, bool> myPredicate = (x) => (char.IsLetter(x) || x == 'X');
...
"abc".All(x => char.IsDigit(x) || myPredicate);
but I get an error
Operator '||' cannot be applied to operands of type 'bool' and Func< char, bool>
You need to invoke the myPredicate function like this:
"abc".All(x => char.IsDigit(x) || myPredicate(x));
Or, what about this other approach?
var text = "abc";
var predicates = new Func<char, bool>[] {
x => char.IsLetter(x) || x == 'X',
char.IsDigit
};
var result = predicates.Any(text.All);
// Outputs TRUE
Console.WriteLine(result);
Also, if you need to check many specific characters, you can create a charEquals with curried parameters:
var text = "abc";
// More type inference to generalize charEquals to just equals, please!
Func<char, Func<char, bool>> charEquals = ca => cb => ca == cb;
var predicates = new Func<char, bool>[] {
char.IsLetter,
charEquals('X'),
charEquals('Y'),
charEquals('Z'),
char.IsDigit
};
var result = predicates.Any(text.All);
Console.WriteLine(result);

Convert a string in a List<int> using LINQ (cleaner way)

I have this string:
string input = "1,2,3,4,s,6";
Pay attention to the s character.
I just want to convert this string in a List<int> using LINQ. I initially tried in this way:
var myList = new List<int>();
input.Split(',').ToList().ForEach(n =>
myList.Add(int.TryParse(n, out int num) ? num : -1)
);
lista.RemoveAll(e => e == -1);
But I prefer not have any -1 instead of a no-number characters.
So now I try with this:
var myList = new List<int>();
input.Split(',').ToList()
.FindAll(n => int.TryParse(n, out int _))
.ForEach(num => myList.Add(int.Parse(num)));
I prefer this, but is really a shame that the parsing happening two times (TryParse at first and then Parse). But, from what I understand, the out variable in TryParse is useless (or not?).
Have you others suggests (using LINQ)?
public class ParsesStringsToIntsWithLinq
{
public IEnumerable<int> Parse(string input)
{
var i = 0;
return (from segment in input.Split(',')
where int.TryParse(segment, out i)
select i);
}
}
[TestClass]
public class Tests
{
[TestMethod]
public void IgnoresNonIntegers()
{
var input = "1,2,3,4,s,6";
var output = new ParsesStringsToIntsWithLinq().Parse(input);
Assert.IsTrue(output.SequenceEqual(new []{1,2,3,4,6}));
}
}
It doesn't return a List<int> but I have to draw the line somewhere. You can make a list out of it.
Using a nice extension method
public static IEnumerable<T> AsSingleton<T>(this T source) {
yield return source;
}
(which you can replace with new[] { n } if preferred)
input.Split(',').SelectMany(s => Int32.TryParse(s, out var n) ? n.AsSingleton() : Enumerable.Empty<int>()).ToList()
I prefer to make a nice helper function:
Func<string, int?> tryParse = s => int.TryParse(s, out int n) ? (int?)n : null;
Then it's a simple matter to parse:
string input = "1,2,3,4,s,6";
List<int> myList =
input
.Split(',')
.Select(s => tryParse(s))
.Where(n => n.HasValue)
.Select(n => n.Value)
.ToList();
That gives:
1
2
3
4
6
int i = 0;
var myList = (from s in input.Split(',') where int.TryParse(s, out i) select i).ToList();
If the numbers are always single ASCII digits:
var myList = "1,2,3,4,s,6".Select(c => c ^ 48).Where(i => i < 10).ToList();
Few slower RegEx alternatives for fun:
var myList2 = Regex.Split("1,2,3,4,s,6", "[^0-9]+").Select(int.Parse).ToList(); // if the string starts and ends with digits
var myList3 = Regex.Replace("1,2,3,4,s,6", "[^0-9]+", " ").Trim().Split(' ').Select(int.Parse).ToList();
var myList4 = Regex.Matches("1,2,3,4,s,6", "[0-9]+").Cast<Match>().Select(m => int.Parse(m.Value)).ToList();
Why does it have to be LINQ?
Try:
//Come up a better name...
public static List<int> ConvertToIntListNoLinq(string input)
{
List<int> output = new List<int>();
foreach(string s in input.Split(','))
{
if(int.TryParse(s, out int result))
{
output.Add(result);
}
}
return output;
}
Fiddle
Here's a generic LINQ extension, which utilizes a delegate. This will allow you to pass in a function returning a bool, while "retaining" the result of the out variable (like int.TryParse).
Usage:
string input = "1,2,3,4,s,6";
List<int> myList = input.Split(',').SelectTry<string, int>(int.TryParse).ToList();
Code:
using System.Collections.Generic;
public static class LINQExtensions
{
public delegate bool TryFunc<TSource, TResult>(TSource source, out TResult result);
public static IEnumerable<TResult> SelectTry<TSource, TResult>(
this IEnumerable<TSource> source, TryFunc<TSource, TResult> selector)
{
foreach (TSource item in source)
{
TResult result;
if (selector(item, out result))
{
yield return result;
}
}
}
}
I think this is a clean way too. Even though it uses that extra variable, the benefit we get is it is clean and understandable.
string ids = "2,4,2,4,5,s"
const int inValidInt = -99;
var ids = ids.Split(',')
.Select(id =>
{
int parsedId = int.TryParse(id, out parsedId) ? parsedId : inValidInt;
return parsedId;
})
.Where(x => x != inValidInt).ToList();
You can do it like this:
List<int> numbers = input
.Split(',')
.Where(t => int.TryParse(t, out int a))
.Select(int.Parse)
.ToList();
You don't need to call .Split(...).ToList() as String[] is already enumerable.
You can use multiple statements in a lambda with braces.
The FindAll, ForEach and RemoveAll methods are not Linq methods, they're members of List<T>. Their Linq equivalent is Where.
Like so:
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',')
.Select( s => { Int32 val; return Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out val ) ? val : -1 } )
.Where( n => n != -1 )
.ToList();
You can make it more concise with a helper method:
static Int32 Parse(String s) {
Int32 ret;
if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
return ret;
}
return -1;
}
Becomes:
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',')
.Select( s => Parse( s ) )
.Where( n => n != -1 )
.ToList();
If you don't want to reserve -1 then you can use nullable ints:
static Int32? Parse(String s) {
Int32 ret;
if( Int32.TryParse( s, NumberStyles.Integer, CultureInfo.InvariantCulture, out ret ) ) {
return ret;
}
return null;
}
List<Int32> numbers = "1,2,3,4,s,6"
.Split(',') // String to String[]
.Select( s => Parse( s ) ) // String[] to IEnumerable<Int32?>
.Where( n => n != null ) // filter out nulls
.Select( n => n.Value ) // IEnumerable<Int32?> to IEnumerable<Int32>
.ToList(); // IEnumerable<Int32> to List<Int32>

Convert Func delegate to a string

Is there any way to convert an existing Func delegate to a string like that:
Func<int, int> func = (i) => i*2;
string str = someMethod(func); // returns "Func<int, int> func = (i) => i*2"
or at least smth close to it
I found a similar question here. It more or less boils down to:
By #TheCloudlessSky,
Expression<Func<Product, bool>> exp = (x) => (x.Id > 5 && x.Warranty != false);
string expBody = ((LambdaExpression)exp).Body.ToString();
// Gives: ((x.Id > 5) AndAlso (x.Warranty != False))
var paramName = exp.Parameters[0].Name;
var paramTypeName = exp.Parameters[0].Type.Name;
// You could easily add "OrElse" and others...
expBody = expBody.Replace(paramName + ".", paramTypeName + ".")
.Replace("AndAlso", "&&");
Console.WriteLine(expBody);
// Output: ((Product.Id > 5) && (Product.Warranty != False))
It doesn't return the Func<int, int> func = (i) => part like in your question, but it does get the underlying expression!

Using Enumerable.Aggregate(...) Method over an empty sequence

I would like to use the Enumerable.Aggregate(...) method to concatenate a list of strings separated by a semicolon. Rather easy, isn't it?
Considering the following:
private const string LISTSEPARATOR = "; ";
album.OrderedTracks is List<TrackDetails>
TrackDetails has DiscNumber Int16? property
The following statement will trow an exception if the sequence returned by Distinct() is empty (as the Aggregate() method doesn't apply on empty sequence):
txtDiscNumber.Text = album.OrderedTracks
.Where(a => a.DiscNumber.HasValue)
.Select(a => a.DiscNumber.Value.ToString())
.Distinct()
.Aggregate((i, j) => i + LISTSEPARATOR + j);
The workaround I am using:
List<string> DiscNumbers =
album.OrderedTracks
.Where(a => a.DiscNumber.HasValue)
.Select(a => a.DiscNumber.Value.ToString())
.Distinct()
.ToList();
if (!DiscNumbers.Any())
txtDiscNumber.Text = null;
else
txtDiscNumber.Text =
DiscNumbers.Aggregate((i, j) => i + LISTSEPARATOR + j);
Is there any better solution? Is it possible to do this in a single LINQ statement?
Thanks in advance.
To concatenate a list of strings, use the string.Join method.
The Aggregate function doesn't work with empty collections. It requires a binary accumulate function and it needs an item in the collection to pass to the binary function as a seed value.
However, there is an overload of Aggregate:
public static TResult Aggregate<TSource, TAccumulate, TResult>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func,
Func<TAccumulate, TResult> resultSelector
)
This overload allows you to specify a seed value. If a seed value is specified, it will also be used as the result if the collection is empty.
EDIT: If you'd really want to use Aggregate, you can do it this way:
sequence.Aggregate(string.Empty, (x, y) => x == string.Empty ? y : x + Separator + y)
Or this way by using StringBuilder:
sequence.Aggregate(new StringBuilder(), (sb, x) => (sb.Length == 0 ? sb : sb.Append(Separator)).Append(x)).ToString()
I think you might find the following helper extension method useful.
public static TOut Pipe<TIn, TOut>(this TIn _this, Func<TIn, TOut> func)
{
return func(_this);
}
It allows you to express your query in the following way.
txtDiscNumber.Text = album.OrderedTracks
.Where(a => a.DiscNumber.HasValue)
.Select(a => a.DiscNumber.Value.ToString())
.Distinct()
.Pipe(items => string.Join(LISTSEPARATOR, items));
This still reads "top to bottom," which greatly aids readability.
You can use
.Aggregate(string.Empty, (i, j) => i + LISTSEPARATOR + j);
with the initial value it works for empty collections
Use String.Join like this:
txtDiscNumber.Text = String.Join(LISTSEPARATOR,
album.OrderedTracks
.Where(a => a.DiscNumber.HasValue)
.Select(a => a.DiscNumber.Value.ToString())
.Distinct());
Used methods like that a lot for debugging purposes,
came up with two extension-methods:
public static string Concatenate<T, U>(this IEnumerable<T> source, Func<T, U> selector, string separator = ", ")
{
if (source == null)
{
return string.Empty;
}
return source
.Select(selector)
.Concatenate(separator);
}
public static string Concatenate<T>(this IEnumerable<T> source, string separator = ", ")
{
if (source == null)
{
return string.Empty;
}
StringBuilder sb = new StringBuilder();
bool firstPass = true;
foreach (string item in source.Distinct().Select(x => x.ToString()))
{
if (firstPass)
{
firstPass = false;
}
else
{
sb.Append(separator);
}
sb.Append(item);
}
return sb.ToString();
}
Use like this:
string myLine = myCol.Concatenate(x => x.TheProperty);

how to refactor a set of <= , >= if...else statements into a dictionary or something like that

there is a method that receives an int parameter and returns a string by checking parameter through a set of if...else statements :
if(param == 1)
{
return "1";
}
else if(param ==20)
{
return "20";
}
else
{
if(param <= 10)
{
return "below 10";
}
else if(param <= 30 )
{
return "below 30";
}
...
}
I wonder if it is possible to put these ">= , <=" conditions in a dictionary
You can use Dictionary<Func<int, bool>, string>:
private string Do(int input)
{
var dic = new Dictionary<Func<int, bool>, string>
{
{param => param == 1, "1"},
{param => param == 20, "20"},
{param => param <= 10, "below 10"},
{param => param <= 30, "blow 30"}
};
return dic.First(pair => pair.Key(input)).Value;
}
Edit:
Comment from #Maarten is correct, Dictionary does not ensure the order of item, List of KeyValuePair should be the best this case:
private string Do(int input)
{
var pairs = new List<KeyValuePair<Func<int, bool>, string>>
{
{param => param == 1, "1"},
{param => param == 20, "20"},
{param => param <= 10, "below 10"},
{param => param <= 30, "blow 30"}
};
var pair = pairs.FirstOrDefault(pair => pair.Key(input));
if (pair == null) return string.Empty; // return whatever you want
return pair.Value;
}
Can use, for example, a dictionary.
Just an example of pseudocode:
var dic = new Dictionary<int,string>{{1,"1"}, {20, "20"}...}
and instead of long if
string value = null;
dic.TryGetValue(param, out value);
Your operations are not well designed for dictionary. consider the following two case
param == 29 and param ==28
both will give output "below 30". If the range of param variable is large then you have to put all the probable values and there corresponding output strings manually in the dictionary. Is it seems a good idea???
No, Dictionary is not designed for that. If you really got too many comparision conditions you could put the comparatees in an array or list, put the values in another array/list in corresponding order, then you use binarysearch to find the key's index, and use the index to get the value. Here is an example:
static string find(int val)
{
int[] keys = {30,40,50};
string[] messages = {"Below 30","Below 40","Below 50"};
int idx = Array.BinarySearch(keys,val);
if(idx < 0)idx = ~idx;
return idx < 3 ? messages[idx] : "Off the chart!";
}
public static void Main (string[] args)
{
Console.WriteLine (find(28));
Console.WriteLine (find(50));
Console.WriteLine (find(100));
}
I tired the following. Please let us know if you run into any issues with this:
int testInput = 15;
Func<int, bool> delegateForCondition1 = param => param == 1;
var conditionsSet = new HashSet<KeyValuePair<Func<int, bool>, String>>
{
new KeyValuePair<Func<int, bool>, String>(delegateForCondition1, "It is 1"),
new KeyValuePair<Func<int, bool>, String>(param => param <= 10 , "below 10"),
new KeyValuePair<Func<int, bool>, String>(param => param <= 30 , "below 30")
};
foreach (KeyValuePair<Func<int, bool>, String> pair in conditionsSet)
{
Func<int, bool> currentKeyAsDelegate = pair.Key;
bool currentResult = pair.Key(testInput);
Console.WriteLine(currentKeyAsDelegate + "---" + currentResult);
}
//Select the first matching condition
KeyValuePair<Func<int, bool>, String> selectedPair = conditionsSet.FirstOrDefault(p => p.Key(testInput));
if (selectedPair.Key != null)
{
Console.WriteLine(selectedPair.Value);
}
Console.ReadLine();

Categories

Resources