How to include generic behaviour in contains string [duplicate] - c#

I've been trying to solve this problem all day, and haven't found a solution that truly works. When I search for some data, I want to filter out the data based on multiple words.
My input value is split up by using the standard .Split-function.
string[] searchstrings = MessageResult.Split(' ');
I've made a query (which obviously doesn't work properly) that tries to filter out all the entries that matches every string in searchstrings.
var suggestions = (from a in query
from w in searchstrings
where a.Message.ToLower().Contains(w.ToLower())
select a).Distinct();
query is my variable which has all the data. How can I make this query to actually only match out entries that includes every string in searchstrings?

I think code below should solve your problem. It checks if all words in searchstring are in a query (a).
var suggestions = (from a in query
where searchstrings.All(word => a.ToLower().Contains(word.ToLower()))
select a);

var query = new string[]
{
"abc foo bar xyz john doe",
"abc foo bar xyz doe",
"hello world",
"abc foo bar john doe",
};
var searchstrings = new string[]
{
"abc",
"foo",
"john",
"xyz",
};
searchstrings = searchstrings.Select(x => x.ToLower()).ToArray();
var results = query.Select(x => x.ToLower())
.Where(x => searchstrings.All(y => x.Contains(y)));
Note:
ToLower() is performed outside the Where clause, to save a lot of calls to that method.

Related

Contains without order

I want to search a list of strings using a set of characters and want to find matches regardless of order. For example if my list contains
List<string> testList = new List<string>() { "can", "rock", "bird" };
I want to be able to search using "irb" and have it return bird. I have to do this many times so I am looking for the most efficient way of doing it.
var query = "irb";
List<string> testList = new List<string>() { "can", "rock", "bird" };
var result = testList.Where(i => query.All(q => i.Contains(q)));
For each item in the testList test to see if it contains all the letters in query
For your scenario, you need to check each character of word in another list of word.
For that, you can do like this :
// Checks whether all character in word is present in another word
Func<string, string, bool> isContain = (s1, s2) =>
{
int matchingLength = 0;
foreach (var c2 in s2.ToCharArray())
{
foreach (var c1 in s1.ToCharArray())
{
if (c1 == c2)
++matchingLength;
}
}
// if matched length is equal to word length given, it would be assumed as matched
return s2.Length == matchingLength;
};
List<string> testList = new List<string>() { "can", "rock", "bird" };
string name = "irb";
var fileredList = testList.Where(x => isContain(x, name));
If you don't care about matching duplicates than checking if all characters in a sequence you are searching for are contained in the word would do for predicate:
"irb".Except("bird").Count() == 0
And whole condition:
List<string> testList = new List<string>() { "can", "rock", "bird" };
var search = "irb";
var matches = testList.Where(word => !search.Except(word).Any());
Notes:
you need to normalize all words to lowercase if you need mixed case letters to match.
if performance of searching for different values is critical - convert search string to HashSet first and do except manually.
if you need to match different values against same list many times - convert list of strings to list of HashSet and use search.All(c => wordAsHashSet.Contains(c)) as condition.
You can use linq to achieve this
List<string> testList = new List<string>() { "can", "rock", "bird" };
var lst = testList.Where(x => x.ToUpperInvariant().Contains("IRD")).ToList();
Make sure you also compare the cases using ToUpper and the string you want to compare also make it UpperCase

EF get record for each item

