LINQ search / match - c#

let assume we have an array like this:
var caps = new[] { "1512x", "001xx", "27058", "201xx", "4756x" };
(original array is huge and come from another linq query)
What I need is to create a LINQ statement accepting a value and tries to match with one of the values on the foreseen array.
For example if I use "15121" I need to match the "1512x" value on the array and return it.
Obviously, if I use "27058" it finds the exact match and simply return it.
Is it possible in LINQ?
The "wildcard" char on the array is "x", but I can change it.
Thanks in advance!
Valerio

You can use regular expressions:
var value = "15121";
var caps = new[] { "1512x", "001xx", "27058", "201xx", "4756x" };
var match = caps
.FirstOrDefault(c => new Regex("^" + c.Replace("x", "[0-9]") + "$").IsMatch(value));
if (match != null)
Console.WriteLine("{0} matches {1}", value, match);
The "pattern" 001xx is converted into the regular expression ^001[0-9][0-9]$ and so on. Then the first matching regular expressions is found.
But if the caps is huge it might not perform so well because each regular expression has to be compiled and converted into a state machine until a match is found.

Assuming you have a predicate method, something like this (or something equivalent using Regex, as described in another answer):
static bool Match(string pattern, string exact)
{
if(pattern.Length != exact.Length) return false;
for(var i = 0; i < pattern.Length; i++)
if(pattern[i] != exact[i] && pattern[i] != 'x')
return false;
return true;
}
Then the LINQ query can look like this:
var found = caps.Single(x => Match(x, yourSearch));

Related

Checking Each Element in an Array to See if it is equal to a string

I have a number of elements in an array, I would like to check if a string is equal to any of these elements in the array. The number of elements in the array can change in number.
I have counted the number of elements in the array hoping to get somewhat of an advantage but haven't been able to come up with a solution.
int ArrayCount = FinalEncryptText.Count();
foreach (string i in FinalEncryptText)
{
}
Using the foreach implementation you have provided, you could include an if condition with String.Equals(string) - as Sean pointed out earlier.
But it's worth noting that String.Equals(string) without additional arguments is equivalent to using the == operator. So it's better if you specify the StringComparison type so that you express what kind of comparison you wish to perform.
For example, you could do something like this:
foreach (string element in myStringArray)
{
if(element.Equals("foo", StringComparison.CurrentCultureIgnoreCase))
...
}
You could even include the evaluation as a predicate in a LINQ query. For example, let's say you wanted to see which strings passed the evaluation:
var matches = myStringArray
.Where(element => element.Equals("foo", StringComparison.CurrentCultureIgnoreCase));
You can read more about comparing strings here.
I'm not sure what your method looks like, but I'm assuming.. you're given a random array of strings.. and you want to find a certain element in that array. Using a foreach loop:
public string Check(string[] FinalEncryptText)
{
foreach (string i in FinalEncryptText)
{
//let's say the word you want to match in that array is "whatever"
if (i == "whatever")
{
return "Found the match: " + i;
}
}
}
Using a regular for loop:
public string Check(string[] FinalEncryptText)
{
for (int i = 0; i < FinalEncryptText.Count; i++)
{
//let's say the word you want to match in that array is "whatever"
if (FinalEncryptText[i] == "whatever")
{
//Do Something
return "Found the match: " + FinalEncryptText[i];
}
}
}
Now if you already have a fixed array.. and you're passing in a string to check if that string exists in the array then it would go something like this:
public string Check(string stringToMatch)
{
for (int i = 0; i < FinalEncryptText.Count; i++)
{
//this will match whatever string you pass into the parameter
if (FinalEncryptText[i] == stringToMatch)
{
//Do Something
return "Found the match: " + FinalEncryptText[i];
}
}
}
You could use the String.Equals method in an if statement. More info on String.Method here: String.Equals Method.
if(firstString.Equals(secondString))
{
//whatever you need to do here
}

Building if condition from array

