Searching a list using an array as the parameter list using LINQ - 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));

Related

multiple word search in flutter

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()

How can i construct a NEST query with optional parameters?

I'm using the NEST .NET client (6.3.1), and trying to compose a search query that is based on a number of (optional) parameters.
Here's what i've got so far:
var searchResponse = await _client.SearchAsync<Listing>(s => s
.Query(qq =>
{
var filters = new List<QueryContainer>();
if (filter.CategoryType.HasValue)
{
filters.Add(qq.Term(p => p.CategoryType, filter.CategoryType.Value));
}
if (filter.StatusType.HasValue)
{
filters.Add(qq.Term(p => p.StatusType, filter.StatusType.Value));
}
if (!string.IsNullOrWhiteSpace(filter.Suburb))
{
filters.Add(qq.Term(p => p.Suburb, filter.Suburb));
}
return ?????; // what do i do her?
})
);
filter is an object with a bunch of nullable properties. So, whatever has a value i want to add as a match query.
So, to achieve that i'm trying to build up a list of QueryContainer's (not sure that's the right way), but struggling to figure out how to return that as a list of AND predicates.
Any ideas?
Thanks
Ended up doing it by using the object initialisez method, instead of the Fluent DSL"
var searchRequest = new SearchRequest<Listing>
{
Query = queries
}
queries is a List<QueryContainer>, which i just build up, like this:
queries.Add(new MatchQuery
{
Field = "CategoryType",
Query = filter.CategoryType
}
I feel like there's a better way, and i don't like how i have to hardcode the 'Field' to a string... but it works. Hopefully someone shows me a better way!

Linq query where there's a certain desired relationship between items in the result

A linq query Where clause can apply a func to an item in the original set and return a bool to include or not include the item based on the item's characteristics. Great stuff:
var q = myColl.Where(o => o.EffectiveDate = LastThursday);
But what if I want to find a set of items where each item is related to the last item in some way? Like:
var q = myColl.Where(o => o.EffectiveDate = thePreviousItem.ExpirationDate);
How do you make a Where (or other linq function) "jump out" of the current item?
Here's what I tried, trying to be clever. I made every item an array just so I can use the Aggregate function:
public IQueryable<T> CurrentVersions
{
get => AllVersions
.Select(vo => new T[] { vo })
.Aggregate((voa1, voa2) => voa1[0].BusinessExpirationDate.Value == voa2[0].BusinessEffectiveDate.Value ? voa1.Concat(voa2).ToArray() : voa1)
.SelectMany(vo => vo);
}
but that doesn't compile on the SelectMany:
The type arguments for method Enumerable.SelectMany<TSource,
TResult>(IEnumerable<TSource>, Func<TSource, IEnumerable<TResult>>)
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
EDIT (SOLUTION)
As it turns out, I was on the right track, but was just confused about what SelectMany does. I didn't need it. I also needed to change IQueryable to IEnumerable because I'm using EF and you can't query after you let go of the DbContext. So, here is the actual solution.
public IEnumerable<T> CurrentVersions
{
get => AllVersions
.Select(vo => new T[] { vo })
.Aggregate((voa1, voa2) => voa1[0].BusinessExpirationDate.Value == voa2[0].BusinessEffectiveDate.Value ? voa1.Concat(voa2).ToArray() : voa1);
}
Linq queries are most effective when each item is processed in isolation. It doesn't work well when trying to relate items within the same collection, without having to process the same collection multiple times and standard linq operators.
The MoreLINQ library helps provide additional operators to fill in some of those gaps. I'm not sure what operators it provides that could be used in this instance, but I know it has a Pairwise() method that combines the current and previous items in the iteration.
In general, for situations like this, if you needed to roll out your own, it would be far easier to write it using a generator to generate your sequence. Either as a general purpose extension method:
public static IEnumerable<TSource> WhereWithPrevious<TSource>(
this IEnumerable<TSource> source,
Func<TSource, TSource, bool> predicate)
{
using (var iter = source.GetEnumerator())
{
if (!iter.MoveNext())
yield break;
var previous = iter.Current;
while (iter.MoveNext())
{
var current = iter.Current;
if (predicate(current, previous))
yield return current;
}
}
}
or one specifically for the problem you're trying to solve.
public static IEnumerable<MyType> GetVersions(IEnumerable<MyType> source)
{
using (var iter = source.GetEnumerator())
{
if (!iter.MoveNext())
yield break;
var previous = iter.Current;
while (iter.MoveNext())
{
var current = iter.Current;
if (current.EffectiveDate == previous.ExpirationDate)
yield return current;
}
}
}
An alternative approach which while standard practice in other languages but terribly inefficient here would be to zip the collection with itself offset by one.
var query = Collection.Skip(1).Zip(Collection, (c, p) => (current:c,previous:p))
.Where(x => x.current.EffectiveDate == x.previous.ExpirationDate)
...;
And with all of that said, using any of these options will most likely make your query incompatible with query providers. It's not something you would want expressed as a single query anyway.

Linq to Entities exception

Hi I have a query like this:
var queryGridData = from question in questions
select new {
i = question.Id,
cell = new List<string>() { question.Id.ToString(), question.Note, question.Topic }
};
The ToString() part needed to convert the int is causing:
LINQ to Entities does not recognize the method 'System.String.ToString()' method, and this method cannot be translated into a store expression.
Hmmmmmmmmmmm. I need it as a string to go into the collection. Any ideas?
I would personally perform just enough of the query in the database to provide the values you want, and do the rest in .NET:
var queryGridData = questions.Select(q => new { q.Id, q.Note, q.Topic })
.AsEnumerable() // Do the rest locally
.Select(q => new { i = q.Id,
cell = new List<string> {
q.Id.ToString(),
q.Note,
q.Topic
} });
(This formatting is horrible, but hopefully it'll be easier to do nicely in an IDE where you've got more space :)

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