Hello guys this is what I have so far.
public List<Word> GetWords(string[] words)
{
return DbContext.Words.Where(w => words.Contains(w.Value.ToLower());
}
The problem is that when I pass for example List containing two same words I am getting one entity. What can be an effective way of getting entity for each word even if it's duplicate?
Given this simplified test case, where sourceData represents your DbContext.Words:
var sourceData = new string[]
{
"a",
"b",
"c"
};
var wordsToFind = new string[]
{
"a",
"a"
};
A silly way would be to execute the query for each requested input:
var foundWords = wordsToFind.Select(w =>
sourceData.Where(s => s.Contains(w.ToLower()))).ToList();
Or you could execute the query once, then duplicate the results per input by executing the query in-memory again:
var foundWords = sourceData.Where(w =>
wordsToFind.Contains(w.ToLower())).ToList();
var result = wordsToFind.SelectMany(w =>
foundWords.Where(f =>
f.Contains(w.ToLower()))).ToList();
Not sure if you want substring or equal words, following query returns multiple records but checking for the same word not substring.
public List<Word> GetWords(string[] words)
{
var results = from word in DbContext.Words.ToArray()
join str in words on word.ToLower() equals str
select word;
return results.ToList();
}
EDIT: First get the filtered records from the database and then join it with the array again to get multiple records. Same thing checking twice. Stored procedure would be more efficient for a huge collection.
public List<Word> GetWords(string[] words)
{
var results = from word in DbContext.Words
.Where(w => words.Contains(w.Value.ToLower())
.ToArray()
join str in words on str.Contains(word.ToLower())
equals true
select word;
return results.ToList();
}

Order of groups with dynamic linq

Somewhat similar to this question:
Where do I put the "orderby group.key" in this LINQ statement?
Except I'm using Dynamic.Linq which makes this a bit harder. I have a bunch of data coming from a database and then I'm grouping by some field and then outputing the result. The problem is that the ordering of the groups seems to randomly jump around which isn't very convenient for the end-user. So taking inspiration from the linked question, if I had this:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
// note the first non-dynamic select here was just because I don't think dynamic linq
// will support indexing a string like that and it's not an important detail anyway
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word");
foreach(IGrouping<object, dynamic> g in wordList)
{
Console.WriteLine("Words that being with {0}:",
g.Key.ToString().ToUpper());
foreach (var word in g)
Console.WriteLine(" " + word);
}
Console.ReadLine();
How would I get it to order the keys? At least part of the problem is that the dynamic GroupBy returns an IEnumerable. I thought it might be as easy as:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word")
.OrderBy("Key");
But that gives me a System.ArgumentException (At least one object must implement IComparable.) when it hits the foreach loop.
My actual code in my project is a little more complicated and looks something like this:
var colGroup = row.GroupBy(string.Format("new({0})",
string.Join(",", c)), string.Format("new({0})",
string.Join(",", v)));
Where c is a list of strings that I need to group by and v is a list of strings that I need to select in each group.
Ok - this is one way to do it, but it might be a little to static to be useful. The problem is that I had this part:
.GroupBy("new (FirstLetter)","Word");
Using new because I can't use a value type as a key (I had another question about that: https://stackoverflow.com/a/26022002/1250301). When with the OrderBy("Key") part, the problem is that it doesn't have a way to compare those dynamic types. I could solve it like this:
var wordList = words.Select(w => new {FirstLetter = w[0].ToString(), Word = w})
.GroupBy("FirstLetter","Word")
.OrderBy("Key");
Making the key a string. Or like this:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter as k)","Word")
.OrderBy("Key.k");
Making it order by something (a char) that is comparable.
I can make it work with my actual problem like this (but it's kind of ugly):
var colGroup = row.GroupBy(string.Format("new({0})", string.Join(",", c)),
string.Format("new({0})", string.Join(",", v)))
.OrderBy(string.Join(",", c.Select(ob => string.Format("Key.{0}", ob))));
I am not sure what you are trying to do, but is that syntax even compiling?
try:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy(x => x.FirstLetter, x => x.Word)
.OrderBy(x => x.Key);

search the database for the words within a string

Imagine that a user entered a sentence and I need to search for the subjects that consist of words within the entered sentence. These are the code that I thought they could solve the case.
var result = from x in dataBase.tableName
select x;
string[] words = enteredString.Split();
foreach(string word in words)
{
result = result.Where(x => x.subject.Contains(word));
}
it shows only the search result with the last word in sentence, but I thought the result must be narrowed down each time a word is used in the where line.
Try this:
foreach(string word in words)
{
var temp = word;
result = result.Where(x => x.subject.Contains(temp));
}
This is called (by ReSharper at least) "access to modified closure" - lambda expressions don't capture the value, they capture the entire variable. And the value of the variable word is changing with each iteration of the loop. So, since the Where() method is lazy-evaluated, by the time this sequence is consumed, the value of word is the last one in the sequence.
I hade some success by inverting the logic like this:
string[] words = enteredString.Split();
var results = from x in database.TableName
where words.Any(w => x.subject.Contains(w))
select x;
-- Edit
A more generic approach, for this kind of queries, would be:
class SearchQuery
{
public ICollection<string> Include { get; private set; }
public ICollection<string> Exclude { get; private set; }
}
[...]
SearchQuery query = new SearchQuery
{
Include = { "Foo" }, Exclude = { "Bar" }
}
var results = from x in database.Table
where query.Include.All(i => x.Subject.Contains(i)) &&
query.Exclude.All(i => !x.Subject.Contains(i))
select x;
This assumes that all words in query.Include must occur in Subject, if you want to find any subjects that have at least one of the words query.Include.All should be query.Include.Any
I've tested this with Entity Framework 4. Which will create a SQL query that applies all criteria in the database rather than in memory.
Here you go:
var result = from x in dataBase.tableName
select x;
string[] words = enteredString.Split();
result.Where(r => words.Any(w => r.Subject.Contains(w));
it can't do the thing - since with every word you are overwriting the previous result - you need to do something similar to:
List<object> AllResults = new List<object>();
foreach(string word in words)
{
var temp = word;
AllResults.AddRange (result.Where(x => x.subject.Contains(temp)).ToList());
}
Not sure what type your result type is hence the List<object>...

Extract portion of string

I have got a collection. The coll has strings:
Location="Theater=2, Name=regal, Area=Area1"
and so on. I have to extract just the Name bit from the string. For example, here I have to extract the text 'regal'
I am struggling with the query:
Collection.Location.???? (what to add here)
Which is the most short and precise way to do it?
[Edit] : What if I have to add to a GroupBy clause
Collection.GroupBy(????);
Expanding on Paul's answer:
var location = "Theater=2, Name=regal, Area=Area1";
var foo = location
.Split(',')
.Select(x => x.Split('='))
.ToDictionary(x => x[0].Trim(), x => x[1]);
Console.WriteLine(foo["Name"]);
This populates the original string into a dictionary for easy reference. Again, no error checking or anything.
Location.Split(",").Select(x => x.Split("=")[1])
That's the extremely lazy, completely-without-error-handling way to do it :)
The quick and dirty way is a simple IndexOf/Substring extraction:
string location = "Theater=2, Name=regal, Area=Area1";
int startPos = location.IndexOf("Name=") + 5;
int endPos = location.IndexOf(",", startPos);
string name = location.Substring(startPos, endPos - startPos);
If Regex is an option you can use the lookaround constructs to pluck out a precise match. The sample I used below should work great in c#. The nice thing about this is that it will continue to work even if more comma delimited items are added before the name part.
System.Text.RegularExpressions.Match m =
System.Text.RegularExpressions.Regex.Match(
"Theater=2, Name=regal, Area=Area", #"(?<=Name=)[a-zA-Z0-9_ ]+(?=,)");
Console.WriteLine(m.Value);
Another LINQ-style answer (without the overhead of a dictionary):
var name = (from part in location.Split(',')
let pair = part.Split('=')
where pair[0].Trim() == "Name"
select pair[1].Trim()).FirstOrDefault();
re group by (edit):
var records = new[] {
new {Foo = 123, Location="Theater=2, Name=regal, Area=Area1"},
new {Foo = 123, Location="Name=cineplex, Area=Area1, Theater=1"},
new {Foo = 123, Location="Theater=2, Area=Area2, Name=regal"},
};
var qry = from record in records
let name = (from part in record.Location.Split(',')
let pair = part.Split('=')
where pair[0].Trim() == "Name"
select pair[1].Trim()).FirstOrDefault()
group record by name;
foreach (var grp in qry)
{
Console.WriteLine("{0}: {1}", grp.Key, grp.Count());
}

Categories