Linq Join Not Equal - c#

I have 3 classes:
public class HoteAvail
{
public int HotelID { get; set; }
public string Name { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public int RoomID { get; set; }
public string RoomName { get; set; }
}
public class DAL
{
public static List<HoteAvail> GetAll()
{
return new List<HoteAvail>()
{
new HoteAvail{HotelID=1,Name="Taj",Rooms=new List<Room>(){new Room{RoomID=1,RoomName="Deliux"}}},
new HoteAvail{HotelID=2,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=3,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
};
}
public static List<HoteAvail> GetAllII()
{
return new List<HoteAvail>()
{
new HoteAvail{HotelID=1,Name="Taj",Rooms=new List<Room>(){new Room{RoomID=1,RoomName="Deliux"},new Room{RoomID=1,RoomName="pp"}}},
new HoteAvail{HotelID=4,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=5,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
};
}
}
I want to join the values of DAL.GetAll() and DAL.GetAllII() and result should contain only those values whose HotelID doesnot matches.
The final result set should be like :
new HoteAvail{HotelID=2,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=3,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
new HoteAvail{HotelID=4,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=5,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}}
I tried:
var groupBNames = new HashSet<string>(DAL.GetAll().Select(x => x.HotelID.ToString()));
var filteredEmployees = DAL.GetAllII().Where(x => !groupBNames.Contains(x.HotelID.ToString()));
var resultList = from a in DAL.GetAll()
where !(DAL.GetAllII().Any(HotelID => HotelID == a))
select a;
But I am not getting any success. Thanks in advance.

I'd recommend doing 2 excepts using a custom IEqualityComparer. You can use this method to create the comparer:
// create a comparer to compare HotelAvail objects by hotelId
// see http://www.codeducky.org/10-utilities-c-developers-should-know-part-two/
// for the implementation of EqualityComparers.Create, which is a nice shortcut
var comparer = EqualityComparers.Create<HoteAvail>(ha => ha.HotelId); // compare by hotelId
var results =
// first take all entries in GetAll() NOT IN GetAllII()
DAL.GetAll().Except(DAL.GetAllII(), comparer)
// then add all entries in GetAllII() NOT IN GetAll()
.Concat(DAL.GetAllII()).Except(DAL.GetAll(), comparer);

You could implement a custom IEqualityComparer<HoteAvail>:
public class HoteAvailComparer: IEqualityComparer<HoteAvail>
{
public bool Equals(HoteAvail x, HoteAvail y)
{
return x != null && y != null && x.HotelID == y.HotelID;
}
public int GetHashCode(HoteAvail obj)
{
return obj.HotelID;
}
}
that you can use for Enumerable.Except which is efficient since it's using a set:
var resultList = DAL.GetAll().Except(DAL.GetAllII(), new HoteAvailComparer());
Console.WriteLine(String.Join(",", resultList.Select(h => h.HotelID))); // 2,3
Update
It is giving me HotelId 2, 3 where as I want to join the values of
DAL.GetAll() and DAL.GetAllII() and result should contain only those
values whose HotelID doesnot matchesi.e. The result should have
HotelId 2,3,4,5
Then you need to use Except from both perspectives:
var hotelComparer = new HoteAvailComparer();
var all1 = DAL.GetAll();
var all2 = DAL.GetAllII();
var resultList = all1.Except(all2, hotelComparer).Concat(all2.Except(all1, hotelComparer));
The desired result 2,3,4,5:
Console.WriteLine(String.Join(",", resultList.Select(h => h.HotelID)));
Of course you could also use Concat and GroupBy, but it's less efficient and maintainable:
resultList = all1.Concat(all2).GroupBy(h => h.HotelID)
.Where(g => g.Count() == 1)
.SelectMany(g => g);
You can use the IEqualityComparer<HoteAvail> for many other LINQ methods like GroupBy+Distinct,Join,Intersect etc.

Related

changing the sorted column value change the sorting list , while wants to use the ThenByDescending on first ordered list

I have following code, In which there are list of students , and I want to sort the students first by value column which contains decimal values and after that I want to sort the already sorted list with same column but with different values . Just for understanding , I changed values using foreach loop in the below example.
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var students = new List<Student>()
{
new Student() { StudentId=1,Name = "Alice", Appd = 10, Value = 3.5 },
new Student() { StudentId=2,Name = "Bob", Appd = 10, Value = 3.7 },
new Student() { StudentId=3,Name = "Raul", Appd = 10, Value = 0.1 },
new Student() { StudentId=4,Name = "Charlie", Appd = 0, Value = 3.6 },
new Student() { StudentId=5,Name = "Dave", Appd = 0, Value = 3.9 },
new Student() { StudentId=6,Name = "Emma", Appd = 0, Value = 3.8 }
};
var orderedFields = students.OrderByDescending(x => x.Value);//5,6,2,4,1,3
foreach ( Student s in orderedFields )
{
s.Value = 120;
}
orderedFields = orderedFields.ThenByDescending(x => x.Value);
var newlist1 = orderedFields.Select(X => X.StudentId).ToList();
}
}
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public int Appd { get; set; }
public double Value { get; set; }
}
}
but as soon I change the Value column values it start to change the order of items in list , and if I take this in another list then I will be not able to use the ThenByDescending feature results.
This is sample code to simplify the problem , in real example these columns name come from Database and based on those columns I want to sort the list, first by first column and then by another columns mentioned. For example in MySQL it will be something like this order by col1 desc, col2 desc.
As everybody is comments is discussing the clone and then sort again the list . so here is issue with that approach.
#1. First Set sorting values in Value column for each student :
Value column first contains for each student either 1 or 0 depending on its enrollment date from the cut off date.
#2 Then on same Value column there is CGPA for each student so student should be sorted based on that.
In short all students who apply before cut off date should appear
first and then sort by descending CGPA and then students who apply
after cut off date but those also should come in descending order of
CGPA.
problem is I have only one column here for values, on which need to be sort.
Second edit :
if (_trackConfigManager.RankDependentOnFields.Any())
{
infoFields.ForEach(x => x.PropertyToCompare = _trackConfigManager.RankDependentOnFields.FirstOrDefault().FieldId);
//Order the table withrespect to the firstfield
var orderedFields = infoFields.OrderByDescending(x => x.Value);
//Then skip the first element and order the rest of the fields by descending.
foreach (var field in __trackConfigManager.RankDependentOnFields.RemoveFirst())
{
infoFields.ForEach(x => x.PropertyToCompare = field.FieldId);
orderedFields = orderedFields.ThenByDescending(x => x.Value);
}
//Format a studentId, Rank dictionary from the above orderded table
int rank = 1 + GetMaxRank(programId, statusId);
}
and RankAggregate class as follow :
public class RankAggregate
{
public student_highschool_info HsInfoObj { get; set; }
public student_interview_info IntInfoObj { get; set; }
public student StuObj { get; set; }
private student_program SpObj { get; set; }
public string PropertyToCompare { get; set; }
public bool IsDateTimeField { get; set; }
public long StudentId { get; set; }
public int Choice { get; set; }
public double Value
{
get
{
var tokens = PropertyToCompare.Split(new char[] {':'});
if (tokens.Count() > 1)
{
PropertyToCompare = (Choice == 1)
? "student_interview_FirstPrgTotalScore"
: (Choice == 2) ? "student_interview_SecondPrgTotalScore" : "dummy";
}
var fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student_highschool_info), PropertyToCompare);
if (fldInfo != null)
{
if (HsInfoObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_highschool_info),
PropertyToCompare, HsInfoObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_highschool_info),
PropertyToCompare, HsInfoObj) ?? 0;
return Convert.ToDouble(val1);
}
}
fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student_interview_info), PropertyToCompare);
if (fldInfo != null)
{
if (IntInfoObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_interview_info),
PropertyToCompare, IntInfoObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student_interview_info),
PropertyToCompare, this.IntInfoObj) ?? 0;
return Convert.ToDouble(val1);
}
}
fldInfo = ReflectionUtility.GetPublicPropertyName(typeof(student), PropertyToCompare);
if (fldInfo != null)
{
if (StuObj == null)
return 0;
IsDateTimeField = (fldInfo.PropertyType == typeof(DateTime?));
if (IsDateTimeField)
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student),
PropertyToCompare, StuObj) ?? 0;
var dt = DateTime.Parse(val1.ToString());
return -Convert.ToDouble(dt.Ticks);
}
else
{
var val1 = ReflectionUtility.GetValueOfPublicProperty(typeof(student),
PropertyToCompare, this.StuObj) ?? 0;
return Convert.ToDouble(val1);
}
}
return 0.0;
}
}
public RankAggregate(long studentId, student_highschool_info _hsInfo, student_interview_info _intInfo, student _profileInfo, student_program _spInfo)
{
StudentId = studentId;
HsInfoObj = _hsInfo;
IntInfoObj = _intInfo;
StuObj = _profileInfo;
SpObj = _spInfo;
if (SpObj != null)
{
Choice = SpObj.choice;
}
}
}
Don't know why can't you add another field to the Student class, anyway since you can't do that, you have to fix these values in some places, for example using a tuple:
var studentsWithValues = students.Select(s => (s, s.Value))
.ToList();
Then after changing the values, you can sort the above array:
var orderedFields = studentsWithValues.OrderByDescending(t => t.Value)
.ThenByDescending(t => t.s.Value)
.Select(t => t.s)
Update for uncertain columns
Bind each student object with a list of values:
var studentsWithValues = students.Select(s => new
{
Student = s,
Values = new List<double> { s.Value }
})
.ToList();
After the values are updated, append each value to the binded list:
UpdateValues();
studentsWithValues.ForEach(t => t.Values.Add(t.Student.Value));
Then you can sort these values:
var e = studentsWithValues.OrderByDescending(t => t.Values[0]);
var valueCount = studentsWithValues.First().Values.Count;
for (int i = 1; i < valueCount; i++)
{
int j = i;
e = e.ThenByDescending(t => t.Values[j]);
}
var orderedFields = e.Select(t => t.Student);
Short answer
Use:
var orderedStudents = students
.OrderByDescending(student => student.Value)
.ToList();
foreach (Student student in orderedStudents) etc.
Longer answer
Your orderedFields is not a list, nor a sequence. It is an object that can be enumerated. It has not been enumerated yet. In other words: it is not a Collection<T>, it is an IEnumerable<T>. Usually in descriptions you'll find the phrases: delayed execution or deferred execution.
When you execute foreach (Student s in orderedFields), you start to enumerate the items in students. You don't enumerate the items in the original order, you enumerate them ordered by .Value.
but as soon I change the Value column values it start to change the order of items in list
So, the next time you enumerate orderedFields, the items in students are enumerated again, and ordered again by the changed value of .Value.
If you want to change the source of the items in your LINQ statement, you have to execute the delayed execution by calling one of the LINQ methods that doesn't return IEnumerable<T>, like ToList(), ToArray(), ToDictionary(), but also FirstOrDefault(), Sum(), Count(), Any()
By calling one of the non-delayed methods, the source is enumerated and the result is put in a new object. If you change the items in the new object, and use this new object as source for your next LINQ-statement, then the order of the new object is used, not the order in the original object.
Careful: if you put the references of the original items in the new List, and you change the values, you change the original items. If you don't want that, use a Select(student => new {...}) to put the values in a new object. If you change those values, the original students are not affected.

