Sort List with Linq, with groups alphabetic - c#

I have a class Guest
class Guest
{
bool f = true;
bool g = false;
bool s = false;
string name = "";
}
And a List where all the included -> var g = new List<Guest>();
It can be only one of the booleans true.
At the start come all the f guest then g guest in the middle and at last the s guest.
But all guest must be sorted alphabetic in f or g or in the a group.
Maybe so?
var query = (from Guest in GuestList
orderby Guest.f, Guest.g, Guest.s, Guest.name
select Guest);
I'm just not it.
Thanks and greetz, Malte

Sounds like pretty typical nested sorting. There is no need to group.
var result = source
.OrderBy(guest =>
guest.f ? 1 :
guest.g ? 2 :
guest.s ? 3 :
4)
.ThenBy(guest => guest.name);
For those who are unfamiliar with the syntax, allow me to read the code.
In the call to OrderBy, there is a lambda function which uses a chained ternary operator to generate a sort key for each row. OrderBy sorts by this sort key.
The result of OrderBy is an IOrderedEnumerable<Guest> and is passed to ThenBy.
ThenBy preserves the sorting of the prior ordering operations and works to break ties.

This should work. The Sort and OrderBy will use the CopmpareTo() method
public class Guest : IComparable<Guest>
{
public bool f { get; set; }
public bool g { get; set; }
public bool s { get; set; }
public string name { get; set; }
public int CompareTo(Guest other)
{
int results = 0;
if (this.f)
{
if (other.f)
{
results = this.name.CompareTo(other.name);
}
else
{
results = 1;
}
}
else
{
if (other.f)
{
results = -1;
}
else
{
if (this.g)
{
if (other.g)
{
results = this.name.CompareTo(other.name);
}
else
{
results = 1;
}
}
else
{
if (other.g)
{
results = -1;
}
else
{
if (this.s)
{
if (other.s)
{
results = this.name.CompareTo(other.name);
}
else
{
results = 1;
}
}
else
{
results = this.name.CompareTo(other.name);
}
}
}
}
}
return results;
}
Below is simpler method which will even work with when more than one property is true. Notice that I used 1,2,4 instead of 1,2,3 as in other solutions. 1,2,3 has issue that there is more than one way of getting 3 when multiple properties are true.
public class Guest : IComparable<Guest>
{
public bool f { get; set; }
public bool g { get; set; }
public bool s { get; set; }
public string name { get; set; }
public int CompareTo(Guest other)
{
int results = 0;
int thisRank = (this.f ? 1 : 0) + (this.g ? 2 : 0) + (this.s ? 4 : 0);
int otherRank = (other.f ? 1 : 0) + (other.g ? 2 : 0) + (other.s ? 4 : 0);
if (thisRank == otherRank)
{
results = this.name.CompareTo(other.name);
}
else
{
results = thisRank.CompareTo(otherRank);
}
return results;
}
}

here is David B's example with more common syntax for the if statements.
var result = source
.OrderBy(guest => { if (guest.f == true) return 1 else
if (guest.g == true) return 2 else
if (guest.s == true) return 3 else return 4;})
.ThenBy(guest => guest.name);

Related

linq Groupby a list by "sublist"

I have a List<Meb> (a bar nesting), each of these nestings have a list of details inside.
All of these bars are unique, because each of element inside is unique by its ID.
Now I want to add a checkbox, in order to group or not all bars that have the same list of details inside (the list of items inside are identical, except their ID, and some parameters I first set to -1 or ""). Here is the function I made in order to do that :
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
List<Meb> retour = new List<Meb>();
foreach(Meb mebOri in mebInput)
{
Meb meb = new Meb();
meb.ID = -1;
meb.Number = mebOri.Number;
meb.Length = mebOri.Length;
meb.Quantity=mebOri.Quantity;
foreach(Repere repOri in mebOri.ListReperes)
{
Repere rep = new Repere();
rep.Name = repOri.Name;
rep.Quantite = repOri.Quantite;
rep.ID = -1;
meb.ListReperes.Add(rep);
}
retour.Add(meb);
}
retour = retour.GroupBy(l => l.ListReperes)
.Select(cl => new Meb
{
ID=-1,
Number = cl.First().Number,
Length = cl.First().Length,
Quantity=cl.Sum(c => c.Quantity),
ListReperes = cl.First().ListReperes,
}).ToList();
return retour;
}
The idea is that:
1st: I create a new List<Meb> that copies the original List<Meb>, for the List<Repere>, I also copy it, but setting the ID to "-1", as others properties that could differ between them.
2nd: I make a group by on the List<Repere>
But on the end no groupby is done, and the output remains the same as the input.
Edit :
I explain better the structure of my objects because it seems it was not clear enough :
Each Meb object represents a beam, each beams contains Repere objects(details), these details have a lot of parameters, most importants are ID, Name, Quantity, concrete example :
ID Name Quantity
Meb1(Quantity1) contains : 11 Repere1 2
20 Repere2 1
25 Repere3 1
Meb2(Quantity2) contains : 12 Repere1 2
24 Repere2 2
28 Repere3 1
Meb3(Quantity3) contains : 31 Repere1 2
18 Repere2 1
55 Repere3 1
So I import my List<Meb>, and I want to group all my Mebs, comparing their details list.
In that case the result would be :
Meb1(Quantity4) contains : 0 Repere1 2
0 Repere2 1
0 Repere3 1
Meb2(Quantity2) contains : 0 Repere1 2
0 Repere2 2
0 Repere3 1
I would recommend that you add some sort of property in your Meb class that hashes all of your ListReperes items, and then group off that.
You can have a look at this link: How to generate a unique hash for a collection of objects independent of their order
IE then you would do:
retour = retour.GroupBy(l => l.HashReperes) and this would provide you a unique grouped list of your lists.
where HashReperes is the property that provides the Hash of the Reperes List.
Use IEquatable. Then you can use the standard linq GroupBy(). See code below
public class Meb : IEquatable<Meb>, INotifyPropertyChanged
{
public int ID { get; set; }
public int Number { get; set; }
public int Length { get; set; }
public int Quantity { get; set;}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
return mebInput.GroupBy(x => x).Select(x => new Meb() {
ID = x.First().ID,
Number = x.First().Number,
Length = x.First().Length,
Quantity = x.Sum(y => y.Quantity)
}).ToList();
}
public bool Equals(Meb other)
{
if ((this.Number == other.Number) && (this.Length == other.Length) && (this.Quantity == other.Quantity))
{
return true;
}
else
{
return false;
}
}
public override int GetHashCode()
{
return ID;
}
}
If you don't want to use IEquatable then use this
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
return mebInput.GroupBy(x => new { number = x.Number, len = x.Length }).Select(x => new Meb()
{
ID = x.First().ID,
Number = x.Key.number,
Length = x.Key.len,
Quantity = x.Sum(y => y.Quantity)
}).ToList();
}
For comparing a List use something like this
public class MyClassA : IEquatable<List<MyClassB>>
{
public List<MyClassB> myClassB { get; set; }
public bool Equals(List<MyClassB> other)
{
if(other == null) return false;
if (this.myClassB.Count() != other.Count()) return false;
var groupThis = this.myClassB.OrderBy(x => x.propertyA).ThenBy(x => x.propertyB).GroupBy(x => x).ToList();
var groupOther = other.OrderBy(x => x.propertyA).ThenBy(x => x.propertyB).GroupBy(x => x).ToList();
if (groupThis.Count() != groupOther.Count) return false;
for (int i = 0; i < groupThis.Count(); i++)
{
if (groupThis[i].Count() != groupOther[i].Count()) return false;
}
return true;
}
public override int GetHashCode()
{
return 0;
}
}
public class MyClassB : IEquatable<MyClassB>
{
public int propertyA { get; set; }
public string propertyB { get; set; }
public bool Equals(MyClassB other)
{
if (other == null) return false;
if ((this.propertyA == other.propertyA) && (this.propertyB == other.propertyB))
{
return true;
}
else
{
return false;
}
}
public override int GetHashCode()
{
return 0;
}
}
On the end, here is the way I could solve the problem :
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
List<Meb> retour = new List<Meb>();
foreach(Meb mebOri in mebInput)
{
Meb meb = new Meb();
meb.ID = -1;
meb.Number = mebOri.Number;
meb.Length = mebOri.Length;
meb.Quantity=mebOri.Quantity;
foreach(Repere repOri in mebOri.ListReperes)
{
Repere rep = new Repere();
rep.Name = repOri.Name;
rep.Quantite = repOri.Quantite;
rep.ID = -1;
meb.ListReperes.Add(rep);
}
retour.Add(meb);
// Here I added a string property, in which I concatenate
//name and quantity of each Repere in my List<Repere>,
//so on the end the "SomeString" parameters will be identical
//for all Meb that have the same List<Repere> (ignoring the IDs).
foreach(Meb meb in retour)
{
meb.SomeString = "";
foreach(RepereNest rep in meb.ListReperes)
{
meb.SomeString += rep.Name + rep.Quantite;
}
}
}
retour = retour.GroupBy(l => l.SomeString)
.Select(cl => new Meb
{
ID=-1,
Number = cl.First().Number,
SomeString=cl.First().SomeString,
Length = cl.First().Length,
Quantity=cl.Sum(c => c.Quantity),
ListReperes = cl.First().ListReperes,
}).ToList();
return retour;
}
Well for now ths is the only way I could find to group not on my parameters(for this no problem), but on parameters inside a List of my object. And I think this method is not so bad, because I also have Lists inside of Repere objects, so I could use the same tip in future. On the end I just don't understand why it is not possible to check when Lists of my objects are equals?

