Question with PredicateBuilder multiple AND OR - c#

I've a question about the PredicateBuilder and I really hope you can give me some advice on how to solve this. I'll try to explain this.
I have the case where people can search for products based on keywords. Each keyword belongs to a keywordgroup, so some real data would be:
KeywordGroup / Keyword
Type - Chain/
Type - Bracelet/
Color - Purple/
Color - Green
Now I want to have the following results:
Between each different KeywordGroup there should be an OR.
Between each different Keyword inside a KeywordGroup there should be an AND.
So e.g., a user want's to search for only Bracelets with the colors Purlple or Green.
Is this possible with this PredicateBuilder?
This is what I have so far:
================================
/// <summary>
/// Search for products
/// </summary>
/// <param name="itemsPerPage"></param>
/// <returns></returns>
public List<Product> SearchProducts(int from, int max, string sorting, List<Keyword> filter, out int totalitems) {
try {
var predicate = PredicateBuilder.True<Product>();
KeywordGroup previousKeywordGroup = null;
foreach (Keyword k in filter.OrderBy(g=>g.KeywordGroup.SortOrder)) {
if (previousKeywordGroup != k.KeywordGroup) {
previousKeywordGroup = k.KeywordGroup;
predicate = predicate.And(p => p.Keywords.Contains(k));
}
else
predicate = predicate.Or(p => p.Keywords.Contains(k));
}
var products = context.Products.AsExpandable().Where(predicate);
//var products = from p in context.Products
// from k in p.Keywords
// where filter.Contains(k)
// select p;
totalitems = products.Distinct().Count();
if (sorting == "asc")
return products.Where(x => x.Visible == true).Distinct().Skip(from).Take(max).OrderBy(o => o.SellingPrice).ToList();
else
return products.Where(x => x.Visible == true).Distinct().Skip(from).Take(max).OrderByDescending(o => o.SellingPrice).ToList();
}
catch (Exception ex) {
throw ex;
}
}
================================
It doesn't work, though.
Can you help me out?
Thanks!
Daniel

You need to use a temporary variable in the loop for each keyword. From the Predicate Builder page:
The temporary variable in the loop is
required to avoid the outer variable
trap, where the same variable is
captured for each iteration of the
foreach loop.
Try this instead:
foreach (Keyword k in filter.OrderBy(g=>g.KeywordGroup.SortOrder)) {
Keyword temp = k;
if (previousKeywordGroup != k.KeywordGroup) {
previousKeywordGroup = k.KeywordGroup;
predicate = predicate.And(p => p.Keywords.Contains(temp));
}
else
predicate = predicate.Or(p => p.Keywords.Contains(temp));
}
Notice the use of temp in each line where predicate And and Or are used.

This is just making a big list of And ands Or statements, you need to group them together.
Something like this..
var grouped = filter.GroupBy(item => item.KeyWordGroup, item => item.KeyWords);
foreach (var item in grouped)
{
var innerPredicate = PredicateBuilder.True<Product>();
foreach (var inner in item)
{
innerPredicate = innerPredicate.Or(p => item.Contains(k));
}
predicate = predicate.And(innerPredicate); //not sure this is correct as dont have IDE..
}

Related

Execute foreach loop fully, though if statement get executed in loop