Combine two list elements into single element

I have a list with two elements
element 1:
no:1,
vendor: a,
Description: Nice,
price :10
element 2:
no:1
vendor:a,
Description: Nice,
price:20
i have lot more fields in list elements so i cannot use new to sum the price
if everything is same except price i need to combine two elements into a single element by summing price.
o/p element 1:
no:1,
vendor:a,
Description:Nice,
price:30
Tried below one but not sure how to sum the price and return the entire fields with out using new
list.GroupBy(y => new { y.Description,y.vendor, y.no})
.Select(x => x.ToList().OrderBy(t => t.Price)).FirstOrDefault()
If you prefer LINQ query expressions:
var groupedElements = from element in elements
group element by new
{
element.no,
element.Description,
element.vendor
}
into grouped
select new {grouped, TotalPrice = grouped.Sum(x => x.price)};
The total price is calculated with the final .Sum method call on the grouped elements.
Try following :
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
List<Element> totals = elements.GroupBy(x => x.no).Select(x => new Element()
{
no = x.Key,
vendor = x.FirstOrDefault().vendor,
Description = x.FirstOrDefault().Description,
price = x.Sum(y => y.price)
}).ToList();
}
}
public class Element
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
}
Try following using Clone
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = elements.GroupBy(x => x.no).ToList();
List<Element> totals = new List<Element>();
foreach (var group in groups)
{
Element newElement = (Element)group.FirstOrDefault().Clone();
newElement.price = group.Sum(x => x.price);
totals.Add(newElement);
}
}
}
public class Element : ICloneable
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
public object Clone()
{
return this;
}
}
}
Willy-nilly you have to create Key which has 3 properties;
If you don't like the current solution with anonymous class
list
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
...
You can do it in different way, e.g. with a help of unnamed tuple:
list
.GroupBy(y => Tuple.Create(
y.Description,
y.vendor,
y.no)
)
...
Or named tuple (see https://learn.microsoft.com/en-us/dotnet/csharp/tuples for details):
list
.GroupBy(y => (
Description : y.Description,
vendor : y.vendor,
no : y.no)
)
...
Or even tailored class. What's matter the most, however, is that you can't just get First item from the group
but should create a new instance. Another issue is premature materialization: .ToList() when you then get rid of this new born list and keep on querying with .OrderBy(...)
var result = result
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
.Select(group => MyObject() { //TODO: put the right syntax here
Description = group.Key.Description,
vendor = group.Key.vendor,
no = group.Key.no,
price = group.Sum(item => item.price) // you want to sum prices, right?
});
You need to create a custom IEqualityComparer, which when passed into the GroupBy clause, will group the items according to your needs.
Asuming the following sample class:
public class Element
{
public int no { get; set; }
public string vendor { get; set; }
public string Description { get; set; }
public decimal price { get; set; }
}
You can implement the following IEqualityComparer which using Reflection will compare every Propertypresent in the Element class except the ones defined in the Linq Where clause, in this case "price". Bear in mind further customizations could be required.
public class ElementComparer : IEqualityComparer<Element>
{
public bool Equals(Element a, Element b) => typeof(Element).GetProperties()
.Where(p => p.Name != "price")
.All(p => p.GetValue(a).Equals(p.GetValue(b)));
public int GetHashCode(Element obj) => obj.no.GetHashCode();
}
Then simply group them this way
list.GroupBy(x => x, new ElementComparer()).Select(g =>
{
// Here you need to either clone the first element of the group like
// #jdweng did, or create a new instance of Element like I'm doing below
Element element = new Element();
foreach (var prop in element.GetType().GetProperties())
{
if (prop.Name == "price")
{
prop.SetValue(element, g.Sum(y => y.price));
}
else
{
prop.SetValue(element, prop.GetValue(g.First()));
}
}
return element;
});
I think what you're trying to do is write dynamic code that groups by all properties except for the property you want to sum. This solution should work, though I loath to use reflection. A more performant method would be to use expression trees to generate an aggregation delegate that you reuse, but that is very involved. This should do the trick:
Edit: There's another answer that also seems to work. Mine assumes you will want to do this with any collection regardless of type. Doesn't require ICloneable or a type-specific IEqualityComparer<T>, though, as a slight trade-off, the other one will likely perform better in very large datasets.
static T[] GetGroupSums<T>(IEnumerable<T> collection, string sumPropertyName) where T : new()
{
//get the PropertyInfo you want to sum
//var sumProp = (PropertyInfo)((MemberExpression)((UnaryExpression)memberExpression.Body).Operand).Member;
var sumProp = typeof(T).GetProperty(sumPropertyName);
//get all PropertyInfos that are not the property to sum
var groupProps = typeof(T).GetProperties().Where(x => x != sumProp).ToArray();
//group them by a hash of non-summed properties (I got this hash method off StackExchange many years back)
var groups = collection
.GroupBy(x => GetHash(groupProps.Select(pi => pi.GetValue(x)).ToArray()))
.Select(items =>
{
var item = new T();
var firstItem = items.First();
//clone the first item
foreach (var gp in groupProps)
{
gp.SetValue(item, gp.GetValue(firstItem));
}
//Get a decimal sum and then convert back to the sum property type
var sum = items.Sum(_item => (decimal)Convert.ChangeType(sumProp.GetValue(_item), typeof(decimal)));
sumProp.SetValue(item, Convert.ChangeType(sum, sumProp.PropertyType));
//If it will always be int, just do this
//var sum = items.Sum(_item => (int)sumProp.GetValue(_item));
//sumProp.SetValue(item, sum);
return item;
});
return groups.ToArray();
}
//I got this hash method off StackExchange many years back
public static int GetHash(params object[] args)
{
unchecked
{
int hash = 17;
foreach (object arg in args)
{
hash = hash * 23 + arg.GetHashCode();
}
return hash;
}
}
Use it like this:
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 2, vendor = "a", Description = "Nice", price = 15},
new Element() { no = 2, vendor = "b", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = GetGroupSums(elements, nameof(Element.price));

How could distinct multi array list on c#

Consider objModels is object of ChildModel class
public class ChildModel
{
public string Title { get; set; }
public long Id { get; set; }
}
It has some data like:
title,id: a,1 b,1 a,1
Two of these data are same, and after distinct it could be like: a,1 b,1
Question is how could I distinct it on c#
List<ChildModel> obj= objModels.ToList();
Also these aren't help
objModels.Distinct();
obj.Distinct();
You could use a library named MoreLINQ
This is the query you could use with MoreLINQ to find elements that are distinct by multiple properties:
var query = objModels.DistinctBy(p => new { p.Id, p.Title});
try this:
var answer= obj.DistinctBy(p => new { p.Id , p.Title });
and check this link for other way
I would do the following:
namespace ConsoleApplication7
{
class Program
{
static void Main()
{
List<ChildModel> list = new List<ChildModel>();
list.Add(new ChildModel { Title = "a", Id = 1 });
list.Add(new ChildModel { Title = "b", Id = 1 });
list.Add(new ChildModel { Title = "a", Id = 1 });
var x = list.Distinct(new ChildModelComparer()).ToList();
var y = x; //This has only got two child items.
}
}
class ChildModelComparer : IEqualityComparer<ChildModel>
{
public bool Equals(ChildModel x, ChildModel y)
{
return x.Id.Equals(y.Id) && x.Title.Equals(y.Title);
}
public int GetHashCode(ChildModel obj)
{
if (string.IsNullOrEmpty(obj.Title) && obj.Id == 0)
{
return 0;
}
return $"{obj.Id}{obj.Title}".GetHashCode();
}
}
public class ChildModel
{
public string Title { get; set; }
public long Id { get; set; }
}
}
you can use .GroupBy:
var result= obj
.GroupBy(p => p.Title)
.Select(g => g.First()) // choose which one
.ToList();
edit: if you want to GroupBy more than one Property you can just change p.Title to new {p.Title, p.Id} like so
var result= obj
.GroupBy(p => new {p.Title, p.Id})
.Select(g => g.First()) // choose which one
.ToList();

How to compare all values in an object without repeating if statements?

I am trying to compare all possible values in a list of objects like this:
public class Object21
{
int Id,
bool firstbool,
bool secondbool
}
I would loop through the objects and compare them like this:
List<Object1> objects;
foreach(var o in objects)
{
if(firstbool && secondbool)
....
if(firstbool && !secondbool)
....
if(!firstbool && secondbool)
....
if(!firstbool && !secondbool)
....
}
This seems ok, but what if the object had several values that you were running through if statements.
public class Object2
{
int Id;
int firstbool;
....
int twentiethbool;
}
Then you would have to write out all of the possible conditional statements and your code would be terribly written and hard to read.
List<Object2> objects2;
foreach(var o in objects2)
{
if(firstbool && secondbool && ... && twentiethbool)
....
if(....)
....
....
....
if(!firstbool && !secondbool && ... && !twentiethbool)
....
}
Is there a simpler way to write the second scenario so that you are not writing every combination of if statements?
In the end I would like to calculate the percentage occurrence of each condition in the list.
To answer the first part of the question (about comparing every combination):
There isn't really a good way to do that, other than write a bunch of if statements. Of course; you probably shouldn't be doing that anyways :)
You could probably use reflection and recursion, but thats going to get messy really fast.
Luckily, to just get the percentage occurrence of each flag, you can just do:
list.Count(i => i.firstbool) / (double)list.Count();
...
first, create a dictionary to save all conditions
var dict = new Dictionary<string, int>{{"001",0},{"010",0} ...}
then, create key use bool values
var key=string.Empty;
key+=firstbool ?"0":"1"
key+=secondbool ?"0":"1"
......
after all, you can know which condition occurred
dict[key]++;
Given a class structure like this:
public class YourClass
{
public int Id { get; set; }
public bool firstbool { get; set; }
public bool secondbool { get; set; }
public bool thirdbool { get; set; }
}
You can use reflection to get all the boolean values (and only bool values) inside the class:
public IEnumerable<bool> GetBools(YourClass obj)
{
return obj.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof (bool))
.Select(x => (bool)x.GetValue(obj, null));
}
Then use LINQ to iterate through the collection, and create a dictionary of combinations and totals:
List<YourClass> objects = new List<YourClass>();
var totals = objects.GroupBy(x => String.Join(",", GetBools(x)))
.ToDictionary(x => x.Key, x => x.Count() / (double)objects.Count);
This will give you a dictionary with each unique combination and the percentage it occurs.
Given this input:
var o = new List<YourClass>
{
new YourClass {firstbool = true, secondbool = true, thirdbool = false},
new YourClass {firstbool = false, secondbool = false, thirdbool = false},
new YourClass {firstbool = true, secondbool = true, thirdbool = false}
};
The result in the dictionary will be:
{["True,True,False", 0.666666666666667]}
{["False,False,False", 0.333333333333333]}
it's probably easier to rewrite your class, storing each condition in an array like follows:
public class MyObject
{
public static int numFields = 20;
public enum Conditions
{
C1, C2, C3, .... C20 //name for each condition, so can set values using descriptive names
};
public Boolean[] BinaryFields = new Boolean[numFields];
public void setCondition(Conditions condition, Boolean value)
{
BinaryFields[(int)condition] = value;
}
public override string ToString()
{
return string.Join(",", BinaryFields);
}
}
then you can calculate the stat by counting what is actually there, instead of numerating through all of the 2^20 possibilities. something like follows:
static void Main(string[] args)
{
//simulation: creat 10 MyObjects
List<MyObject> lst = new List<MyObject>();
for (int i = 0; i < 10; i++)
{
MyObject m = new MyObject();
//example of setting condition
m.setCondition(MyObject.Conditions.C1, true);
lst.Add(m);
}
//calculate stat
var resultCount = new Dictionary<string, int>(); //conditionResult, count
foreach (MyObject m in lst)
{
if (resultCount.ContainsKey(m.ToString()))
{
resultCount[m.ToString()] += 1;
}
else
{
resultCount.Add(m.ToString(), 1);
}
}
//print stat
foreach(KeyValuePair<string, int> entry in resultCount){
Debug.WriteLine("probability for conditoin={0} is {1}", entry.Key, (double)entry.Value / lst.Count);
}
}
If you have some unique action for each boolean properties combination I suggest you to use some kind of string key for your object, generated on those values. Something like "001001", "000000" etc. Then use Dictionary<string, Func<int>> to hold all your unique actions, get and perform the right one by it's key. For example:
public class Object21
{
public int Id { get; set; }
public bool FirstBool { get; set; }
public bool SecondBool { get; set; }
public bool ThirdBool { get; set; }
public bool FourthBool { get; set; }
public bool FifthBool { get; set; }
public bool SixthBool { get; set; }
public void Process()
{
// Perform the action
Actions[Key]();
}
// Returns "001001" like representation of your object
public string Key
{
get
{
return string.Join(string.Empty, GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(bool))
.Select(x => (bool)x.GetValue(this, null) ? "1" : "0" ));
}
}
private static Dictionary<string, Func<int>> Actions
{
get
{
return new Dictionary<string, Func<int>>
{
{"000000", new Func<int>(delegate
{
Console.WriteLine("000000 action performed.");
return 0;
})},
{"000001", new Func<int>(delegate
{
Console.WriteLine("000001 action performed.");
return 1;
})},
{"000010", new Func<int>(delegate
{
Console.WriteLine("000010 action performed.");
return 2;
})},
// More actions
{"111111", new Func<int>(delegate
{
Console.WriteLine("111111 action performed.");
return 63;
})}
};
}
}
}
And then use this in your program like this:
static void Main(string[] args)
{
var list = new List<Object21>
{
// initialize your list
};
foreach (var object21 in list)
{
object21.Process();
}
// Calculate your occurrences (basically what #Grant Winney suggested)
var occurrences = list.GroupBy(o => o.Key).ToDictionary(g => g.Key, g => (g.Count() / (double)list.Count)*100);
foreach (var occurrence in occurrences)
{
Console.WriteLine("{0}: {1}%", occurrence.Key, occurrence.Value);
}
}