List<object> select use multiple values

I want to find a specific value from a List with the method select.
My code :
public class Calc
{
public int IdCalc { get; set; }
public double Result { get; set; }
public int Number { get; set; }
}
public class Program
{
static void Main()
{
Calc myC1 = new Calc();
List<Calc> liCalc = new List<Calc>();
myC1.IdCalc = -1;
myC1.Result = 20.2;
myC1.Number = 1;
Calc myC2 = new Calc();
myC2.IdCalc = 22;
myC2.Result = 20.2;
myC2.Number = 2;
liCalc.Add(myC1);
liCalc.Add(myC2);
double getResult = ((Calc)(liCalc.Select(Calc => Calc.IdCalc = 22 && Calc.Number = 2))).Result;
Console.ReadKey();
}
}
As you can see my List contains two objects: myC1 and myC2.
I just want to find the value of Result when IdCalc = 22 and Number = 2 thats why I tried to use Select but it's not working with two parameters.
You could use Where, which lets you filter results based on some criteria, however that will return an IEnumerable<Calc>. Since you are only looking for a single result, you should use First which also takes a predicate and only returns the first Calc:
Calc myCalc = liCalc.First(c => c.IdCalc == 22 && c.Number == 2);
double result = myCalc.Result;
This will throw an exception if there is nothing that matches the filter, though. If you're worried about that, use FirstOrDefault which will return null if there is no match.
public class Calc
{
public int IdCalc { get; set; }
public double Result { get; set; }
public int Number { get; set; }
}
public class Program
{
static void Main()
{
Calc myC1 = new Calc();
List<Calc> liCalc = new List<Calc>();
myC1.IdCalc = -1;
myC1.Result = 20.2;
myC1.Number = 1;
Calc myC2 = new Calc();
myC2.IdCalc = 22;
myC2.Result = 20.2;
myC2.Number = 2;
liCalc.Add(myC1);
liCalc.Add(myC2);
double getResult = liCalc.First(item => item.IdCalc == 22 && item.Number == 2).Result; //Note that this will throw an exception if no item in the list satisfies the condition.
Console.ReadKey();
}
You could use the following statement
double getResult = liCalc.Where(Calc => Calc.IdCalc = 22 && Calc.Number = 2))).Select(y=>y.Result).FirstOrDefault();
Essentially using Where() followed by Select().