I have following if condition:
if (!string.IsNullOrEmpty(str) &&
(!str.ToLower().Contains("getmedia") && !str.ToLower().Contains("cmsscripts") &&
!str.ToLower().Contains("cmspages") && !str.ToLower().Contains("asmx") &&
!str.ToLower().Contains("cmsadmincontrols"))
)
I am trying to create an array of keywords instead of putting multiple AND conditions, could you please help?
string[] excludeUrlKeyword = ConfigurationManager.AppSettings["ExcludeUrlKeyword"].Split(',');
for (int i = 0; i < excludeUrlKeyword.Length; i++)
{
var sExcludeUrlKeyword = excludeUrlKeyword[i];
}
How to build the same if condition from the array?
You can use LINQ's All or Any method to evaluate a condition on array elements:
// Check for null/empty string, then ...
var lower = str.ToLower();
if (excludeUrlKeyword.All(kw => !lower.Contains(kw))) {
...
}
Note that this is not the fastest approach: you would be better off with a regex. As an added bonus, regex would prevent "aliasing", when you discard a string with a keyword appearing as part of a longer word.
If you would like to try regex approach, change ExcludeUrlKeyword in the config file from comma-separated getmedia,cmsscripts,cmspages,asmx to pipe-separated getmedia|cmsscripts|cmspages|asmx, so that you could feed it directly to regex:
var excludeUrlRegex = ConfigurationManager.AppSettings["ExcludeUrlKeyword"];
if (!Regex.IsMatch(str.ToLower(), excludeUrlRegex)) {
...
}
Linq Any should do this
if (!string.IsNullOrEmpty(str) && !excludeUrlKeyword.Any(x => str.ToLower().Contains(x)))

Evaluate Conditional Expression

