Here’s the premise. Suppose we have a cookbook that contains all possible recipes with their names and needed ingredients. Using Linq it should be easy to query the cookbook and, for example, only show those recipes you can make given one or more ingredients you have at your house. My solution is a) extremely nice or b) very strange, bloated and way wrong. I leave it up to you and make sure to message me if you have a much nicer solution…because franky, I’m not a big fan of what follows (I have the itching sensation that using the correct binary logic I can solve this problem much easier…).
Using a bitflag enumeration
In the old MFC days you couldn’t get around using bitflags every 2 lines or code, or so it seemed. To solve this problem, we are again using enumeration that can act as bitflags: meaning you can add 2 or more items together in one variable and still identify the singular items. What you need for this is an enumeration with the [Flags] attribute, which basically sas that this enumeration can be used as a bit field (i.e. a set of flags).A very nice tutorial on bitflags can be found here: http://www.abhisheksur.com/2010/10/make-your-enumeration-to-bit-flags.html .
Let’s create this enumeration:
[Flags]
enum IngredientsEnum { None=0, Spinach=1, Chicken=2, Tomato=4 }
We would like to be able to combine one or more ingredients and be able to filter our recipes in different ways. To be able to do that, take care that you:
- Have an explicit item ‘None’ which means “no flags are set” (thank MSDN for the tip)
- All items need to be powers of 2
Make a small cookbook
We now create a CookBook that contains our recipes
List<Recipe> CookBook= new List<Recipe>()
{
new Recipe(){ Name= “Raw tomato”,
Ingredients= IngredientsEnum.Tomato},
new Recipe(){ Name= “Fried Chicken”,
Ingredients= IngredientsEnum.Chicken },
new Recipe(){ Name= “Mario’s lovely pizza”,
Ingredients= IngredientsEnum.Chicken | IngredientsEnum.Spinach},
new Recipe(){ Name= “Tim’s secret dish”,
Ingredients= IngredientsEnum.Chicken | IngredientsEnum.Spinach | IngredientsEnum.Tomato}
};
And of course our Recipe class is :
public class Recipe{
public string Name { get; set; }
public IngredientsEnum Ingredients { get; set; }
}
Making LINQ queries on the cookbook
There are three possible sorts of queries we now would like to do on our cookbook:
- Show all recipes of which at least one chosen ingredient is present
- Show only recipes that only contain given ingredients
- Show all recipes that at least contain all chosen ingredients.
Suppose we define the following filter that we use to show the previous three types of queries:
IngredientsEnum filterIngredients =
IngredientsEnum.Chicken | IngredientsEnum.Spinach;
At least one query
To show all recipes that contains at least one of the ingredients in the filter, we need to perform a ‘bitwise and’ each recipe with the filter. If this addition returns a value larger than 0, we know that at least one ingredient is present.
The following query will thus return all recipes in the cookbook except for the “Raw tomato”:
var possiblities=
from c in CookBook
where (c.Ingredients & filterIngredients)>0
select c;
Only given ingredients query
If we only would like to know what recipe only contains those ingredients of our filter, we have the following query (which will only return “Mario’s Lovely Pizza”):
var possiblities =
from c in CookBook
where c.Ingredients == filterIngredients
select c;
This is the reason why each ingredient in our enumeration should be powers of 2. If we simply incremented each enumitem with 1, the previous query could return other recipes of which the bitwise and of the ingredients by ‘chance’ gives a value equal to that of filter.
At least query
To show all recipes that contain all the ingriedients in the filter, but also those that contain more than those alone, we can use the HasFlag() method. The following query, applied to our Chick with Spinach filter will thus return both “Mario’s Lovely Pizza” and “Tim’s secret dish”:
var possiblities =
from c in CookBook
where c.Ingredients.HasFlag(filterIngredients)
select c;
Small tip: Use bit shifts instead of numbers, so you don’t have to write all powers of two. Eg 1 << 2 instead of 4.
And for C you can use (c.Ingredients & filter) == filter. I don't know about HasFlag, but this just seems nicer…
LikeLike
Seems useful, but you have to be aware of the limitations of Enums: they can’t be easily extended without changing the source code and flag enums only support a limited number of values, because of their bitwise nature. I wouldn’t use them to represent ingredients, for example, because future recipes may need to extend the existing collection of available ingredients.
LikeLike
Good mini code jewel now that I ran into that issue myself.
LikeLike
Can’t help you bro…but was fun reading anyway. 🙂
LikeLike