How do I Find an object up the list I am iterating through

I am iterating through a List of objects of Type "prvEmployeeIncident".
The object has the following properties:
public DateTime DateOfIncident { get; set; }
public bool IsCountedAsAPoint;
public decimal OriginalPointValue;
public bool IsFirstInCollection { get; set; }
public bool IsLastInCollection { get; set; }
public int PositionInCollection { get; set; }
public int DaysUntilNextPoint { get; set; }
public DateTime DateDroppedBySystem { get; set; }
public bool IsGoodBehaviorObject { get; set; }
My List is sorted by the DateOfIncident property. I would like to find the next object up the list where IsCounted == true and change it to IsCounted = false.
One question:
1) How do I find this object up the list ?
If I understand your question correctly, you can use LINQ FirstOrDefault:
var nextObject = list.FirstOrDefault(x => x.IsCountedAsAPoint);
if (nextObject != null)
nextObject.IsCountedAsAPoint = false;
If I understand correctly this can be solved with a simple foreach loop. I don't exactly understand your emphasis on "up" as you don't really move up a list, you traverse it. Anyways, the following code snippet finds the first Incident where IsCounted is true and changes it to false. If you're starting from a given position change the for each loop to a for loop and start at i = currentIndex with the exit condition being i < MyList.Count. Leave the break statement to ensure you only modify one Incident object.
foreach (prvEmployeeIncident inc in MyList)
{
if (inc.IsCountedAsAPoint)
{
inc.IsCountedAsAPoint = false;
break;
}
}
You can use List(T).FindIndex to search up the list.
Example:
public class Foo
{
public Foo() { }
public Foo(int item)
{
Item = item;
}
public int Item { get; set; }
}
var foos = new List<Foo>
{
new Foo(1),
new Foo(2),
new Foo(3),
new Foo(4),
new Foo(5),
new Foo(6)
};
foreach (var foo in foos)
{
if(foo.Item == 3)
{
var startIndex = foos.IndexOf(foo) + 1;
var matchedFooIndex = foos.FindIndex(startIndex, f => f.Item % 3 == 0);
if(matchedFooIndex >= startIndex) // Make sure we found a match
foos[matchedFooIndex].Item = 10;
}
}
Just be sure you do not modify the list itself since that will throw an exception.

Distinct with custom comparer