I have a scenario in which I am saving my "if" conditions in database as a string. For example:
String condition = "(([age] >= 28) && ([nationality] == 'US'))";
OR
String condition = "([age] >= 28)";
Now, I want to evaluate that the user has input the condition syntactically correct. These are example of incorrect syntax:
String condition = "(([age] >= 28) && ([nationality] == 'US')"; //Missed ')' bracket
String condition = "[age] >= 28)"; //Missed Opening bracket '('
Like we have in Evaluate Query Expression. Might be Expression tress can be helpful. But how? Need help in this regard.
Take a look at NCalc. It's a framework for evaluating mathematical expressions.
When the expression has a syntax error, the evaluation will throw an EvaluationException.
try
{
new Expression("(3 + 2").Evaluate();
}
catch(EvaluationException e)
{
Console.WriteLine("Error catched: " + e.Message);
}
Though, you can also detect syntax errors before the evaluation by using the HasErrors() method.
Expression e = new Expression("a + b * (");
if(e.HasErrors())
{
Console.WriteLine(e.Error);
}
Visual studio doesn't really know what the strings represent so to my knowledge there is no parsing done within the strings themselves.
Typically when programming with C# and using sql, you'd try to do as much of the calculations as possible in C# itself (if it's feasible select the whole table then deal with the result using C#).
If the database is really slow which is quite often the case, it may be useful writing a SQL Builder class to deal with the hardcoded strings.
If you use neither of these methods, unfortunately the best you can really hope for is runtime exceptions (which isn't optimal for obvious reasons).
EDIT:
It seems a SelectQueryBuilder library already exists for the second scenario I suggested.
I found this solution
evaluate an arithmetic expression stored in a string (C#)
SOLUTION:
string conditiontext = "(([age] >= 28) && ([nationality] == \"US\"))";
conditiontext = conditiontext.Replace("[age]", 32)
.Replace("[nationality]","US");
/*VsaEngine*/
var engine = Microsoft.JScript.Vsa.VsaEngine.CreateEngine();
/** Result will be either true or false based on evaluation string*/
var result = Microsoft.JScript.Eval.JScriptEvaluate(conditiontext, engine);
[Note: This interface is deprecated. But it evaluates any arithmetic expressions and c# expressions]
You could use System.Data and its DataTable.Compute() method.
Here is the code:
public bool CheckCondition()
{
// parameters
(string name, object value)[] variables =new (string name, object value)[1];
variables[0].name = "age";
variables[0].value = 28;
variables[1].name = "nationality";
variables[1].value = "US";
string conditions = "(([age] >= 28) && ([nationality] == 'US'))";
conditions.Replace("[", "").Replace("]", "").Replace("&&", "AND").Replace("||", "OR");
using DataTable table = new DataTable();
foreach (var (name, value) in variables)
table.Columns.Add(name, value is null ? typeof(object) : value.GetType());
table.Rows.Add();
foreach (var (name, value) in variables)
table.Rows[0][name] = value;
table.Columns.Add("_Result", typeof(double)).Expression = conditions
?? throw new ArgumentNullException(nameof(conditions));
return (bool)(Convert.ChangeType(table.Compute($"Min(_Result)", null), typeof(bool)));
}

LINQ contains appends % and escapes %

I'm using LINQ to create dynamic sql, when I'm using contains I don't want it to prefix and suffix % and if I'm using % inside my string I don't want to escape it. It escapes the percentage signs added by me using ~ as prefix before % as escape sequence character
For instance:
string str = '%test%.doc%'
.Contains(str) // converts this into LIKE '%~%test~%.doc~%%'
Expected Conversion: LIKE '%test%.doc%%'
as questioner asked, I've made my comments an answer
See Using LINQ Contains vs. SqlMethods.Like and in general the SqlMethods.Like method which will enable you to do a custom LIKE with Linq-to-sql.
Simple example:
var res = from row in dc.Table
where SqlMethods.Like(row.Column, "%A%A%")
select row;
More examples with Contains,StartsWith and Like: http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/10/16/linq-to-sql-like-operator.aspx
Contains is probably translating into the use of the LIKE operator in SQL. This operator takes % as a wildcard character. Contains("abc") maps to LIKE '%abc%'.
I use the following extensions to avoid that case (although in my specific case, I'm still using wildcards, but you could modify it for your own effect).
public static bool Like(this string value, string term)
{
Regex regex = new Regex(string.Format("^{0}$", term.Replace("*", ".*")), RegexOptions.IgnoreCase);
return regex.IsMatch(value ?? string.Empty);
}
public static IEnumerable<string> Like(this IEnumerable<string> source, string expression)
{
return (from s in source where s.Like(expression) select s);
}
Unfortunately, I can't think of an easy way to do this, but this might work:
var a = from t in Db.Tests
let i1 = t.Name.IndexOf("test")
let i2 = t.Name.IndexOf(".doc")
where i1 != -1 && i2 != -1 && i1 < i2
select t;
Here is the equivalent in method chains:
Db.Tests.Select(t => new {t, i1 = t.Name.IndexOf("test")}).Select(
#t1 => new {#t1, i2 = #t1.t.Name.IndexOf(".doc")}).Where(
#t1 => #t1.#t1.i1 != -1 && #t1.i2 != -1 && #t1.#t1.i1 < #t1.i2).Select(#t1 => #t1.#t1.t);

Can all 'for' loops be replaced with a LINQ statement?

Is it possible to write the following 'foreach' as a LINQ statement, and I guess the more general question can any for loop be replaced by a LINQ statement.
I'm not interested in any potential performance cost just the potential of using declarative approaches in what is traditionally imperative code.
private static string SomeMethod()
{
if (ListOfResources .Count == 0)
return string.Empty;
var sb = new StringBuilder();
foreach (var resource in ListOfResources )
{
if (sb.Length != 0)
sb.Append(", ");
sb.Append(resource.Id);
}
return sb.ToString();
}
Cheers
AWC
Sure. Heck, you can replace arithmetic with LINQ queries:
http://blogs.msdn.com/ericlippert/archive/2009/12/07/query-transformations-are-syntactic.aspx
But you shouldn't.
The purpose of a query expression is to represent a query operation. The purpose of a "for" loop is to iterate over a particular statement so as to have its side-effects executed multiple times. Those are frequently very different. I encourage replacing loops whose purpose is merely to query data with higher-level constructs that more clearly query the data. I strongly discourage replacing side-effect-generating code with query comprehensions, though doing so is possible.
In general yes, but there are specific cases that are extremely difficult. For instance, the following code in the general case does not port to a LINQ expression without a good deal of hacking.
var list = new List<Func<int>>();
foreach ( var cur in (new int[] {1,2,3})) {
list.Add(() => cur);
}
The reason why is that with a for loop, it's possible to see the side effects of how the iteration variable is captured in a closure. LINQ expressions hide the lifetime semantics of the iteration variable and prevent you from seeing side effects of capturing it's value.
Note. The above code is not equivalent to the following LINQ expression.
var list = Enumerable.Range(1,3).Select(x => () => x).ToList();
The foreach sample produces a list of Func<int> objects which all return 3. The LINQ version produces a list of Func<int> which return 1,2 and 3 respectively. This is what makes this style of capture difficult to port.
In fact, your code does something which is fundamentally very functional, namely it reduces a list of strings to a single string by concatenating the list items. The only imperative thing about the code is the use of a StringBuilder.
The functional code makes this much easier, actually, because it doesn’t require a special case like your code does. Better still, .NET already has this particular operation implemented, and probably more efficient than your code1):
return String.Join(", ", ListOfResources.Select(s => s.Id.ToString()).ToArray());
(Yes, the call to ToArray() is annoying but Join is a very old method and predates LINQ.)
Of course, a “better” version of Join could be used like this:
return ListOfResources.Select(s => s.Id).Join(", ");
The implementation is rather straightforward – but once again, using the StringBuilder (for performance) makes it imperative.
public static String Join<T>(this IEnumerable<T> items, String delimiter) {
if (items == null)
throw new ArgumentNullException("items");
if (delimiter == null)
throw new ArgumentNullException("delimiter");
var strings = items.Select(item => item.ToString()).ToList();
if (strings.Count == 0)
return string.Empty;
int length = strings.Sum(str => str.Length) +
delimiter.Length * (strings.Count - 1);
var result = new StringBuilder(length);
bool first = true;
foreach (string str in strings) {
if (first)
first = false;
else
result.Append(delimiter);
result.Append(str);
}
return result.ToString();
}
1) Without having looked at the implementation in the reflector, I’d guess that String.Join makes a first pass over the strings to determine the overall length. This can be used to initialize the StringBuilder accordingly, thus saving expensive copy operations later on.
EDIT by SLaks: Here is the reference source for the relevant part of String.Join from .Net 3.5:
string jointString = FastAllocateString( jointLength );
fixed (char * pointerToJointString = &jointString.m_firstChar) {
UnSafeCharBuffer charBuffer = new UnSafeCharBuffer( pointerToJointString, jointLength);
// Append the first string first and then append each following string prefixed by the separator.
charBuffer.AppendString( value[startIndex] );
for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++) {
charBuffer.AppendString( separator );
charBuffer.AppendString( value[stringToJoinIndex] );
}
BCLDebug.Assert(*(pointerToJointString + charBuffer.Length) == '\0', "String must be null-terminated!");
}
The specific loop in your question can be done declaratively like this:
var result = ListOfResources
.Select<Resource, string>(r => r.Id.ToString())
.Aggregate<string, StringBuilder>(new StringBuilder(), (sb, s) => sb.Append(sb.Length > 0 ? ", " : String.Empty).Append(s))
.ToString();
As to performance, you can expect a performance drop but this is acceptable for most applications.
I think what's most important here is that to avoid semantic confusion, your code should only be superficially functional when it is actually functional. In other words, please don't use side effects in LINQ expressions.
Technically, yes.
Any foreach loop can be converted to LINQ by using a ForEach extension method,such as the one in MoreLinq.
If you only want to use "pure" LINQ (only the built-in extension methods), you can abuse the Aggregate extension method, like this:
foreach(type item in collection { statements }
type item;
collection.Aggregate(true, (j, itemTemp) => {
item = itemTemp;
statements
return true;
);
This will correctly handle any foreach loop, even JaredPar's answer. EDIT: Unless it uses ref / out parameters, unsafe code, or yield return.
Don't you dare use this trick in real code.
In your specific case, you should use a string Join extension method, such as this one:
///<summary>Appends a list of strings to a StringBuilder, separated by a separator string.</summary>
///<param name="builder">The StringBuilder to append to.</param>
///<param name="strings">The strings to append.</param>
///<param name="separator">A string to append between the strings.</param>
public static StringBuilder AppendJoin(this StringBuilder builder, IEnumerable<string> strings, string separator) {
if (builder == null) throw new ArgumentNullException("builder");
if (strings == null) throw new ArgumentNullException("strings");
if (separator == null) throw new ArgumentNullException("separator");
bool first = true;
foreach (var str in strings) {
if (first)
first = false;
else
builder.Append(separator);
builder.Append(str);
}
return builder;
}
///<summary>Combines a collection of strings into a single string.</summary>
public static string Join<T>(this IEnumerable<T> strings, string separator, Func<T, string> selector) { return strings.Select(selector).Join(separator); }
///<summary>Combines a collection of strings into a single string.</summary>
public static string Join(this IEnumerable<string> strings, string separator) { return new StringBuilder().AppendJoin(strings, separator).ToString(); }
In general, you can write a lambda expression using a delegate which represents the body of a foreach cycle, in your case something like :
resource => { if (sb.Length != 0) sb.Append(", "); sb.Append(resource.Id); }
and then simply use within a ForEach extension method. Whether this is a good idea depends on the complexity of the body, in case it's too big and complex you probably don't gain anything from it except for possible confusion ;)

Categories

Resources