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>...
Related
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();
}
FilePrefixList.Any(s => FileName.StartsWith(s))
Can I get s value here? I want to display the matched string.
Any determines only if there is a match, it doesn't return anything apart from the bool and it needs to execute the query.
You can use Where or First/FirstOrDefault:
string firstMastch = FilePrefixList.FirstOrDefault(s => FileName.StartsWith(s)); // null if no match
var allMatches = FilePrefixList.Where(s => FileName.StartsWith(s));
string firstMastch = allMatches.FirstOrDefault(); // null if no match
So Any is fine if all you need to know is if ther's a match, otherwise you can use FirstOrDefault to get the first match or null(in case of reference types).
Since Any needs to execute the query this is less efficient:
string firstMatch = null;
if(FilePrefixList.Any(s => FileName.StartsWith(s)))
{
// second execution
firstMatch = FilePrefixList.First(s => FileName.StartsWith(s));
}
If you want to put all matches into a separate collection like a List<string>:
List<string> matchList = allMatches.ToList(); // or ToArray()
If you want to output all matches you can use String.Join:
string matchingFiles = String.Join(",", allMatches);
Not with Any, no... that's only meant to determine whether there are any matches, which is why it returns bool. However, you can use FirstOrDefault with a predicate instead:
var match = FilePrefixList.FirstOrDefault(s => FileName.StartsWith(s));
if (match != null)
{
// Display the match
}
else
{
// Nothing matched
}
If you want to find all the matches, use Where instead.
if FilePrefixList is a List<string>, you can use List<T>.Find method:
string first = FilePrefixList.Find(s => FileName.StartsWith(s));
fiddle: List.Find vs LINQ (Find is faster)
List<T>.Find (MSDN) returns the first element that matches the conditions defined by the specified predicate, if found; otherwise, the default value for type T
Enumerable.Any() returns bool denoting whether any item matched the criteria.
If you need the matched item, use SingleOrDefault() instead:
var matchedPrefix = FilePrefixList.SingleOrDefault(s => FileName.StartsWith(s));
See MSDN
please check try this:
we assuming FilePrefixList is collectionlist
class A
{
public int ID { get; set; }
public string Name { get; set; }
}
List<A> FilePrefixList= new List<A>();
FilePrefixList.Add(new A
{
ID = 1,
Name = "One"
});
FilePrefixList.Add(new A
{
ID =2,
Name = "Two"
});
FilePrefixList.Add(new A
{
ID = 3,
Name = "Three"
});
select data from list is:
var listItems = FilePrefixList.Where(x =>x.Name.StartsWith("T")).ToList();
I have the following code which extends my filter functionality to allow me to search for more than one Zip-code. Ex. (1254,125,145,1455)
Current Code:
if (Post_Code_PAR != null)
{
String[] PostCode_Array = Post_Code_PAR.Split(',');
query = from p in query where PostCode_Array.Contains(p.Post_Code) select p;
}
I want to extend this code in a way that if I give it something like (0*) it will find zip codes which starts with 0. If I give it 123* it will give me all zip codes which starts with 123. Thanks a lot.
use regular expression:
https://msdn.microsoft.com/en-us/library/ms228595.aspx
https://msdn.microsoft.com/en-us/library/az24scfc(v=vs.110).aspx
example:
IEnumerable<string> allInputs = new[] {"70000", "89000", "89001", "90001"};
string commaSeparatedPostCodePatterns = #"89000 , 90\d\d\d";
if (string.IsNullOrWhiteSpace(commaSeparatedPostCodePatterns))
return;
string[] postCodePatterns = commaSeparatedPostCodePatterns
.Split(',')
.Select(p => p.Trim()) // remove white spaces
.ToArray();
var allMatches =
allInputs.Where(input => postCodePatterns.Any(pattern => Regex.IsMatch(input, pattern)));
foreach (var match in allMatches)
Console.WriteLine(match);
With such a problem, the initial requirements are very simple but quickly become more and more complex (due to internationalization, exceptions to the rule, some smart tester testing an unexpected limit-case like 90ยง$% ...). So regular expressions provide the best trade-off simplicity vs. extendability
The regex equivalent to * is .*. But in your case, you would rather need more restrictive patterns and use the \d placeholder matching a single digit. Optional digit would be \d?. Zero or more digits would be \d*. One or more digits would be \d+. Please look at the documentation for more details.
You can replace the * with \d* which is the Regex way of saying "any amount of digits", basically. Then run through and filter with Regex.IsMatch().
if (Post_Code_PAR != null)
{
var PostCode_Array = from p in Post_Code_PAR.Split(',') select String.Format("^{0}$", p.Replace("*", #"\d*"));
query = (from p in query where PostCode_Array.Any(pattern => Regex.IsMatch(p.Post_Code, pattern)) select p).ToArray();
}
I tried to keep it as close to your code as possible but you could clean it up a bit and use lambda instead. But this should work just as well :)
string Post_Code_PAR = "";
String[] PostCode_Array = Post_Code_PAR.Split(',');
var zipCodeList = query.Where(x => CustomFilter(x.Post_Code, PostCode_Array)).ToList();
private static bool CustomFilter(string code, string[] foundZipCodes)
{
return foundZipCodes.Any(x =>
{
x = x.Trim();
var text = x.Replace("*", "");
if (x.EndsWith("*"))
return code.StartsWith(text);
if (x.StartsWith("*"))
return code.EndsWith(text);
if (x.Contains("*"))
return false;
return code.StartsWith(text);
});
}
now user can find: "0*,*52" etc.
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);
I have a string containing up to 9 unique numbers from 1 to 9 (myString) e.g. "12345"
I have a list of strings {"1"}, {"4"} (myList) .. and so on.
I would like to know how many instances in the string (myString) are contained within the list (myList), in the above example this would return 2.
so something like
count = myList.Count(myList.Contains(myString));
I could change myString to a list if required.
Thanks very much
Joe
I would try the following:
count = mylist.Count(s => myString.Contains(s));
It is not perfectly clear what you need, but these are some options that could help:
myList.Where(s => s == myString).Count()
or
myList.Where(s => s.Contains(myString)).Count()
the first would return the number of strings in the list that are the same as yours, the second would return the number of strings that contain yours. If neither works, please make your question more clear.
If myList is just List<string>, then this should work:
int count = myList.Count(x => myString.Contains(x));
If myList is List<List<string>>:
int count = myList.SelectMany(x => x).Count(s => myString.Contains(s));
Try
count = myList.Count(s => s==myString);
This is one approach, but it's limited to 1 character matches. For your described scenario of numbers from 1-9 this works fine. Notice the s[0] usage which refers to the list items as a character. For example, if you had "12" in your list, it wouldn't work correctly.
string input = "123456123";
var list = new List<string> { "1", "4" };
var query = list.Select(s => new
{
Value = s,
Count = input.Count(c => c == s[0])
});
foreach (var item in query)
{
Console.WriteLine("{0} occurred {1} time(s)", item.Value, item.Count);
}
For multiple character matches, which would correctly count the occurrences of "12", the Regex class comes in handy:
var query = list.Select(s => new
{
Value = s,
Count = Regex.Matches(input, s).Count
});
try
var count = myList.Count(x => myString.ToCharArray().Contains(x[0]));
this will only work if the item in myList is a single digit
Edit: as you probably noticed this will convert myString to a char array multiple times so it would be better to have
var myStringArray = myString.ToCharArray();
var count = myList.Count(x => myStringArray.Contains(x[0]));