Using lambda expressions in array.convertall() - c#

I'm still getting acquainted with delegates and lambdas, I'm not using LINQ, and I also only discovered the ConvertAll function today, so I'm experimenting and asking this to bolster my understanding.
The task I had was to establish whether a string of numbers were even or odd. So first, convert the string list to an int list, and from there into a bool list. As bloated as the code would be, I wondered if I could get it all on one line and reduce the need for an extra for loop.
string numbers = "2 4 7 8 10";
List<bool> evenBools = new List<bool>(Array.ConvertAll(numbers.Split(' '), (x = Convert.Int32) => x % 2 == 0))
The expected result is [true, true, false, true, true]. Obviously the code doesn't work.
I understand that the second argument of Array.ConvertAll) requires the conversation to take place. From string to int, that's simply Convert.ToInt32. Is it possible to do that on the fly though (i.e. on the left side of the lambda expression), so that I can get on with the bool conversion and return on the right?

Second parameter of ConvertAll method is Converter<TInput,TOutput> delegate. You can use anonymous method here, pass string input to it (left side of =>), parse to int inside and return bool value, indicating whether it's even or odd
var boolArray = Array.ConvertAll(numbers.Split(' '), input => Convert.ToInt32(input) % 2 == 0);
List<bool> evenBools = new List<bool>(boolArray);
Finally create a List<bool> from array.
Left side of lambda expressions contains input parameters only (input string in your case). Right side is used for expressions and statements, here you have a logic for parsing and return a bool value.
With help of System.Linq it can be written on the similar way
var boolArray = numbers.Split(' ').Select(input => Convert.ToInt32(input) % 2 == 0).ToArray();
You also might use int.Parse instead of Convert.ToInt32

var boolList = numbers.Split(' ').Select(Int32.Parse).ToList().ConvertAll(i => i % 2 == 0);

Related

Comparing integer numbers predicate to LINQ

I have this simple query where I need to identify all tickets within start and end number of a specific TicketBook object on api side in EF Core.
var ticketBook = await Context.TicketBooks.FirstOrDefaultAsync(x=>x.Id == query.TicketBookId);
if (ticketBook != null)
{
dbTickets = dbTickets.Where(x => ConvertTicketNumberToInt(x, ticketBook));
}
private bool ConvertTicketNumberToInt(Ticket t, TicketBook tb)
{
try
{
var numberOnly = new string(t.Number.Where(t => char.IsDigit(t)).ToArray());
var tNumber = Convert.ToInt64(numberOnly);
return tNumber >= tb.StartIntNumber && tNumber <= tb.EndIntNumber;
}
catch(OverflowException)
{
return false;
}
}
the problem is the "Number" property in Ticket class is nvarchar (string) but I need to convert it into integer for this particular query only and for that I have written a small method which does it for me. But as you can see its very time consuming and not efficient at all so my api call just times out.
I am trying to figure out how to do this in LINQ without writing extra methods like this. The trick is that "number" property can sometimes can have a few alphabets in it which throws exception while converting it to integer so I need to remove those non digit characters before the comparison that's why I had to write this dedicated method for it.
As already mentioned, you are facing some performance issues storing nvarchar instead of long.
Anyway, what you're doing in your code is not that bad - you have fairly simple method for the job which keeps your LINQ code clean and tidy. But since you want to have a single LINQ query, try the following (it can be done shorter but I've chosen this way for readability):
var ticketBook = await Context.TicketBooks.FirstOrDefaultAsync(x=>x.Id == query.TicketBookId);
if (ticketBook != null)
{
dbTickets = dbTickets
.Select(t => new { Ticket = t, Number = new string(t.Number.Where(n => char.IsDigit(n)).ToArray()) })
.Select(t =>
{
long ticketNumber = long.MinValue;
long.TryParse(t.Number), out ticketNumber);
return new { Ticket = t, Number = ticketNumber };
})
.Where(t => t.Ticket >= ticketBook.StartIntNumber && t.Ticket <= ticketBook.EndIntNumber)
.Select(t => t.Ticket);
}
What it does:
in first pass all your varchars are stripped of the letters and converted to strings containing only the digits, then an anonymous type with the complete Ticket class is returned along with this string
the strings are parsed to long - I've abused long.MinValue to indicate a failed conversion (since you're using char.IsDigit(c) I see you're not expect any negative values in your results. You might as well use ulong for twice the positive range and abuse 0 value) and again, an anonymous type is returned
those anonymous structures are filtered with the condition you provided
finally, only the original Ticket structure is returned
If you're concerned about the number of passes over the initial results - I've run several performance tests to find out whether having a number of Selects with short operations inside is slower than having one pass with an elaborate operation and I haven't observed any significant difference.
Your best bet is to do most of the conversion in the database.
If you have access to the context, you can do this:
dbTickets = Context.Tickets
.FromSqlRaw("SELECT * FROM Tickets WHERE CAST(CASE WHEN PATINDEX('%[^0-9]%',Number) = 0 THEN Number ELSE LEFT(Number,PATINDEX('%[^0-9]%',Number)-1) END as int) BETWEEN {0} AND {1}", ticketBook.StartIntNumber, ticketBook.EndIntNumber)
.ToList();
This will strip off any trailing letters from the Number column and convert it to an int, then use that to make sure it is between your StartIntNumber and EndIntNumber.
That said, I would highly suggest you add an additional column into your tickets table that uses a derivative of the above to calculate an integer and then make the column a persistent calculated column. Then you can index on that column. Very little (if ANY) should need to be changed in your code if you do this, and the performance benefit will be huge.
This is based on your comment that said sometimes Number has additional letters at the end, like 123A. The above would need to be modified if Number can have letters at the start or in the middle like A123 or 1A23. Currently, it would treat A123 as 0, and 1A23 as 1.

