multiple word search in flutter - algorithm

Suppose I have a recipe called Garlic parmesan butter. I need to return an object when the appropriate name has been found.
Now in a simple ad-hoc solution I can search in the following way:
class SearchRecipe {
late RecipeModel recipe;
RecipeModel returnRecipe(String? suggestion) {
for (int i = 0; i < Store.instance.getAllRecipes().length; i++) {
if (suggestion == Store.instance.getAllRecipes()[i].recipeName) {
return Store.instance.getAllRecipes()[i];
}
}
return recipe;
}
}
But I need a simple way where if the user types in Garlic butter I need to return the object associated with the Garlic Paremesan butter.
How can I do it?
Edit: I should've clarified that I'm working with a List of objects. So the Store.instance.getAllRecipes() basically returns a List<RecipeModel>.
Update 1: This is what I've written:
class SearchRecipe {
//late RecipeModel recipe;
RecipeModel returnRecipe(String? suggestion) {
List<RecipeModel> results = [];
suggestion!.split(' ').forEach((s) {
results.addAll(Store.instance
.getAllRecipes()
.where((element) => element.recipeName!.contains(s)));
});
results = results.toSet().toList();
for (int i = 0; i < results.length; i++) {
return results[i];
}
return results[0];
}
}

String search = 'Garlic butter';
List<String> list = [
'Garlic Paremesan butter',
'Butter',
'Garlic',
'Butter Garlic',
'Paremesan',
'Stackoverflow'
];
List<String> results = [];
search.split(' ').forEach((s) {
results.addAll(list.where((element) => element.contains(s)));
});
// Avoid repeated values
results = results.toSet().toList();

Split the user input at spaces. Then you have a list. You can check the list and depending on your preference implement a variety of behaviors.
You can match if all words in the list are found. You can return if at least one of the words is matched.
You could give preference to more contained words or you could check the order of words.
In either case you would also not check for equality but use a function like includes / contains to check whether the searched word is part of the name.
(Checking the order could be done by remembering which words you already identified and only searching after the words that were found. In your example you would find ‘garlic’ and after that you would just look through ‘paremesan Butter’ and try to find ‘butter’)

first split the input text
var string = "Hello world!";
string.split(" ");
// ['Hello', 'world!'];
then iterate over each word in above array and check whether the above
Store.instance.getAllRecipes()[i].recipeName contains above word.
if(str.equalsIgnoreCase(str))
{
//remaining code
}
try contains method.
.contains()

Related

How to implement a logic which involves multiple search criteria

