Search in a List of List Object in C# - c#

I am having a List of List object in C#. I would like to search this list using ´findall´ method of LINQ.
Here is the code I am using :
String searchString = "Keyword";
List<IntVector> newList = UserData.FindAll(s =>
s.ClientName.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0 ||
s.CustomerID.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0 ||
s.AddInfo.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0 ||
s.MobileNo.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
However, I am not able to search the nested list. One of the member of ´UserData´ is ´CustomerInfo´ which itself is a list of String.
Any idea how to do it ?
Thanks

First, instead of repeating same code many times, I'd create extension method
public static bool IgnoreCaseContains(this string s, string value)
{
return s.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
}
Now use Where and Any operators:
var query = from u in UserData
where u.ClientName.IgnoreCaseContains(searchString) ||
u.CustomerID.IgnoreCaseContains(searchString) ||
u.AddInfo.IgnoreCaseContains(searchString) ||
u.MobileNo.IgnoreCaseContains(searchString) ||
u.CustomerInfo.Any(i => i.IgnoreCaseContains(searchString))
select u;
BTW FindAll returns list, so you don't need to create copy of that list with ToList call.
One step further, you can move all this complex search into user search specification, or even in another extension method:
public static bool Matches(this IntVector user, string searchString)
{
return user.ClientName.IgnoreCaseContains(searchString) ||
user.CustomerID.IgnoreCaseContains(searchString) ||
user.AddInfo.IgnoreCaseContains(searchString) ||
user.MobileNo.IgnoreCaseContains(searchString) ||
user.CustomerInfo.Any(i => i.IgnoreCaseContains(searchString))
}
In this case your code will look like
List<IntVector> newList = UserData.FindAll(u => u.Matches(searchString));
Or
var spec = new UserSearchSpecification("keyword");
List<IntVector> newList = UserData.FindAll(u => spec.IsSatisfiedBy(u));

You can use Any()extension method to see if any of the string in list of strings matches your keyword-
List<IntVector> newList = UserData.FindAll(s =>
s.ClientName.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.CustomerID.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.AddInfo.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.MobileNo.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase) >= 0 ||
s.CustomerInfo.Any(t => t.IndexOf(searchString,
StringComparison.OrdinalIgnoreCase)) >= 0);

Related

How can I store linq queries?

I've researched but I haven't found anything about this situation.
I have two linq queries:
//checking all columns if there is
mockDataList = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search)
).Skip(start).Take(length).ToList();
//getting count info
var filteredTotal = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search)
).Count();
I want to count before to take 10 of them. Therefore I've had to write two queries. I don't want it to duplicate. How can I store without executing query?
*sorry for my grammar mistakes
LINQ queries are executed, when your loop through them via foreach or when you call methods like FirstOrDefault(), ToList(), ToArray(), ... So the following is no problem:
var query = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search)
); // nothing is done here, no filtering
mockDataList = query.Skip(start).Take(length).ToList(); // here, the filtering is done
var filteredTotal = query.Count(); // here, the filtering is done again
Just store LINQ query in the local variable:
var query = mockDataList.Where(w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search));
var filteredTotal = query.Count();
mockDataList = query.Skip(start).Take(length).ToList();
I think the better practice would be to store the IEnumerable as suggested by #SomeBody.
IEnumerable<MyClass> query = mockDataList.Where(w => .....);
List<MyClass> PaginatedFilteredItems = query.Skip(start).Take(length).ToList();
int FilteredItemsTotal = query.Count();
// Or
int PaginatedFilteredItemsTotal = PaginatedFilteredItems.Count;
Another approach whould be to use a Func or Expression to store your query:
public class MyClass{
public int Id;
public string Email;
public string Gender;
public string Name;
public string Surname;
}
Func<MyClass, bool> MyClassFunc = w =>
w.Email.ToLower().Contains(search) ||
w.Gender.ToLower().Contains(search) ||
w.Name.ToLower().Contains(search) ||
w.Surname.ToLower().Contains(search) ||
w.Id.ToString().ToLower().Contains(search);
then you could use it like this:
mockDataList = mockDataList.Where(MyClassFunc).Skip(start).Take(length).ToList();

Check if string contains characters in certain order in C#r