FsCheck: How to generate test data that depends on other test data?

FsCheck has some neat default Arbitrary types to generate test data. However what if one of my test dates depends on another?
For instance, consider the property of string.Substring() that a resulting substring can never be longer than the input string:
[Fact]
public void SubstringIsNeverLongerThanInputString()
{
Prop.ForAll(
Arb.Default.NonEmptyString(),
Arb.Default.PositiveInt(),
(input, length) => input.Get.Substring(0, length.Get).Length <= input.Get.Length
).QuickCheckThrowOnFailure();
}
Although the implementation of Substring certainly is correct, this property fails, because eventually a PositiveInt will be generated that is longer than the genereated NonEmptyString resulting in an exception.
Shrunk: NonEmptyString "a" PositiveInt 2 with exception: System.ArgumentOutOfRangeException: Index and length must refer to a location within the string.
I could guard the comparison with an if (input.Length < length) return true; but that way I end up with lots of test runs were the property isn't even checked.
How do I tell FsCheck to only generate PositiveInts that don't exceed the input string? I presume I have to use the Gen<T> class, but it's interface is just hella confusing to me... I tried the following but still got PositiveInts exceeding the string:
var inputs = Arb.Default.NonEmptyString();
// I have no idea what I'm doing here...
var lengths = inputs.Generator.Select(s => s.Get.Length).ToArbitrary();
Prop.ForAll(
inputs,
lengths,
(input, length) => input.Get.Substring(0, length).Length <= input.Get.Length
).QuickCheckThrowOnFailure();
You can create generators which depend on values generated from another using SelectMany. This also allows you to use the LINQ query syntax e.g.
var gen = from s in Arb.Generate<NonEmptyString>()
from i in Gen.Choose(0, s.Get.Length - 1)
select Tuple.Create(s, i);
var p = Prop.ForAll(Arb.From(gen), t =>
{
var s = t.Item1.Get;
var len = t.Item2;
return s.Substring(0, len).Length <= s.Length;
});
Check.Quick(p);

Checking values inside a 2D array