Trying to use Distinct() using a custom comparer and it gives me the error:
cannot be inferred from the usage. Try specifying the type arguments explicitly
The Default comparer works fine but doesn't give the results I expect of course. How can I fix this?
public class TimeEntryValidation
{
public string EmployeeID { get; set; }
public string EmployeeLocation { get; set; }
public string EmployeeDepartment { get; set; }
public int RowIndex { get; set; }
}
public class MyRowComparer : IEqualityComparer<TimeEntryValidation>
{
public bool Equals(TimeEntryValidation x, TimeEntryValidation y)
{
return (x.EmployeeDepartment == y.EmployeeDepartment && x.EmployeeLocation == y.EmployeeLocation);
}
public int GetHashCode(TimeEntryValidation obj)
{
return obj.EmployeeID.GetHashCode();
}
}
void Query(List<TimeEntryValidation> listToQuery)
{
var groupedData =
from oneValid in listToQuery
group oneValid by oneValid.EmployeeID
into g
where g.Count() > 1
select new {DoubleItems = g};
var listItems = groupedData.Distinct(new MyRowComparer());
}
The type of groupedData is some IEnumerable<{an anonymous type}> whereas MyRowComparer is IEqualityComparer<TimeEntryValidation>
It's unclear whether you intended listItems to be a list of groups, or whether you wanted the actual items themselves.
If it's the latter, you probably want something like this:
void Query(List<TimeEntryValidation> listToQuery)
{
var groupedData = from oneValid in listToQuery
group oneValid by oneValid.EmployeeID
into g
where g.Count() > 1
select g ;
var listItems = groupedData.SelectMany(group => group).Distinct(new MyRowComparer());
//listItems is now an IEnumerable<TimeEntryValidation>
}

How to rotate data by converting rows to columns using LINQ

I wrote the following query to rotate data by converting rows to columns using LINQ:
var query = from p in context.PrivilegesTable
group p by p.Type into g
select new Privileges
{
Type = g.Key,
AllowRead = g.Any(p => p.Seq == 1),
AllowAdd = g.Any(p => p.Seq == 2)
AllowEdit = g.Any(p => p.Seq == 3)
AllowDelete = g.Any(p => p.Seq == 4)
};
I think there's a better way to implement it. for more details, read the following:
I have the following Privileges table:
Type Seq
1 1
2 1
2 2
3 1
3 2
3 3
And the following Privileges class:
class Privileges
{
public int ID { get; set; }
public Type Type { get; set;}
public int AllowRead { get; set; } // when sequence = 1
public int AllowAdd { get; set; } // when sequence = 2
public int AllowEdit { get; set; } // when sequence = 3
public int AllowDelete { get; set; } // when sequence = 4
}
Now, I want to create a List<Prvileges> using LINQ to have the following result:
[Type, AllowRead, AllowAdd, AllowEdit, AllowDelete]
[1, True, False, False, False]
[2, True, True, False, False]
[3, True, True, True, False]
Is my query bad for performance ?
Is there a better one ?
I'd suggest using a enum to represent the privileges you can have.
[Flags]
enum Privilege
{
None = 0,
Read = 1,
Add = 2,
Edit = 4,
Delete = 8,
}
In that way you can store a single value to represent all your privileges. Then your Privileges class would be like this:
class Privileges {
public int ID { get; set; }
public Type Type { get; set;}
private Privilege m_Privileges;
public bool AllowRead {
get { return HasPrivilege(Privilege.Read); }
set { CheckPrivilege(Privilege.Read, value); }
}
public bool AllowAdd {
get { return HasPrivilege(Privilege.Add); }
set { CheckPrivilege(Privilege.Add, value); }
}
public bool AllowEdit {
get { return HasPrivilege(Privilege.Edit); }
set { CheckPrivilege(Privilege.Edit, value); }
}
public bool AllowDelete {
get { return HasPrivilege(Privilege.Delete); }
set { CheckPrivilege(Privilege.Delete, value); }
}
private bool HasPrivilege(Privilege p) {
return (m_Privileges & p) == p;
}
private void CheckPrivilege(Privilege p, bool owns) {
if (owns)
m_Privileges = m_Privileges | p;
else
m_Privileges = m_Privileges & (~p);
}
}
Of course this approach is only valid if you have a limited and known number of privileges.
Hope that helps
Your query performance will be abysmal because every Any does a join (L2S implementation problem). In SQL you would do a pivot clause. It can be simulated in L2S but the join problem remains (I reported this bug today on connect). There is no workaround to my knowledge, sorry. If perf is a problem, you must cache or use raw sql.
Edit: I think I have found a special case for you problem:
var query = from p in context.PrivilegesTable
let shift = 1 << p.Seq
group shift by p.Type into g
select new
{
Type = g.Key,
AllowMask = g.Sum(),
};
How cool is that? ;-) Unfortunately it is terrible code and works only up to 64 bit flags.

Categories

Resources