I am using foreach loop, now what I want is this loop should run for each elements in that loop count.
Once that is finished then it should return to the function, here in this loop I have also use if statement.
But problem is when first time if statement is executing it is returning to that function without finishing whole foreach loop count.
But instead of that, it should run foreach loop fully and then return with that messages.
Below is code :
foreach (var field in fields.OrderBy(x => x.Sequence))
{
if (!header.Any(x => x.Sequence == field.Sequence && x.Name == field.Name))
{
var colName = header.Where(x => x.Sequence == field.Sequence).Select(x => x.ExcelColName).FirstOrDefault();
var newheaderName = header.Where(x => x.Sequence == field.Sequence).Select(x => x.Name).FirstOrDefault();
message = string.Format("In cell {0}1, column '{1}' was expected but '{2}' was there. The Excel data will NOT be imported.", colName, field.Name, newheaderName);
messageList.Add(message); // Here after adding all message in list, then it should return to calling function
} else
{
}
return null;
}
Put the return messageList at the end of the method.
Sidenote: you can improve performance much: don't filter on Sequence three times per iteration. Store the result of header.Where(x => x.Sequence == field.Sequence) at the beginning of the foreach in a list-variable. Then check if it contains items:
foreach (var field in fields.OrderBy(x => x.Sequence))
{
var seqHeaders = header.Where(x => x.Sequence == field.Sequence).ToList();
var matchingHeader = seqHeaders.FirstOrDefault(h => h.Name == field.Name);
if(!seqHeaders.Any())
{
// you didn't handle this case
messageList.Add($"Sequence not found '{field.Sequence}'. The Excel data will NOT be imported.");
}
else if (matchingHeader == null)
{
string colName = seqHeaders[0].ExcelColName;
string newHeaderName = seqHeaders[0].Name;
messageList.Add($"In cell {colName} column '{field.Name}' was expected but '{newHeaderName}' was there. The Excel data will NOT be imported.");
}
else
{
// use matchingHeader ...
}
}
// rest of method here ...
return messageList; // don't return null but an empty list if there were no errrors
Your return is in your foreach loop. Bring it out of the scope of the foreach.
Let's say you call your function like this.
CallMyFunction();
...
If you don't need anything to be returned from this function then use void like this.
public void CallMyFunction()
{
foreach (var field in fields.OrderBy(x => x.Sequence))
{
if (!header.Any(x => x.Sequence == field.Sequence && x.Name == field.Name))
{
var colName = header.Where(x => x.Sequence == field.Sequence).Select(x => x.ExcelColName).FirstOrDefault();
var newheaderName = header.Where(x => x.Sequence == field.Sequence).Select(x => x.Name).FirstOrDefault();
message = string.Format("In cell {0}1, column '{1}' was expected but '{2}' was there. The Excel data will NOT be imported.", colName, field.Name, newheaderName);
messageList.Add(message); // Here after adding all message in list, then it should return to calling function
} else
{
} // Remove entirely the return.
}
That way your loop will fully run.

How do I pick out values between a duplicate value in a collection?

I have a method that returns a collection that has a duplicate value.
static List<string> GenerateItems()
{
var _items = new List<string>();
_items.Add("Tase");
_items.Add("Ray");
_items.Add("Jay");
_items.Add("Bay");
_items.Add("Tase");
_items.Add("Man");
_items.Add("Ran");
_items.Add("Ban");
return _items;
}
I want to search through that collection and find the first place that duplicate value is located and start collecting all the values from the first appearance of the duplicate value to its next appearance. I want to put this in a collection but I only want the duplicate value to appear once in that collection.
This is what I have so far but.
static void Main(string[] args)
{
string key = "Tase";
var collection = GenerateItems();
int index = collection.FindIndex(a => a == key);
var matchFound = false;
var itemsBetweenKey = new List<string>();
foreach (var item in collection)
{
if (item == key)
{
matchFound = !matchFound;
}
if (matchFound)
{
itemsBetweenKey.Add(item);
}
}
foreach (var item in itemsBetweenKey)
{
Console.WriteLine(item);
}
Console.ReadLine();
}
There must be an easier way of doing this. Perhaps with Indexing or a LINQ query?
You can do something like that
string key = "Tase";
var collection = GenerateItems();
int indexStart = collection.FindIndex(a => a == key);
int indexEnd = collection.FindIndex(indexStart+1, a => a == key);
var result = collection.GetRange(indexStart, indexEnd-indexStart);
You can use linq select and group by to find the first index and last index of all duplicates (Keep in mind if something is in the list more then 2 times it would ignore the middle occurences.
But I personally think the linq for this seems overcomplicated. I would stick with simple for loops and if statements (Just turn it into a method so it reads better)
Here is a solution with Linq to get all duplicate and all values between those duplicates including itself once as you mentioned.
var collection = GenerateItems();
var Duplicates = collection.Select((x,index) => new { index, value = x })
.GroupBy(x => x.value)//group by the strings
.Where(x => x.Count() > 1)//only take duplicates
.Select(x=>new {
Value = x.Key,
FirstIndex = x.Min(y=> y.index),//take first occurenc
LastIndex = x.Max(y => y.index)//take last occurence
}).ToList();
var resultlist = new List<List<string>>();
foreach (var duplicaterange in Duplicates)
resultlist .Add(collection.GetRange(duplicaterange.FirstIndex, duplicaterange.LastIndex - duplicaterange.FirstIndex));
Try this function
public List<string> PickOut(List<string> collection, string key)
{
var index = 0;
foreach (var item in collection)
{
if (item == key)
{
return collection.Skip(index).TakeWhile(x=> x != key).ToList();
}
index++;
};
return null;
}
First finding the duplicate key then find the second occurrence of the item and then take result.
var firstduplicate = collection.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key).First();
var indices = collection.Select((b, i) => b == firstduplicate ? i : -1).Where(i => i != -1).Skip(1).FirstOrDefault();
if (indices>0)
{
var result = collection.Take(indices).ToList();
}

how to ensure a List<String> contains each string in a sequence exactly once

Suppose I have a list of strings, like this:
var candidates = new List<String> { "Peter", "Chris", "Maggie", "Virginia" };
Now I'd like to verify that another List<String>, let's call it list1, contains each of those candidates exactly once.
How can I do that, succintly? I think I can use Intersect(). I also want to get the missing candidates.
private bool ContainsAllCandidatesOnce(List<String> list1)
{
????
}
private IEnumerable<String> MissingCandidates(List<String> list1)
{
????
}
Order doesn't matter.
This may not be optimal in terms of speed, but both queries are short enough to fit on a single line, and are easy to understand:
private bool ContainsAllCandidatesOnce(List<String> list1)
{
return candidates.All(c => list1.Count(v => v == c) == 1);
}
private IEnumerable<String> MissingCandidates(List<String> list1)
{
return candidates.Where(c => list1.Count(v => v == c) != 1);
}
Here we are talking about Except, Intersect and Distinct. I could have used a lamba operator with expression but it would have to loop over each and every item. That functionality is available with a predefined functions.
for your first method
var candidates = new List<String> { "Peter", "Chris", "Maggie", "Virginia" };
private bool ContainsAllCandidatesOnce(List<String> list1)
{
list1.Intersect(candidates).Distinct().Any();
}
This will give any element from list1 which are in common in candidates list or you can do it the other way
candidates.Intersect(list1).Distinct().Any();
for your second method
private IEnumerable<String> MissingCandidates(List<String> list1)
{
list1.Except(candidates).AsEnumerable();
}
This will remove all elements from list1 which are in candidates. If you wants it the other way you can do
candidates.Except(list1).AsEnumerable();
This should be quite efficient:
IEnumerable<string> strings = ...
var uniqueStrings = from str in strings
group str by str into g
where g.Count() == 1
select g.Key;
var missingCandidates = candidates.Except(uniqueStrings).ToList();
bool isValid = !missingCandidates.Any();
Filter out repeats.
Ensure that all the candidates occur in the filtered-out-set.
GroupJoin is the right tool for the job. From msdn:
GroupJoin produces hierarchical results, which means that elements
from outer are paired with collections of matching elements from
inner. GroupJoin enables you to base your results on a whole set of
matches for each element of outer.
If there are no correlated elements in inner for a given element of outer, the sequence of matches for that element will be empty but
will still appear in the results.
So, GroupJoin will find any matches from the target, for each item in the source. Items in the source are not filtered if no matches are found in the target. Instead they are matched to an empty group.
Dictionary<string, int> counts = candidates
.GroupJoin(
list1,
c => c,
s => s,
(c, g) => new { Key = c, Count = g.Count()
)
.ToDictionary(x => x.Key, x => x.Count);
List<string> missing = counts.Keys
.Where(key => counts[key] == 0)
.ToList();
List<string> tooMany = counts.Keys
.Where(key => 1 < counts[key])
.ToList();
private bool ContainsAllCandidatesOnce(List<String> list1)
{
return list1.Where(s => candidates.Contains(s)).Count() == candidates.Count();
}
private IEnumerable<String> MissingCandidates(List<String> list1)
{
return candidates.Where(s => list1.Count(c => c == s) != 1);
}
How about using a HashSet instead of List?
private static bool ContainsAllCandidatesOnce(List<string> lotsOfCandidates)
{
foreach (string candidate in allCandidates)
{
if (lotsOfCandidates.Count(t => t.Equals(candidate)) != 1)
{
return false;
}
}
return true;
}
private static IEnumerable<string> MissingCandidates(List<string> lotsOfCandidates)
{
List<string> missingCandidates = new List<string>();
foreach (string candidate in allCandidates)
{
if (lotsOfCandidates.Count(t => t.Equals(candidate)) != 1)
{
missingCandidates.Add(candidate);
}
}
return missingCandidates;
}

C# Predicate Builder with "NOT IN" functionality

With PredicateBuilder how do I get functionality similar to the SQL IN or NOT IN query?
For example I have a list of IDs and I want to select all of the People whose IDs either Match or do not match the IDs.
The people match functionality is fairly straightforward (although there may be a better way to do it)
var predicate = PredicateBuilder.False<Person>()
foreach (int i in personIDs)
{
int temp = i;
predicate = predicate.Or(e=>e.PersonID == temp);
}
return persons.Where(predicate);
So how do I get the opposite? I want all persons whose IDs are not in the personIDs list.
Ask De Morgan:
NOT (P OR Q) = (NOT P) AND (NOT Q)
To have your code generate the equivalent of a NOT IN condition, rewrite as
var predicate = PredicateBuilder.True<Person>()
and
predicate = predicate.And(e=>e.PersonID != temp);
Do you use Entity Framework?
Then you can build the query without PredicateBuilder:
var personIds = new List<int>() { 8,9,10 };
var query = persons.Where(it => !personIds.Contains(it.PersonId));
From this LINQ statement a SQL NOT IN query is created.
Is this what you want?
var predicate = PredicateBuilder.True<Person>()
foreach (int i in personIDs)
{
int temp = i;
predicate = predicate.And(e => e.PersonID != temp);
}
return persons.Where(predicate);
Without looking at the api....
var predicate = PredicateBuilder.True<Person>()
foreach (int i in personIDs)
{
int temp = i;
predicate = predicate.And(e=>e.PersonID != temp);
}
return persons.Where(predicate);

Use LINQ to move item to top of list

Is there a way to move an item of say id=10 as the first item in a list using LINQ?
Item A - id =5
Item B - id = 10
Item C - id =12
Item D - id =1
In this case how can I elegantly move Item C to the top of my List<T> collection?
This is the best I have right now:
var allCountries = repository.GetCountries();
var topitem = allCountries.Single(x => x.id == 592);
var finalList = new List<Country>();
finalList.Add(topitem);
finalList = finalList.Concat(allCountries.Where(x=> x.id != 592)).ToList();
What do you want to order by, other than the known top item? If you don't care, you can do this:
var query = allCountries.OrderBy(x => x.id != 592).ToList();
Basically, "false" comes before "true"...
Admittedly I don't know what this does in LINQ to SQL etc. You may need to stop it from doing the ordering in the database:
var query = allCountries.AsEnumerable()
.OrderBy(x => x.id != 592)
.ToList();
LINQ is strong in querying collections, creating projections over existing queries or generating new queries based on existing collections. It is not meant as a tool to re-order existing collections inline. For that type of operation it's best to use the type at hande.
Assuming you have a type with a similar definition as below
class Item {
public int Id { get; set; }
..
}
Then try the following
List<Item> list = GetTheList();
var index = list.FindIndex(x => x.Id == 12);
var item = list[index];
list[index] = list[0];
list[0] = item;
Linq generallyworks on Enumerables, so it doesn't now that the underlying type is a collection. So for moving the item on top of the list I would suggest using something like (if you need to preserve the order)
var idx = myList.FindIndex(x => x.id == 592);
var item = myList[idx];
myList.RemoveAt(idx);
myList.Insert(0, item);
If your function returns only an IEnumerable, you can use the ToList() method to convert it to a List first
If you don't preserve the order you can simply swap the values at position 0 and position idx
var allCountries = repository.GetCountries();
allCountries.OrderByDescending(o => o.id == 12).ThenBy(o => o.id)
This will insert the object with id=12 at the top of the list and rotate the rest down, preserving the order.
Here is an extension method you might want to use. It moves the element(s) that match the given predicate to the top, preserving order.
public static IEnumerable<T> MoveToTop(IEnumerable<T> list, Func<T, bool> func) {
return list.Where(func)
.Concat(list.Where(item => !func(item)));
}
In terms of complexity, I think it would make two passes on the collection, making it O(n), like the Insert/Remove version, but better than Jon Skeet's OrderBy suggestion.
You can "group by" in two groups with Boolean key, and then sort them
var finalList= allCountries
.GroupBy(x => x.id != 592)
.OrderBy(g => g.Key)
.SelectMany(g => g.OrderBy(x=> x.id ));
I know this a old question but I did it like this
class Program
{
static void Main(string[] args)
{
var numbers = new int[] { 5, 10, 12, 1 };
var ordered = numbers.OrderBy(num => num != 10 ? num : -1);
foreach (var num in ordered)
{
Console.WriteLine("number is {0}", num);
}
Console.ReadLine();
}
}
this prints:
number is 10
number is 1
number is 5
number is 12
public static IEnumerable<T> ServeFirst<T>(this IEnumerable<T> source,
Predicate<T> p)
{
var list = new List<T>();
foreach (var s in source)
{
if (p(s))
yield return s;
else
list.Add(s);
}
foreach (var s in list)
yield return s;
}
Its interesting the number of approaches you find when trying to solve a problem.
var service = AutogateProcessorService.GetInstance();
var allConfigs = service.GetAll();
allConfigs = allConfigs.OrderBy(c => c.ThreadDescription).ToList();
var systemQueue = allConfigs.First(c => c.AcquirerId == 0);
allConfigs.Remove(systemQueue);
allConfigs.Insert(0, systemQueue);
To also check if the item was found without Exception, something like:
var allCountries = repository.GetCountries();
var lookup = allCountries.ToLookup(x => x.id == 592);
var finalList = lookup[true].Concat(lookup[false]).ToList();
if ( lookup[true].Count() != 1 ) YouAreInTrouble();
Even easier if you have the object:
listOfObjects.Remove(object);
listOfObjects.Insert(0, object);
I wrote a static extension method to do this. Note this doesn't preserve the order, it simply swaps the item out. If you needed to preserve the order you should do a rotate not a simple swap.
/// <summary>
/// Moves the item to the front of the list if it exists, if it does not it returns false
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="collection"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static bool MoveToFrontOfListWhere<T>(this List<T> collection, Func<T, bool> predicate)
{
if (collection == null || collection.Count <= 0) return false;
int index = -1;
for (int i = 0; i < collection.Count; i++)
{
T element = collection.ElementAt(i);
if (!predicate(element)) continue;
index = i;
break;
}
if (index == -1) return false;
T item = collection[index];
collection[index] = collection[0];
collection[0] = item;
return true;
}

Categories

Resources