I have a 2 dimensional array int[][] data and i want to see if the value contains the value 45 for example
I created this line of code
bool contains = data.Where(x => x.Contains(45)).ToArray().Count() != 0 ? true : false;
but it looks like there is unnecessary code and I am sure that there is an easier or more efficient way to do this
You could use Any linq extension.
bool exists = data.SelectMany(x=>x).Any(x=>x == 45);
Or
bool exists = data.Any(x=>x.Any(s=>s == 45));
Array.IndexOf is optimised for integer arrays, so if your inner arrays are long and you care about speeding it up that much it might be worth doing it like this:
bool exists = data.Any(a => Array.IndexOf(a, 45) != -1);
There are two issues in your code:
data is not an array of int, it is an array int[]
the ternary operator takes a bool condition and returns a value based on the bool value. So x ? true : false is the same as x. There is no need for the operator.
So what (I assume) you want is not to check if data contains 45, but if any of the arrays in data contains 45. So you either flatten the jagged array into on enumeration using SelectMany or concat two Any calls:
bool contains = data.SelectMany(d => d).Contains(45);
or
bool contains = data.Any(d => d.Contains(45);

Linq FirstOrDefault evaluates predicate each iteration?

If I had a statement such as:
var item = Core.Collections.Items.FirstOrDefault(itm => itm.UserID == bytereader.readInt());
Does this code read an integer from my stream each iteration, or does it read the integer once, store it, then use its value throughout the lookup?
Consider this code:
static void Main(string[] args)
{
new[] { 1, 2, 3, 4 }.FirstOrDefault(j => j == Get());
Console.ReadLine();
}
static int i = 5;
static int Get()
{
Console.WriteLine("GET:" + i);
return i--;
}
It shows, that it will call the method the number of times it needs to meet the first element matching the condition. The output will be:
GET:5
GET:4
GET:3
I don't know without checking but would expect it to read it each time.
But this is very easily remedied with the following version of your code.
byte val = bytereader.readInt();
var item = Core.Collections.Items.FirstOrDefault(itm => itm.UserID == val);
Myself, I would automatically take this approach anyway just to remove any doubt. Might be a good habit to form as there is no reason to read it for each item.
It's actually quite obvious that the call is performed for each item - FirstOrDefault() takes an delegate as argument. This fact is a bit obscured by using a lambda method but in the end the method only sees a delegate that it can call for each item to check the predicate. In order to evaluate the right hand side only once some magic mechanism would have to understand and rewrite the method and (sometimes sadly) there is no real magic inside compilers and runtimes.

C# Can a comparison in a String value return a Boolean value. eg. "5 < 10" return true

Is there a way a comparison in a string value can return a Boolean value. Example.
If (5 > 5000) would obviously return a false value. But what i wanted to do is have the "5 > 5000" return a false value.
Example.
string com = "5 > 10";
so is there a way to make this com variable return a false value as if it was a comparison between integers.
No built-in way but NCalc can help here
NCalc.Expression expr = new NCalc.Expression("5>10");
bool b = (bool)expr.Evaluate();
You can even use parameters
NCalc.Expression expr = new NCalc.Expression("a<b");
expr.EvaluateParameter += (name, args) =>
{
if (name == "a") args.Result = 5;
if (name == "b") args.Result = 10;
};
bool b = (bool)expr.Evaluate();
There is no built-in way to do this.
Although there are a couple of ways to approach this, one is to simply parse the text yourself. I did this in the code presented in the article A C# Expression Evaluator. You might want to review that code.
No, this can't be done directly.
You should write your own class or extend the String class. For handling a string such as "5 < 10", you need your own method.
You should search the string for signs that indicate comparison, such as "<", "==" etc, then split it and perform the comparison.
Basically: doing it yourself is the only way, but you can try to do it in an elegant way.
Short answer: no.
Long answer: feel free to parse the string yourself, looking for > < and =. Split by whitespace, parse ints then evaluate. It might get harder if you want it to work with parentheses as well...
Not directly, per se (short of the unsafe Javascript eval-execute-my-data hack) but you can try parsing it yourself, depending on how complicated of an expression you want to accept. For example, this should work with the string you have:
var arr = com.Split('>').Select(x=>int.Parse(x.Trim())).ToArray();
return arr[0] > arr[1];
You can also use regular expressions to get more complicated (untested, but it ought to work):
var r = new Regex(#"(\d+)\b*(<|>|=|<=|>=)\b*(\d+)")
var match = r.Match(com);
if(match.Success)
{
var a = int.Parse(match.Captures[0]);
var b = int.Parse(match.Captures[2]);
switch(match.Captures[1])
{
case "<":
return a < b;
case "=":
return a = b;
case ">":
return a > b;
case "<=":
return a <= b;
case "<=":
return a >= b;
}
}
//uh-oh
throw new ArgumentException("com");
Consider using FLEE:
Flee is an expression parser and evaluator for the .NET framework. It allows you to compute the value of string expressions such as sqrt(a^2 + b^2) at runtime. It uses a custom compiler, strongly-typed expression language, and lightweight codegen to compile expressions directly to IL. This means that expression evaluation is extremely fast and efficient. Try out the demo, which lets you generate images based on expressions, and see for yourself.
With FLEE you can easily accomplish this using something like:
var con = new ExpressionContext();
const string com = #"5 > 5000";
var comparison = con.CompileDynamic(com);
var result = comparison.Evaluate();
MessageBox.Show(result.ToString());
HTH...

Categories