I am not sure whether I can ask such kind of questions here. Apologizes if this is an improper question.
We have a functionality which searches some items based on the user input. The problem is the user can enter multiple criteria. The possible combinations are
name:some name
type:some type
domain: some domain
name:some name [ & | ] domain:some domain
name:some name [ & | ] type:some type
type:some type [ & | ] domain:some domain
name:some name [ & | ] type:some type [ & | ] domain:some domain
The 'domain' and 'type' are attributes of type X and 'name' is an attribute of type Y.
I have implemented different methods which actually retrieves all data for different combinations. The basic methods are
Set<Item> getItemsForAName(String name)
Set<Item> getItemsForADomain(String domain)
Set<Item> getItemsForAType(String type)
Other methods will use these 3 methods internally and will return the result.
The problem I am facing is now I have to write some logic which will actually call appropriate method based on the search string.
After various logics finally I decided to use regular expressions. So I wrote some if else statements like this
//I have the search string separated and put in a map already
if(searchString.matches("name:[\\w\\s-]+")) {
Set<item> items = getItemsForAName(map.get("name"));
} else if (searchString.matches("type:[\\w\\s-]+")) {
Set<Item> items = getItemsForAType(map.get("type"));
} else if (searchString.matches("domain:[\\w\\s-]+")) {
Set<Item> items = getItemsForAType(map.get("domain"));
} else if (searchString.matches("domain:[\\w\\s-]+&type:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainAndType(map.get("domain"), map.get("type"));
} else if (searchString.matches("domain:[\\w\\s-]+&name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainAndName(map.get("domain"), map.get("name"));
} else if (searchString.matches("type:[\\w\\s-]+&name:[\\w\\s-]+")) {
Set<Item> items = getItemsForATypeAndName(map.get("type"), map.get("name"));
} else if (searchString.matches("domain:[\\w\\s-]+\\|type:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainOrType(map.get("domain"), map.get("type"));
} else if (searchString.matches("domain:[\\w\\s-]+\\|name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainOrName(map.get("domain"), map.get("name"));
} else if (searchString.matches("type:[\\w\\s-]+\\|name:[\\w\\s-]+")) {
Set<Item> items = getItemsForATypeOrName(map.get("type"), map.get("name"));
} else if (searchString.matches("domain:[\\w\\s-]+&type:[\\w\\s-]+&name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainAndTypeAndName(map.get("domain"), map.get("type"), map.get("name"));
} else if (searchString.matches("domain:[\\w\\s-]+\\|type:[\\w\\s-]+\\|name:[\\w\\s-]+")) {
Set<Item> items = getItemsForADomainOrTypeOrName(map.get("domain"), map.get("type"), map.get("name"));
}
I suppose that this is a stupid logic. I have even tried some other ways to break this logic. Other logic I wrote is while constructing the map I store the separators even. I use a LinkedHashMap for this because I need the insertion order to make it a bit simple. Then I write some if else statements like this
if(!map.get("name").isEmpty() && !map.get("type").isEmpty() && map.get("domain").isEmpty() && !map.get("separator1").equals("&") && map.get("separator2").equals("&")) {
Set<Item> items = getItemsForADomainAndTypeAndName(map.get("domain"), ap.get("type"), map.get("name"));
} else if .....
However I dint choose the second procedure because I need the insertion order I am using LinkedHashMap so may become a problem in future and I need to even put the separators in the map. So I decided to go with RegEx way. The problem is regular expressions take more time I read somewhere. I am not able to take a decision or I am not sure whether there is an even better approach.
Can anyone please suggest a simple solution? Thank you all in advance.
Perhaps try using an overloaded search function (if your language supports this) that will call the appropriate function based on an if/else ladder that checks whether certain values are not null.

Searching a list using an array as the parameter list using LINQ

I currently have some code that looks like this
string[] contains = new string[]{"marge", "homer", "lisa", "bart", "maggie"};
string[] actions = new string[]{"get dye", "mmm beer", "blow saxophone", "have a cow", "bang"};
for (int i = 0; i < someActions.Count; ++i)
{
if (someActions.Contains(contains[i]))
callRoutine(actions[i]);
}
(this is a very trivial example - someActions is a List)
I'm wondering if there is a way in LINQ to do the same as loop? I'm thinking of something along the lines of
int i = position in contains or 0
callRoutine(actions[i]);
The problem is that I don't know how to use an array as the search parameter. Various searches suggest I need to use IEnumerable to do this, but I'm not sure.
Any help would be appreciated here.
Paul
This wouldn't work nicely with your current data setup.
If you are flexible on your data, you could try this:
var namesToActions = new Dictionary<string, string>()
{
{ "marge" , "get dye" },
{ "homer", "mmm beer"},
{ "lisa", "blow saxophone"},
{ "bart", "have a cow"},
{ "maggie", "bang"}
};
someActions.ForEach(a => callRoutine(namesToActions[a]));
Switching to a Dictionary makes it a little easier to perform the type of Linq action you're looking for and provides additional flexibility and quicker lookup times.
I'm not sure what your purpose is, but if you want to convert your for loop into a linq statement, you can do this:
var i = 0;
someActions.ForEach(x =>
{
if (someActions.Contains(contains[i]))
callRoutine(actions[i]);
i++;
});
someActions.Intersect(contains).ForEach(callRoutine);
OR
someActions.Intersect(contains).ForEach(i=>callRoutine(i));

I have 2 Lists of strings. How do I get a bool that tells me if one lists contains atleast one string from the other list ? (Using Lambda)

This should be simple but I could not wrap my head around it.. Here is how I am doing it now but it seems so wasteful.
There is a
List<string> committees
and
List<string> P.committees
I just want to see if one list has any strings that are contained in the other.
List<Person> listFilteredCommitteesPerson = new List<Person>();
foreach (Person p in listFilteredPerson)
{
foreach (string strCommittee in p.Committees)
{
if (committees.Contains(strCommittee))
{
listFilteredCommitteesPerson.Add(p);
}
}
}
listFilteredPerson = listFilteredCommitteesPerson;
For a boolean value:
var match =
committees.Intersect(listFilteredPerson.SelectMany(p => p.Committees)).Any();
If you want a collection of Person that have a match you can use:
var peopleThatMatch =
listFilteredPerson.Where(p => committees.Intersect(p.Committees).Any());
or:
var peopleThatMatch =
listFilteredPerson.Where(p => p.Committees.Any(s => committees.Contains(s)));
You might want to consider another collection type (e.g. HashSet<T>) for performance reasons if you have large collections.

How to dynamically add OR operator to WHERE clause in LINQ

I have a variable size array of strings, and I am trying to programatically loop through the array and match all the rows in a table where the column "Tags" contains at least one of the strings in the array. Here is some pseudo code:
IQueryable<Songs> allSongMatches = musicDb.Songs; // all rows in the table
I can easily query this table filtering on a fixed set of strings, like this:
allSongMatches=allSongMatches.Where(SongsVar => SongsVar.Tags.Contains("foo1") || SongsVar.Tags.Contains("foo2") || SongsVar.Tags.Contains("foo3"));
However, this does not work (I get the following error: "A lambda expression with a statement body cannot be converted to an expression tree")
allSongMatches = allSongMatches.Where(SongsVar =>
{
bool retVal = false;
foreach(string str in strArray)
{
retVal = retVal || SongsVar.Tags.Contains(str);
}
return retVal;
});
Can anybody show me the correct strategy to accomplish this? I am still new to the world of LINQ :-)
You can use the PredicateBuilder class:
var searchPredicate = PredicateBuilder.False<Songs>();
foreach(string str in strArray)
{
var closureVariable = str; // See the link below for the reason
searchPredicate =
searchPredicate.Or(SongsVar => SongsVar.Tags.Contains(closureVariable));
}
var allSongMatches = db.Songs.Where(searchPredicate);
LinqToSql strange behaviour
I recently created an extension method for creating string searches that also allows for OR searches. Blogged about here
I also created it as a nuget package that you can install:
http://www.nuget.org/packages/NinjaNye.SearchExtensions/
Once installed you will be able to do the following
var result = db.Songs.Search(s => s.Tags, strArray);
If you want to create your own version to allow the above, you will need to do the following:
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, params string[] searchTerms)
{
if (!searchTerms.Any())
{
return source;
}
Expression orExpression = null;
foreach (var searchTerm in searchTerms)
{
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var containsExpression = BuildContainsExpression(stringProperty, searchTermExpression);
orExpression = BuildOrExpression(orExpression, containsExpression);
}
var completeExpression = Expression.Lambda<Func<T, bool>>(orExpression, stringProperty.Parameters);
return source.Where(completeExpression);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
}
Alternatively, take a look at the github project for NinjaNye.SearchExtensions as this has other options and has been refactored somewhat to allow other combinations
There is another, somewhat easier method that will accomplish this. ScottGu's blog details a dynamic linq library that I've found very helpful in the past. Essentially, it generates the query from a string you pass in. Here's a sample of the code you'd write:
Dim Northwind As New NorthwindDataContext
Dim query = Northwind.Products _
.Where("CategoryID=2 AND UnitPrice>3") _
.OrderBy("SupplierId")
Gridview1.DataSource = query
Gridview1.DataBind()
More info can be found at scottgu's blog here.
Either build an Expression<T> yourself, or look at a different route.
Assuming possibleTags is a collection of tags, you can make use of a closure and a join to find matches. This should find any songs with at least one tag in possibleTags:
allSongMatches = allSongMatches.Where(s => (select t from s.Tags
join tt from possibleTags
on t == tt
select t).Count() > 0)

Custom search in Dynamics CRM 4.0

I have a two related questions.
First:
I'm looking to do a full text search against a custom entity in Dynamics CRM 4.0. Has anyone done this before or know how to do it?
I know that I can build QueryExpressions with the web service and sdk but can I do a full text search with boolean type syntax using this method? As far as I can tell that won't do the trick.
Second:
Does anyone else feel limited with the searching abilities provided with Dynamics CRM 4.0? I know there are some 3rd pary search products out there but I haven't found one I like yet. Any suggestions would be appreciated.
Searching and filtering via the CRM SDK does take some time to get used to. In order to simulate full text search, you need to use nested FilterExpressions as your QueryExpression.Criteria. SDK page for nested filters The hardest part is figuring out how to build the parent child relationships. There's so much boolean logic going on that it's easy to get lost.
I had a requirement to build a "search engine" for one of our custom entities. Using this method for a complex search string ("one AND two OR three") with multiple searchable attributes was ugly. If you're interested though, I can dig it up. While it's not really supported, if you can access the database directly, I would suggest using SQL's full text search capabilities.
--
ok, here you go. I don't think you'll be able to copy paste this and fulfill your needs. my customer was only doing two to three key word searches and they were happy with the results from this. You can see what a pain it is to just do this in a simple search scenario. I basically puked out code until it was 'working'.
private FilterExpression BuildFilterV2(string[] words, string[] seachAttributes)
{
FilterExpression filter = new FilterExpression();
List<FilterExpression> allchildfilters = new List<FilterExpression>();
List<string> andbucket = new List<string>();
List<string> orBucket = new List<string>();
// clean up commas, quotes, etc
words = ScrubWords(words);
int index = 0;
while (index < words.Length)
{
// if current word is 'and' then add the next wrod to the ad bucket
if (words[index].ToLower() == "and")
{
andbucket.Add(words[index + 1]);
index += 2;
}
else
{
if (andbucket.Count > 0)
{
List<FilterExpression> filters = new List<FilterExpression>();
foreach (string s in andbucket)
{
filters.Add(BuildSingleWordFilter(s, seachAttributes));
}
// send existing and bucket to condition builder
FilterExpression childFilter = new FilterExpression();
childFilter.FilterOperator = LogicalOperator.And;
childFilter.Filters = filters.ToArray();
// add to child filter list
allchildfilters.Add(childFilter);
//new 'and' bucket
andbucket = new List<string>();
}
if (index + 1 < words.Length && words[index + 1].ToLower() == "and")
{
andbucket.Add(words[index]);
if (index + 2 <= words.Length)
{
andbucket.Add(words[index + 2]);
}
index += 3;
}
else
{
orBucket.Add(words[index]);
index++;
}
}
}
if (andbucket.Count > 0)
{
List<FilterExpression> filters = new List<FilterExpression>();
foreach (string s in andbucket)
{
filters.Add(BuildSingleWordFilter(s, seachAttributes));
}
// send existing and bucket to condition builder
FilterExpression childFilter = new FilterExpression();
childFilter.FilterOperator = LogicalOperator.And;
childFilter.Filters = filters.ToArray();
// add to child filter list
allchildfilters.Add(childFilter);
//new 'and' bucket
andbucket = new List<string>();
}
if (orBucket.Count > 0)
{
filter.Conditions = BuildConditions(orBucket.ToArray(), seachAttributes);
}
filter.FilterOperator = LogicalOperator.Or;
filter.Filters = allchildfilters.ToArray();
return filter;
}
private FilterExpression BuildSingleWordFilter(string word, string[] seachAttributes)
{
List<ConditionExpression> conditions = new List<ConditionExpression>();
foreach (string attr in seachAttributes)
{
ConditionExpression expr = new ConditionExpression();
expr.AttributeName = attr;
expr.Operator = ConditionOperator.Like;
expr.Values = new string[] { "%" + word + "%" };
conditions.Add(expr);
}
FilterExpression filter = new FilterExpression();
filter.FilterOperator = LogicalOperator.Or;
filter.Conditions = conditions.ToArray();
return filter;
}
private ConditionExpression[] BuildConditions(string[] words, string[] seachAttributes)
{
List<ConditionExpression> conditions = new List<ConditionExpression>();
foreach (string s in words)
{
foreach (string attr in seachAttributes)
{
ConditionExpression expr = new ConditionExpression();
expr.AttributeName = attr;
expr.Operator = ConditionOperator.Like;
expr.Values = new string[] { "%" + s + "%" };
conditions.Add(expr);
}
}
return conditions.ToArray();
}
Hm, that's a pretty interesting scenario...
You could certainly do a 'Like' query, and 'or' together the colums/attribute conditions you want included in the search. This seems to be how CRM does queries from the box above entity lists (and they're plenty fast). It looks like the CRM database has a full-text index, although exactly which columns are used to populate it is a bit foggy to me after a brief peek.
And remember LinqtoCRM for CRM query love (I started the project, sorry about the shameless plug).
Second - I can recommend "Global Search" by Akvelon which provides ability to search in all Custom Entities and attributes and Out of Box entities and attributes. Also they are using FTS for search in the attached documents contents. You can find more details in their official site: http://www.akvelon.com/Products/Dynamics%20CRM%20global%20Search/default.aspx
I would suggest utilizing the Dynamics CRM filtered views provided for you in the database. Then you can utilize all the power of native SQL to do any LIKE's or other logic you need. Plus, the filtered views are security trimmed, so you won't have to worry about users accessing records they do not have permission to.

Resources