I have a code that's working right now, but it doesn't check if the characters are in order, it only checks if they're there. How can I modify my code so the the characters 'gaoaf' are checked in that order in the string?
Console.WriteLine("5.feladat");
StreamWriter sw = new StreamWriter("keres.txt");
sw.WriteLine("gaoaf");
string s = "";
for (int i = 0; i < n; i++)
{
s = zadatok[i].nev+zadatok[i].cim;
if (s.Contains("g") && s.Contains("a") && s.Contains("o") && s.Contains("a") && s.Contains("f") )
{
sw.WriteLine(i);
sw.WriteLine(zadatok[i].nev + zadatok[i].cim);
}
}
sw.Close();
You can convert the letters into a pattern and use Regex:
var letters = "gaoaf";
var pattern = String.Join(".*",letters.AsEnumerable());
var hasletters = Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase);
For those that needlessly avoid .*, you can also solve this with LINQ:
var ans = letters.Aggregate(0, (p, c) => p >= 0 ? s.IndexOf(c.ToString(), p, StringComparison.InvariantCultureIgnoreCase) : p) != -1;
If it is possible to have repeated adjacent letters, you need to complicate the LINQ solution slightly:
var ans = letters.Aggregate(0, (p, c) => {
if (p >= 0) {
var newp = s.IndexOf(c.ToString(), p, StringComparison.InvariantCultureIgnoreCase);
return newp >= 0 ? newp+1 : newp;
}
else
return p;
}) != -1;
Given the (ugly) machinations required to basically terminate Aggregate early, and given the (ugly and inefficient) syntax required to use an inline anonymous expression call to get rid of the temporary newp, I created some extensions to help, an Aggregate that can terminate early:
public static TAccum AggregateWhile<TAccum, T>(this IEnumerable<T> src, TAccum seed, Func<TAccum, T, TAccum> accumFn, Predicate<TAccum> whileFn) {
using (var e = src.GetEnumerator()) {
if (!e.MoveNext())
throw new Exception("At least one element required by AggregateWhile");
var ans = accumFn(seed, e.Current);
while (whileFn(ans) && e.MoveNext())
ans = accumFn(ans, e.Current);
return ans;
}
}
Now you can solve the problem fairly easily:
var ans2 = letters.AggregateWhile(-1,
(p, c) => s.IndexOf(c.ToString(), p+1, StringComparison.InvariantCultureIgnoreCase),
p => p >= 0
) != -1;
Why not something like this?
static bool CheckInOrder(string source, string charsToCheck)
{
int index = -1;
foreach (var c in charsToCheck)
{
index = source.IndexOf(c, index + 1);
if (index == -1)
return false;
}
return true;
}
Then you can use the function like this:
bool result = CheckInOrder("this is my source string", "gaoaf");
This should work because IndexOf returns -1 if a string isn't found, and it only starts scanning AFTER the previous match.

The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities

I have implemented a linq expression to return a resultset and getting the following error
{"The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities."}
public IEnumerable<TBI.JV.Business.Objects.Asset> GetAssetsBasicBySedols(string[] sedols)
{
var priceDate = DateTime.UtcNow.Date.AddMonths(-8);
var typeList = new string[]
{
"UNIT TRUST",
"OEIC",
"INVESTMENT TRUST",
"INVESTMENT COMPANY",
"PENSION FUND",
"INSURANCE BOND",
"LISTED EQUITY",
"PREFERENCE SHARE",
"ZERO DIVIDEND PREF",
"GILT (CONVENTIONAL)",
"GILT (INDEX LINKED)",
"AIM",
"VCT",
"OFFSHORE FUND",
"ETP"
};
using (var dealingContext = new dbDealingContainer())
{
return (from fundprice in dealingContext.FundPrices
where (fundprice.FUND_STATUS == "ACTIVE" || fundprice.FUND_STATUS == "SUSPENDED") &&
(fundprice.INVNAME != null || fundprice.INVNAME != "") &&
!fundprice.INVNAME.StartsWith("IFSL Bestinvest") &&
// fundprice.WaterlooTradable == true &&
fundprice.BID_MID_PRICE > 0 && typeList.Contains(fundprice.FUND_TYPE)
&& ((sedols.Length > 0 && sedols.Contains(fundprice.SEDOL_NUMBER))
||sedols.Contains(fundprice.SEDOL_NUMBER_ACC)) || sedols.Length == 0
select new TBI.JV.Business.Objects.Asset
{
AssetName = fundprice.INVNAME,
AssetId = fundprice.Id,
AssetType = fundprice.FUND_TYPE,
Epic = fundprice.INVESTMENT_CODENAME,
StarRating = fundprice.STARLEN,
Sedol = fundprice.SEDOL_NUMBER,
SedolAcc = fundprice.SEDOL_NUMBER_ACC
}).ToList();
}
}
The error is thrown at the following line of code sedols.Length > 0 and also sedols.Length == 0. How do I resolve this. My method should be able to take an empty string array as input and return all records.
Define two variables above the query and than use them instead in the query:
var isGreaterThanZero = sedols.Length > 0;
var isEmpty = sedols.Length == 0;
The Any() extension method is supported by LINQ to Entities.
Using exampleArray.Any() and !exampleArray.Any() means you don't have to declare local variables and fortunately it's a nice, succinct syntax.