Select distinct values from a list using LINQ in C#

I have a collection of Employee
Class Employee
{
empName
empID
empLoc
empPL
empShift
}
My list contains
empName,empID,empLoc,empPL,empShift
E1,1,L1,EPL1,S1
E2,2,L2,EPL2,S2
E3,3,L3,EPL3,S3
E4,4,L1,EPL1,S1
E5,5,L5,EPL5,S5
E6,6,L2,EPL2,S2
I need to take the employees having distinct values
empLoc,empPL,empShift.
Is there is any way to achieve this using LINQ ?
You can use GroupBy with anonymous type, and then get First:
list.GroupBy(e => new {
empLoc = e.empLoc,
empPL = e.empPL,
empShift = e.empShift
})
.Select(g => g.First());
You could implement a custom IEqualityComparer<Employee>:
public class Employee
{
public string empName { get; set; }
public string empID { get; set; }
public string empLoc { get; set; }
public string empPL { get; set; }
public string empShift { get; set; }
public class Comparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
return x.empLoc == y.empLoc
&& x.empPL == y.empPL
&& x.empShift == y.empShift;
}
public int GetHashCode(Employee obj)
{
unchecked // overflow is fine
{
int hash = 17;
hash = hash * 23 + (obj.empLoc ?? "").GetHashCode();
hash = hash * 23 + (obj.empPL ?? "").GetHashCode();
hash = hash * 23 + (obj.empShift ?? "").GetHashCode();
return hash;
}
}
}
}
Now you can use this overload of Enumerable.Distinct:
var distinct = employees.Distinct(new Employee.Comparer());
The less reusable, robust and efficient approach, using an anonymous type:
var distinctKeys = employees.Select(e => new { e.empLoc, e.empPL, e.empShift })
.Distinct();
var joined = from e in employees
join d in distinctKeys
on new { e.empLoc, e.empPL, e.empShift } equals d
select e;
// if you want to replace the original collection
employees = joined.ToList();
You can try with this code
var result = (from item in List
select new
{
EmpLoc = item.empLoc,
EmpPL= item.empPL,
EmpShift= item.empShift
})
.ToList()
.Distinct();
I was curious about which method would be faster:
Using Distinct with a custom IEqualityComparer or
Using the GroupBy method described by Cuong Le.
I found that depending on the size of the input data and the number of groups, the Distinct method can be a lot more performant. (as the number of groups tends towards the number of elements in the list, distinct runs faster).
Code runs in LinqPad!
void Main()
{
List<C> cs = new List<C>();
foreach(var i in Enumerable.Range(0,Int16.MaxValue*1000))
{
int modValue = Int16.MaxValue; //vary this value to see how the size of groups changes performance characteristics. Try 1, 5, 10, and very large numbers
int j = i%modValue;
cs.Add(new C{I = i, J = j});
}
cs.Count ().Dump("Size of input array");
TestGrouping(cs);
TestDistinct(cs);
}
public void TestGrouping(List<C> cs)
{
Stopwatch sw = Stopwatch.StartNew();
sw.Restart();
var groupedCount = cs.GroupBy (o => o.J).Select(s => s.First()).Count();
groupedCount.Dump("num groups");
sw.ElapsedMilliseconds.Dump("elapsed time for using grouping");
}
public void TestDistinct(List<C> cs)
{
Stopwatch sw = Stopwatch.StartNew();
var distinctCount = cs.Distinct(new CComparerOnJ()).Count ();
distinctCount.Dump("num distinct");
sw.ElapsedMilliseconds.Dump("elapsed time for using distinct");
}
public class C
{
public int I {get; set;}
public int J {get; set;}
}
public class CComparerOnJ : IEqualityComparer<C>
{
public bool Equals(C x, C y)
{
return x.J.Equals(y.J);
}
public int GetHashCode(C obj)
{
return obj.J.GetHashCode();
}
}
Try,
var newList =
(
from x in empCollection
select new {Loc = x.empLoc, PL = x.empPL, Shift = x.empShift}
).Distinct();

Categories