I have collection of items which are having one enum property list.
Original property looks like
public class Content {
List<State> States {get; set;}
}
where 'State' is enum with almost 15 options.
While iterating collection of Content objects, I want to check it States property has certain values like State.Important and State.Updated exists in States and set another string from it.
something like
if(item.States.Has(State.Important) && item.States.Has(State.Updated))
string toProcess = "Do";
How to do this using Linq or Lambda ?
This should work if you must use Linq:
if (item.States.Any(state => state == State.Important) && item.States.Any(state => state == State.Updated))
Otherwise just use Contains() like #ElRonnoco says.
(However if your states are flags (powers of 2), then this answer will be slightly different.)
The trouble with this approach is that it iterates over the collection fully twice if neither of the states are set. If this happens often, it will be slower than it could be.
You can solve it without linq in a single pass like so:
bool isUpdated = false;
bool isImportant = false;
foreach (var state in item.States)
{
if (state == State.Important)
isImportant = true;
else if (state == State.Updated)
isUpdated = true;
if (isImportant && isUpdated)
break;
}
if (isImportant && isUpdated)
{
// ...
}
This is unlikely to be an issue unless you have very large lists which often don't have either of the target states set, so you're probably best off using El Ronnoco's solution anyway.
If you have a lot of states to deal with, you could simplify things by writing an extension method like so:
public static class EnumerableExt
{
public static bool AllPredicatesTrueOverall<T>(this IEnumerable<T> self, params Predicate<T>[] predicates)
{
bool[] results = new bool[predicates.Length];
foreach (var item in self)
{
for (int i = 0; i < predicates.Length; ++i)
if (predicates[i](item))
results[i] = true;
if (results.All(state => state))
return true;
}
return false;
}
I had some difficulty coming up for a name for this. It will return true if for each predicate there is at least one item in the sequence for which the predicate is true. But that's a bit long for a method name... ;)
Then your example would become:
if (item.States.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Here's some sample code that uses it:
enum State
{
Unknown,
Important,
Updated,
Deleted,
Other
}
void run()
{
IEnumerable<State> test1 = new[]
{
State.Important,
State.Updated,
State.Other,
State.Unknown
};
if (test1.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Console.WriteLine("test1 passes.");
else
Console.WriteLine("test1 fails.");
IEnumerable<State> test2 = new[]
{
State.Important,
State.Other,
State.Other,
State.Unknown
};
if (test2.AllPredicatesTrueOverall(s => s == State.Important, s => s == State.Updated))
Console.WriteLine("test2 passes.");
else
Console.WriteLine("test2 fails.");
// And to show how you can use any number of predicates:
bool result = test1.AllPredicatesTrueOverall
(
state => state == State.Important,
state => state == State.Updated,
state => state == State.Other,
state => state == State.Deleted
);
}
But perhaps the easiest is to write an extension method for IEnumerable<State> (if you only have the one state enum to worry about):
public static class EnumerableStateExt
{
public static bool AllStatesSet(this IEnumerable<State> self, params State[] states)
{
bool[] results = new bool[states.Length];
foreach (var item in self)
{
for (int i = 0; i < states.Length; ++i)
if (item == states[i])
results[i] = true;
if (results.All(state => state))
return true;
}
return false;
}
}
Then your original code will become:
if (item.States.AllStatesSet(State.Important, State.Updated))
and you can easily specify more states:
if (item.States.AllStatesSet(State.Important, State.Updated, State.Deleted))
You don't need Linq. I don't thinq
if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
string toProcess = "Do";
http://msdn.microsoft.com/en-us/library/bhkz42b3.aspx
List has a Contains method, so your code would be
if(item.States.Contains(State.Important) && item.States.Contains(State.Updated))
string toProcess = "Do";
I see no real benefit in using Linq or a lambda expression here...
You could go with
!(new List<States>{State.Important, State.Updated}.Except(item.States).Any());
It's not really shorter, but easier if you have a huge number of states to check.
As long as you want to check that the item has all states needed, you just have to add new States to the first list.
var res = (from items in item
where items.States.Has(State.Important) && items.States.Has(State.Updated)
select new { NewProcess = "Do" }).ToList();
foreach (var result in res)
{
string result = result.NewProcess
}
Try this
Maybe you could consider using your enum as a set of flags, i.e. you can combine multiple states without having a list:
[Flags]
public enum State
{
Important = 1,
Updated = 2,
Deleted = 4,
XXX = 8
....
}
public class Content
{
public State MyState { get; set; }
}
if ((myContent.MyState & State.Important) == State.Important
&& (myContent.MyState & State.Updated) == State.Updated)
{
// Important AND updated
}
Some sort of following implementation
Content obj = new Content();
obj.States = SomeMethod();
if(obj.States.Any(h => h == State.Important) && obj.States.Any(h => h == State.Updated))
{
string toProcess = "Do";
}
Related
First of all, I'm not asking for code or for anyone doing my job for me. Just need some ideas on how these kind of rules can be applied.
I have a simple view that posts some data to the controller by unique pair (key - value). The controller receives that data into a Dictionary demoData.
In my database I have a simple table (Products) with an id and a name.
In my database I also have a table (Rules) that contains data_key, a data_value and a product_ID. The idea with this table is that when the controller receives the data values, it checks against this table and IF data_key is equal to the data_value stored, it adds the product_ID to a new dict. Something like this:
public Dictionary<int, string> testC([FromBody]Dictionary<string,string> demoData)
{
var getRules = AsDynamic(App.Data["Rules"]);
// contains Link_datakey, Link_datavalue, Link_product
var getProducts = AsDynamic(App.Data["Products"]);
// contains Product_id, Product_name
Dictionary<int, string> testdict = new Dictionary<int, string>();
var product = 0;
foreach(var r in getRules){
if (demoData.ContainsKey(r.Link_datakey)) {
if (demoData[r.Link_datakey] == r.Link_datavalue) {
product = Convert.ToInt32(r.Link_product);
if (!testdict.ContainsKey(product)) {
testdict.Add(product, getProducts.Where(i => i.id_source == r.Link_product).First().Product_name);
}
}
}
}
return testdict;
}
This works fine but there are two severe limitations:
Rule must be of "equal" type. If I wanted something like > or < I would need to create a different if clause for each of the options, since the operator can not be variable (I assume)
This was created assuming only one rule would be checked for any each loop. I now need multiple rules checked like:
if(some_datakey < some_datavalue AND someother_datakey == someother_datavalue) {
// add the product key to the dict
}
And the number of rules must be variable... Sometimes it's only one, but in other products it may require 2 or 3.
Can I ask for some opinions on how to implement this?
The first thing would be the rules table structure. It would no longer be enough a simple key - value - product. Probably something like rule_group_id - key - value so that the rules can be grouped, and then a new table with rule_group_id and the product to be added if all conditions return true.
Then the loops would need to accommodate the multiple rules check, and here I have no idea on how to proceed.
Any ideas you can share?
Here's an half-baked solution I came up with.
Not very happy with it.
Needs a lot of syntax review (like the TryGetValue suggested).
public Dictionary<int, string> testC([FromBody]Dictionary<string,string> demoData)
{
var getRules = AsDynamic(App.Data["Rules"]);
// Link_group Link_datakey Link_datavalue Link_operator
var getRuleGroups = AsDynamic(App.Data["RuleGroups"]);
// Link_group Link_product Link_type
var getProducts = AsDynamic(App.Data["Products"]);
// Product_id Product_name
Dictionary<int, string> testdict = new Dictionary<int, string>();
var product = 0;
var rulegetcount = 0;
var rulesetcount = 0;
int tryparsedataint;
bool tryparsedatabool;
int tryparsevalueint;
bool tryparsevaluebool;
foreach(var rg in getRuleGroups.Where(t => t.Link_type == "add")){
rulegetcount = getRules.Where(r => r.Link_group == rg.Link_group).Count();
rulesetcount = 0;
foreach(var r in getRules.Where(r => r.Link_group == rg.Link_group)){
if (demoData.ContainsKey(r.Link_datakey)) {
if(r.Link_operator == "==") {
if (demoData[r.Link_datakey] == r.Link_datavalue) {
rulesetcount ++;
}
} else if(r.Link_operator == "<") {
tryparsedatabool = Int32.TryParse(demoData[r.Link_datakey], out tryparsedataint);
tryparsevaluebool = Int32.TryParse(r.Link_datavalue, out tryparsevalueint);
if(tryparsedatabool && tryparsevaluebool) {
if (tryparsedataint < tryparsevalueint) {
rulesetcount ++;
}
}
} else if(r.Link_operator == ">") {
tryparsedatabool = Int32.TryParse(demoData[r.Link_datakey], out tryparsedataint);
tryparsevaluebool = Int32.TryParse(r.Link_datavalue, out tryparsevalueint);
if(tryparsedatabool && tryparsevaluebool) {
if (tryparsedataint > tryparsevalueint) {
rulesetcount ++;
}
}
} else if(r.Link_operator == "!=") {
if (demoData[r.Link_datakey] != r.Link_datavalue) {
rulesetcount ++;
}
}
}
}
if (rulegetcount == rulesetcount) {
product = Convert.ToInt32(rg.Link_product);
if (!testdict.ContainsKey(product)) {
testdict.Add(product, getPropList.Where(i => i.Product_id == rg.Link_product).First().Product_name);
}
}
}
return testdict;
}
I have a big array of 700 groups. I need to sort the array by specific rules:
We need to sort by company name
Hierarchy depends on the number of points in the groups.Name
There are a lot of records with same company name, but the first one to show from specific company must contain ".All". After this record, we need to put all others with the same name ordered by "1."
Specific case when there is a position directly applied to the company
Example:
groups[0].CompanyName = "Acompany"
groups[1].CompanyName = "Acompany"
groups[2].CompanyName = "Acompany"
groups[3].CompanyName = "Acompany"
groups[4].CompanyName = "Acompany"
groups[5].CompanyName = "Bcompany"
groups[6].CompanyName = "Bcompany"
groups[7].CompanyName = "Bcompany"
groups[0].Name = "Acompany.All" //(root)
groups[1].Name = "D.Acompany.example" //this is the specific case (leaf)
groups[2].Name = "Acompany.ABC"//(group)
groups[3].Name = "D.Acompany.ABC.PrimaryTeacher" //(leaf)
groups[4].Name = "Acompany.ABC.Something"//(group)
groups[5].Name = "Bcompany.All" //(root)
groups[6].Name = "Bcompany.Sites"//(group)
groups[7].Name = "Bcompany.Sites.example" //(leaf)
The example shows how the array should look like after sort. It's really complicated, I hope I have managed to explain it.
For now I have achieved:
There are 2 problems :
1.D.A.1stFloor.Cleaner must be under A.1stFloor
2.D.B.Society.Worker must be under B.Society
My code for now :
Array.Sort(groups, (a, b) =>
{
if (a.CompanyName != b.CompanyName)
{
return a.CompanyName.CompareTo(b.CompanyName);
}
if (a.Name.Contains(".All"))
{
return -1;
}
if (b.Name.Contains(".All"))
return 1;
if (a.Name.StartsWith("D.") && a.Name.Count(x => x == '.') == 2)
return -1;
if (b.Name.StartsWith("D.") && b.Name.Count(x => x == '.') == 2)
return 1;
if (a.Name.Count(x => x == '.') == 1)
return -1;
if (b.Name.Count(x => x == '.') == 1)
return 1;
if (a.Name.StartsWith("D") && a.Name.Count(x => x == '.') == 3) //needs to be moved I guess
return -1;
if (b.Name.StartsWith("D") && b.Name.Count(x => x == '.') == 3)//needs to be moved I guess
return 1;
return a.Name.CompareTo(b.Name);
});
You construct each company as a tree, like the image below for ACompany, base on your rules and string processing functions:
Then you just need to use a Depth-First Tree traversal algorithm to get the order.
I cannot imagine how complicated your sorting rules is, sorry. But, I would like to suggest you to take advantage of existing sorting function.
Construct each group as an object, I believe that you've done that:
public class Company
{
public string CompanyName { get; set; }
public string Name { get; set; }
}
So, your group is just an array or list of Companies.
var group = new List<Company>();
Then, you implement your sorting rules in your defined comparer:
public class CoordinatesBasedComparer : IComparer<Company>
{
public int Compare(Company a, Company b)
{
//your sorting rules implemented here
}
}
Finally, you just call:
var comparer = new CoordinatesBasedComparer();
group.Sort(comparer);
Hope this helps.
Your rules are not entirely clear, but here's an example of sorting with a comparator:
Array.Sort(groups, (a, b) =>
{
// highest priority rule
if (a.CompanyName != b.CompanyName)
{
return a.CompanyName.CompareTo(b.CompanyName); // or a custom comparison
}
// more rules ordered by priority
if (a.Name == a.CompanyName + ".All")
return -1; // => "a" comes first
if (b.Name == b.CompanyName + ".All")
return 1; // => "b" comes first
// more rules...
// default rule
return a.Name.CompareTo(b.Name);
});
Title could be misleading, so an example:
I have a class:
class Pair
{
Book Book1;
Book Book2;
}
I have a list of these:
var list = new List<Pair>();
list.Add(new Pair() {
Book1 = new Book() { Id = 123 },
Book2 = new Book() { Id = 456 }
});
list.Add(new Pair() {
Book1 = new Book() { Id = 456 },
Book2 = new Book() { Id = 123 }
});
Now, despite the fact the books are 'flipped', my system should treat these as duplicates.
I need a method to remove one of these 'duplicates' from the list (any one - so let's say the first to make it simple).
What I've Tried
var tempList = new List<Pair>();
tempList.AddRange(pairs);
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
return tempList;
This returns no items (given the example above), as both Pair objects would satisfy the condition in the lambda, I only one to remove one though.
NOTE: This wouldn't happen if I just removed the element from the collection straight away (rather than from a temporary list) - but then I wouldn't be able to iterate over it without exceptions.
You can set up an IEqualityComparer<Pair> concrete class and pass that to the .Distinct() method:
class PairComparer : IEqualityComparer<Pair>
{
public bool Equals(Pair x, Pair y)
{
return (x.Book1.Id == y.Book1.Id && x.Book2.Id == y.Book2.Id)
|| (x.Book1.Id == y.Book2.Id && x.Book2.Id == y.Book1.Id);
}
public int GetHashCode(Pair obj)
{
return obj.Book1.Id.GetHashCode() ^ obj.Book2.Id.GetHashCode();
}
}
And then use it like so:
var distinctPairs = list.Distinct(new PairComparer());
The problem is that you are removing the both duplicates.
Try this:
var uniquePairs = list.ToLookup( p => Tuple.Create(Math.Min(p.Book1.Id, p.Book2.Id), Math.Max(p.Book1.Id, p.Book2.Id)) ).Select( g => g.First() ).ToList();
I would use the following
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id
&& o.Book1.Id > o.Book2.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
This will specifically remove the duplicate that is "out of order". But this (and your original) will fail if the duplicate pairs have the books in the same order.
A better solution (since we're looping over ever pair anyways) would be to use a HashSet
var hashSet = new HashSet<Tuple<int,int>>();
foreach (var item in pairs)
{
var tuple = new Tuple<int,int>();
if (item.Book1.Id < item.Book2.Id)
{
tuple.Item1 = item.Book1.Id;
tuple.Item2 = item.Book2.Id;
}
else
{
tuple.Item1 = item.Book2.Id;
tuple.Item2 = item.Book1.Id;
}
if (hashSet.Contains(tuple))
{
tempList.Remove(dup);
}
else
{
hashSet.Add(tuple);
}
}
I've managed to find a solution, but it's one I'm not happy with. It seems too verbose for the job I'm trying to do. I'm now doing an additional check to see whether a duplicate has already been added to the list:
if(toRemove != null && tempList.Any(o => o.Book1.Id == toRemove.Book2.Id
&& o.Book2.Id == toRemove.Book1.Id))
tempList.Remove(toRemove);
I'm very much open to alternative suggestions.
I've tried to search SO for solutions and questions that could be similar to my case.
I got 2 collections of objects:
public class BRSDocument
{
public string IdentifierValue { get; set;}
}
public class BRSMetadata
{
public string Value { get; set;}
}
I fill the list from my datalayer:
List<BRSDocument> colBRSDocuments = Common.Instance.GetBRSDocuments();
List<BRSMetadata> colBRSMetadata = Common.Instance.GetMessageBRSMetadata();
I now want to find that one object in colBRSDocuments where x.IdentifierValue is equal to the one object in colBRSMetadata y.Value. I just need to find the BRSDocument that matches a value from the BRSMetadata objects.
I used a ordinary foreach loop and a simple linq search to find the data and break when the value is found. I'm wondering if the search can be done completely with linq?
foreach (var item in colBRSMetadata)
{
BRSDocument res = colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value);
if (res != null)
{
//Do work
break;
}
}
Hope that some of you guys can push me in the right direction...
Why not do a join?
var docs = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select d;
If there's only meant to be one then you can do:
var doc = docs.Single(); // will throw if there is not exactly one element
If you want to return both objects, then you can do the following:
var docsAndData = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select new
{
Doc = d,
Data = m
};
then you can access like:
foreach (var dd in docsAndData)
{
// dd.Doc
// dd.Data
}
Use Linq ?
Something like this should do the job :
foreach (var res in colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).Where(res => res != null))
{
//Do work
break;
}
If you are just interested by the first item, then the code would be :
var brsDocument = colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).FirstOrDefault(res => res != null);
if (brsDocument != null)
//Do Stuff
part of the code:
Dictionary<Calculation, List<PropertyValue>> result = new Dictionary<Calculation, List<PropertyValue>>();
while (reader != null && reader.Read()) //it loops about 60000, and it will be bigger
{
#region create calc and propvalue variables
//...
#endregion
//this FirstOrDefault needs a lot of time
tmpElementOfResult = result.Keys.FirstOrDefault(r => r.InnerID == calc.InnerID);
if (tmpElementOfResult == null)
{
result.Add(calc, new List<PropertyValue> { propValue });
}
else
{
result[tmpElementOfResult].Add(propValue);
}
}
Could you give me some idea how to make it faster, because now it's approximately 25 sec :( ?
It sounds like you should have a dictionary from the type of calc.InnerID, instead of a Dictionary<Calc, ...>. That way you can do the lookup far more quickly. Do you actually need to store the Calc itself at all, or are you only interested in the ID?
For example:
Dictionary<Guid, List<PropertyValue>> result =
new Dictionary<Guid, List<PropertyValue>>();
while (reader.Read())
{
// Work out calc
List<PropertyValue> list;
if (!result.TryGetValue(calc.InnerID, out list))
{
list = new List<PropertyValue>();
result[calc.InnerID] = list;
}
list.Add(propValue);
}
Alternatively, if you can convert the reader to an IEnumerable<Calc> you could use:
Lookup<Guid, PropertyValue> result = items.ToLookup(x => x.InnerID,
// Or however you get it...
x => x.PropertyValue);
EDIT: It sounds like two Calc values should be deemed equal if they have the same InnerID, right? So override Equals and GetHashCode within Calc to refer to the InnerID. Then you can just use:
Lookup<Calc, PropertyValue> result = items.ToLookup(x => x,
// Or however you get it...
x => x.PropertyValue);
... or you can use code like the first snippet, but with a Dictionary<Calc, ...>:
Dictionary<Calc, List<PropertyValue>> result =
new Dictionary<Calc, List<PropertyValue>>();
while (reader.Read())
{
// Work out calc
List<PropertyValue> list;
if (!result.TryGetValue(calc, out list))
{
list = new List<PropertyValue>();
result[calc] = list;
}
list.Add(propValue);
}
instead of
tmpElementOfResult = result.Keys.FirstOrDefault(r => r.InnerID == calc.InnerID);
use
result.ContainsKey(calc.InnerId);
to check if a key is present.
Is it possible to do something like this:
lookUpForResult = result.ToLookup(x => x.Key.InnerID, x => x.Value);
if (lookUpForResult.Contains(calc.InnerID))
{
result.Add(calc, new List<PropertyValue> { propValue });
}
else
{
(lookUpForResult[calc.InnerID]).Add(propValue);
}