Linq: X objects in a row

I need help with a linq query that will return true if the list contains x objects in a row when the list is ordered by date.
so like this:
myList.InARow(x => x.Correct, 3)
would return true if there are 3 in a row with the property correct == true.
Not sure how to do this.
Using a GroupAdjacent extension, you can do:
var hasThreeConsecutiveCorrect
= myList.GroupAdjacent(item => item.Correct)
.Any(group => group.Key && group.Count() >= 3);
Here's another way with a Rollup extension (a cross between Select and Aggregate) that's somewhat more space-efficient:
var hasThreeConsecutiveCorrect
= myList.Rollup(0, (item, sum) => item.Correct ? (sum + 1) : 0)
.Contains(3);
There is nothing built into linq that handles this case easily. But it is a relatively simple matter to create your own extension method.
public static class EnumerableExtensions {
public IEnumerable<T> InARow<T>(this IEnumerable<T> list,
Predicate<T> filter, int length) {
int run = 0;
foreach (T element in list) {
if (filter(element)) {
if (++run >= length) return true;
}
else {
run = 0;
}
}
return false;
}
}
Updated:
myList.Aggregate(0,
(result, x) => (result >= 3) ? result : (x.Correct ? result + 1 : 0),
result => result >= 3);
Generalized version:
myList.Aggregate(0,
(result, x) => (result >= length) ? result : (filter(x) ? result + 1 : 0),
result => result >= length);

Comparing Strings using LINQ in C#

I have two strings
Like
"0101000000110110000010010011" and
"0101XXXXXXX101100000100100XX"
it should compare each character and it should not consider if the character is X
for the above two strings the result is true.
now i'm using like
Iterating through the length of the string and replacing thecorresponding character in first string with X
is there any way to do this using LINQ
This is reasonably easy in .NET 4, with the Zip method:
using System;
using System.Linq;
class Test
{
static void Main()
{
string a = "0101000000110110000010010011";
string b = "0101XXXXXXX101100000100100XX";
var equal = !(a.Zip(b, (x, y) => new { x, y })
.Where(z => z.x != z.y && z.x != 'X' && z.y != 'X')
.Any());
Console.WriteLine(equal);
}
}
This basically zips the two strings together (considering them as sequences of characters) so we end up with sequences of pairs. We then try to find any pair where the values are different and neither value is 'X'. If any such pair exists, the strings are non-equal; otherwise they're equal.
EDIT: Thinking about it further, we can reverse the predicate and use All instead:
var equal = a.Zip(b, (x, y) => new { x, y })
.All(z => z.x == z.y || z.x == 'X' || z.y == 'X');
If you're not using .NET 4, you could use the MoreLINQ implementation of Zip which would basically allow you to do the same thing.
Alternatively, you could zip the strings with their indexers like this:
var equal = Enumerable.Range(0, a.Length)
.Select(i => new { x = a[i], y = b[i] })
.All(z => z.x == z.y || z.x == 'X' || z.y == 'X');
That feels like it's cheating somewhat, but it works. Note that in all of these examples, I've assumed that you've already checked whether the input strings are the same length.
You could create a custom comparer and then use the SequenceEqual method:
string s1 = "0101000000110110000010010011";
string s2 = "0101XXXXXXX101100000100100XX";
bool areEqual = s1.SequenceEqual(s2, new IgnoreXEqualityComparer()); // True
// ...
public class IgnoreXEqualityComparer : EqualityComparer<char>
{
public override bool Equals(char x, char y)
{
return (x == 'X') || (y == 'X') || (x == y);
}
public override int GetHashCode(char obj)
{
throw new NotImplementedException();
}
}
This should work.
var a1 = "0101000000110110000010010011";
var a2 = "0101XXXXXXX101100000100100XX";
var notmatched = a2.Select((cha, idx) =>
{
if (cha != 'X')
return (cha == a1[idx]) ? true : false;
else
return true;
}).Any(x => x == false);
if (notmatched)
//strings are not match
else
//strings are match
If you didn't have the X's, I would know the way. With the X's however, you can't do this with Linq as fas as I'm aware.
Anyway, just make them char arrays and do:
arrayOne.Distinct(arrayTwo).ToArray().Length == 0
Edit:
Just occured to me, you can check if that result contains only X's. If it does, return true.

Categories