Make a specific Sum<T> - c#

I have a class Contract with inside an ObservableCollection<Repere>:
class Contract : INotifyPropertyChanged
{
private ObservableCollection<Repere> listReperes;
public ObservableCollection<Repere> ListReperes
{
get { return listReperes; }
set
{
listReperes = value;
NotifyPropertyChanged(ref listReperes, value); //NotifyPropertyChanged("ListReperes");
}
}
...
}
My class 'Repere' has an ObservableCollection<Operation>
class Repere : INotifyPropertyChanged
{
private ObservableCollection<Operation> listOperations;
public ObservableCollection<Operation> ListOperations
{
get { return listOperations; }
set
{
NotifyPropertyChanged(ref listOperations, value);
}
...
}
}
Finally, my class Operation is as the following :
public class Operation : INotifyPropertyChanged
{
private int qtyFinished;
public int QtyFinished
{
get { return qtyFinished; }
set
{
qtyFinished = value;
this.NotifyPropertyChanged("QtyFinished");
}
}
public long ID { get; set; }
...
}
My contract has a list of Repere, and Each Repere has inside a list of operations,what I want is to group my Repere by name, and get the total list of Operation for each Repere.
For now I do the following :
List<Repere> liste_rep_group = liste_rep.GroupBy(l => l.Name)
.Select(cl => new Repere
{
Quantite = cl.Sum(c => c.TotalQuantity),
TotalQuantity = cl.Sum(c => c.TotalQuantity),
ID = -1,
IdAff = cl.First().IdAff,
Name = cl.First().Name,
NameOri = cl.First().Name,
Nom_aff = cl.First().Nom_aff,
Profil = cl.First().Profil,
Longueur = cl.First().Longueur,
Hauteur = cl.First().Hauteur,
Largeur = cl.First().Largeur,
Poids = cl.First().Poids,
Priorite = cl.Min(c => c.Priorite),
ListOperations = new ObservableCollection<Operation>(),
}).ToList();
And as I don't know how to "sum" the ListOperations, I then do it manually
foreach (Repere rep in liste_rep)
{
Repere repFound = liste_rep_group.FirstOrDefault(x => x.Name == rep.Name);
if (repFound != null)
{
foreach (Operation op in rep.ListOperations)
{
Operation opFound = repFound.ListOperations.FirstOrDefault(y => y.ID == op.ID);
if (opFound != null)
{
opFound.QtyFinished += op.QtyFinished;
opFound.QtyTot += op.QtyTot;
}
else
{
Operation newOp = new Operation();
newOp.Nom = op.Nom;
newOp.ID = op.ID;
newOp.Clone(op);
repFound.ListOperations.Add(newOp);
}
}
}
}
What I want is :
Group my ObservableCollection by Repere name.
Sum the list of operations for each Repere with same name
The sum may work like that:
public ObservableCollection<Operation> Sum(operation1, operation2)
{
ObservableCollection<Operation> mySum=new ObservableCollection<Operation>();
if(operation1.ID==operation2.ID)
{
operation1.QtyFinished+=operation2.QtyFinished;
mySum.Add(operation1);
return mySum;
}
else
{
mySum.Add(operation1);
mySum.Add(operation2);
return mySum;
}
}
Is there a way to define in my Operation class, a Sum function where I would define how to sum my ObservableCollection?
Edit : Here is an example as requested:
If I want to sum 2 Repere, the result summing operations will be something like that :
Repere1
ID_operation Quantity
1 2
2 1
3 5
Repere1
ID_operation Quantity
1 2
2 1
4 2
Result will be :
Repere1
ID_operation Quantity
1 4
2 2
3 5
4 2
EDIT :
Ok, all is working fine, just need to change it for ObservableCollection :
ListOperations = new ObservableCollection<Operation>(cl.SelectMany(g=>g.ListOperations).GroupBy(o=>o.ID)
.Select(go => new Operation
{
ID = go.First().ID,
QtyFinished = go.Sum(o => o.QtyFinished),
Color=go.First().Color,
}))

Here's one approach:
class Repere
{
public string Name { get; set; }
public List<Operation> Operations { get; set; }
}
class Operation
{
public int Id { get; set; }
public int Quantity { get; set; }
}
class Program
{
static void Main(string[] args)
{
var data = new List<Repere>
{
new Repere { Name = "Repere1", Operations = new List<Operation>
{
new Operation { Id = 1, Quantity = 2 },
new Operation { Id = 2, Quantity = 1 },
new Operation { Id = 3, Quantity = 5 }
}},
new Repere { Name = "Repere1", Operations = new List<Operation>
{
new Operation { Id = 1, Quantity = 2 },
new Operation { Id = 2, Quantity = 1 },
new Operation { Id = 4, Quantity = 2 }
}},
};
var result = data.GroupBy(r => r.Name)
.Select(gr => new
{
Name = gr.Key,
Operations = gr.SelectMany(g => g.Operations)
.GroupBy(o => o.Id)
.Select(go => new { Id = go.Key, Quantity = go.Sum(o => o.Quantity)})
});
foreach (var r in result)
{
Console.WriteLine(r.Name);
foreach (var o in r.Operations)
{
Console.WriteLine($"\t{o.Id}\t{o.Quantity}");
}
}
Console.ReadKey();
}
}
Also, you don't need to have a setter on ListOperations in order for changes to the collection to be properly notified to the UI - the individual items (that implement INotifyPropertyChanged) will raise their own notifications (ie. these won't use the collection's set method).

Related

How can I use LINQ to select a property inside of an IEnumerable that's inside another IEnumerable?

I have this LINQ Query which returns the data shown in the image below:
var result = jmdict
.Where(x => x.Sequence == 1438690)
.SelectMany(entry =>
entry.Senses.Select(s =>
(entry.Sequence, s.Glosses.Where(x => x.Language.Code == "eng"))));
Does anyone have any ideas how I could modify this so that item2 would return just the value of Term ("weather","the elements" etc) instead of the IEnumerator which it currently returns.
1) I tried this suggestion which was given to me earlier
var r3 =
jmdict.Where(x => x.Sequence == 1438690)
.SelectMany(entry => entry.Senses.Select(s => s.Glosses.Where(x => x.Language.Code == "eng")
.Select(y => (entry.Sequence, y => y.Term))));
But this does not work and there's an error line under the last Select giving this error message :
GetDetails.cs(155,155): Error CS0411: The type arguments for method
'Enumerable.Select(IEnumerable,
Func)' cannot be inferred from the usage. Try
specifying the type arguments explicitly. (CS0411) (Download)
public static List<IJapaneseDictionaryEntry> jmdict;
public class JapaneseDictionaryEntry : IJapaneseDictionaryEntry {
public int Sequence { get; set; }
private readonly List<Sense> senses = new List<Sense>();
public IEnumerable<ISense> Senses => this.senses;
}
public interface ISense {
IEnumerable<Gloss> Glosses { get; }
}
public class Gloss {
public string Term { get; }
public Language Language { get; }
public Gloss(string term, Language language, string gender)
{
this.Term = term;
this.Language = language;
this.Gender = gender;
}
}
Finally Here is your corrected second query
var r3 = jmdict.Where(x => x.Sequence == 1438690)
.SelectMany(entry => entry.Senses.SelectMany(s => s.Glosses.Where(x => x.Language.Code == "eng")
.Select(y => (entry.Sequence, y => y.Term))));
I have taken your schema and modified a bit, here out put will be
{Sequence= ?, Term=?}
data.Where(a=>a.Sequence==2)
.SelectMany(b=>b.Senses.SelectMany(x=>x.Glosses.Where(g => g.Language == "German"))
.Select(y => new {b.Sequence, y.Term}));
public class JapaneseDictionaryEntry
{
private List<Sense> senses = new List<Sense>();
public int Sequence { get; set; }
public List<Sense> Senses { get { return senses; } set { senses = value; } }
}
public class Sense
{
private List<Gloss> glosses = new List<Gloss>();
public List<Gloss> Glosses { get { return glosses; } set { glosses = value; } }
}
public class Gloss
{
public string Term { get; set; }
public string Language { get; set; }
}
class Program
{
static List<JapaneseDictionaryEntry> GetData()
{
return new List<JapaneseDictionaryEntry>() {
new JapaneseDictionaryEntry()
{
Sequence = 1,
Senses = new List<Sense>() { new Sense() { Glosses = new List<Gloss>() {
new Gloss() { Term = "1",Language="English"},
new Gloss() { Term = "2",Language="German" },
new Gloss() { Term = "3",Language="German" },
new Gloss() { Term = "4",Language="English" }
}
} }
},
new JapaneseDictionaryEntry()
{
Sequence = 2,
Senses = new List<Sense>() { new Sense() { Glosses = new List<Gloss>() {
new Gloss() { Term = "a", Language="English"},
new Gloss() { Term = "b", Language="German" },
new Gloss() { Term = "c", Language="German" },
new Gloss() { Term = "d", Language="English"}
}
} }
}
};
}
static void Main(string[] args)
{
var data = GetData();
var termData = data.Where(a=>a.Sequence==2)
.SelectMany(b=>b.Senses.SelectMany(x=>x.Glosses.Where(g => g.Language == "German"))
.Select(y => new {b.Sequence, y.Term}));
foreach (var item in termData)
{
Console.WriteLine(item);
}
}}

Code review of this portion of Linq Filters

I am a newbie of c #, I would like to know if I can remove the for each and do a single operation with Linq. I would like to return an IEnumerable with already filtered. is it possible to do this? Every suggestion is welcome, thank you very much
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Linq
{
class Oggetto
{
public int Id { get; set; }
public string MyProperty { get; set; }
public int Deleted { get; set; }
}
class Program
{
static void Main(string[] args)
{
IEnumerable<Oggetto> lista = new List<Oggetto> {
new Oggetto(){ Id = 1, MyProperty = "Propr1", Deleted = 0 },
new Oggetto(){ Id = 1, MyProperty = "Propr2", Deleted = 1 },
new Oggetto(){ Id = 2, MyProperty = "Prop3", Deleted = 0 },
new Oggetto(){ Id = 3, MyProperty = "Propr4", Deleted = 0 },
new Oggetto(){ Id = 3, MyProperty = "Prop5", Deleted = 1 }
};
foreach (var item in lista.Where(x => x.Deleted == 1).GroupBy(x => x.Id).Select(g => g.First()))
{
item.MyProperty = string.Join(",", lista.Where(t => t.Id == item.Id).Select(x => x.MyProperty).ToArray());
Console.WriteLine(item.Id);
Console.WriteLine(item.MyProperty);
}
Console.ReadLine();
}
}
}
You can use projection for this.
var orderedList = lista.GroupBy(x => x.Id)
.Where(x => x.Any(y => y.Deleted == 1))
.Select(x => new Oggetto
{
Id = x.Key, MyProperty = string.Join(",", x.Select(v => v.MyProperty))
});
foreach (var item in orderedList)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.MyProperty);
}
Anyway, as #Alex said you shoud replace Deleted field type to bool and as said by #Marco Salerno start programming in English you'll not regret.
First of all I would avoid the groupBy statement. This is a lot of unneded overhead. You can use distinct instead. This will give you all the IDs you need to know.
var ids = lista.Where(x => x.Deleted).Select(x => x.Id).Distinct();
You can then select all the elements that you need with:
var items = ids.Select(i => lista.Where(x => x.Id == i));
which results in a List of Lists. For the ease of use I would convert this to a Dictionary<K, V> (int this case it's Dictionary<long, List<string>> as a final step:
var dictionary = items.ToDictionary(l => l.First().Id, l => l.Select(o => o.MyProperty).ToList());
You now got a "nice and filtered" collection you can use any way you like (or just output it)
foreach (var item in dictionary)
{
Console.WriteLine($"Id: {item.Key}");
Console.WriteLine($"Properties: {string.Join(", ", item.Value)}");
}
I also changed your class a little bit to:
class Oggetto
{
public int Id { get; set; }
public string MyProperty { get; set; }
// bool instead of int - Deleted has only 2 states
public bool Deleted { get; set; }
}
First of all STOP programming in Italian, start doing it in English.
Anyway, this should be a better approach:
class Program
{
static void Main(string[] args)
{
List<Item> items = new List<Item> {
new Item{ Id = 1, MyProperty = "Propr1", Deleted = 0 },
new Item{ Id = 1, MyProperty = "Propr2", Deleted = 1 },
new Item{ Id = 2, MyProperty = "Prop3", Deleted = 0 },
new Item{ Id = 3, MyProperty = "Propr4", Deleted = 0 },
new Item{ Id = 3, MyProperty = "Prop5", Deleted = 1}
};
foreach (IGrouping<int,Item> group in items.GroupBy(x => x.Id).ToList())
{
List<Item> groupItems = group.ToList();
Item deletedItem = groupItems.Where(x => x.Deleted == 1).FirstOrDefault();
if(deletedItem != null)
{
deletedItem.MyProperty = string.Join(",", groupItems.Select(x => x.MyProperty).ToArray());
Console.WriteLine(deletedItem.Id);
Console.WriteLine(deletedItem.MyProperty);
}
}
}
}
class Item
{
public int Id { get; set; }
public string MyProperty { get; set; }
public int Deleted { get; set; }
}

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?

C# Reactive Extensions (rx) FirstOrDefault enumerates entire collection

It seems that the expected behavior of FirstOrDefault is to complete after finding an item that matches the predicate and the expected behavior of concat is to evaluate lazily. However, the following example enumerates the entire collection even though the predicate matches the first item.
(Thanks for the friendlier code Shlomo)
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
Is this the expected behavior? Is there a way to defer the enumeration of the objects (specifically the building in this case) until truly needed?
The following is Linqpad-friendly code equivalent to what you have:
void Main()
{
var entities = Observable.Defer(() => GetObservable().Concat());
Entity result = null;
var first = entities.FirstOrDefaultAsync(i => i.RowId == 1).Subscribe(i => result = i);
result.Dump();
buildCalled.Dump();
}
// Define other methods and classes here
public IEnumerable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.Select(i => Observable.Return(BuildEntity(i)));
}
public int buildCalled = 0;
public Entity BuildEntity(EntityTableRow entityRow)
{
buildCalled++;
return new Entity { RowId = entityRow.Id, StringVal = entityRow.StringVal };
}
public class Entity
{
public int RowId { get; set; }
public string StringVal { get; set; }
}
public class EntityTableRow
{
public int Id { get; set; }
public string StringVal { get; set; }
}
If you change GetObservable to the following, you'll get the desired result:
public IObservable<IObservable<Entity>> GetObservable()
{
var rows = new List<EntityTableRow>
{
new EntityTableRow { Id = 1, StringVal = "One"},
new EntityTableRow { Id = 2, StringVal = "Two"},
};
return rows.ToObservable().Select(i => Observable.Return(BuildEntity(i)));
}
It appears the implementation of Concat<TSource>(IEnumerable<IObservable<TSource>>) is eager in evaluating the enumerable, whereas the implementation of Concat<TSource>(IObservable<IObservable<TSource>>) and ToObservable<TSource>(IEnumerable<TSource>) maintain laziness appropriately. I can't say I know why.

How can I sort a list that has potential parents with the parent objects first in the list?

In C#, if I have a list of objects, where each object can have a parent, and each parent can have a parent (x combinations), how is the best way to sort the list so that all the parent objects are first in the list?
Here is the object structure:
class test
{
int id { get; set; }
int parentId { get; set; }
bool hasParentObject { get; set; }
}
Here is an example of some objects:
Object a:
id = 1;
parentId = 0;
hasParentObject = false;
Object b:
id = 2;
parentId = 1;
hasParentObject = true;
Object c:
id = 3;
parentId = 2;
hasParentObject = true;
Thanks.
EDIT
With the following code, how can the code be modified so that if an object does not have a parent, the object is in the list before any objects that do have parents?
Code:
class Test : IComparable<Test>
{
public int Id { get; set; }
public int ParentId { get; set; }
public bool HasParentObject { get; set; }
public int CompareTo(Test other)
{
if(!this.HasParentObject)
return 1;
else if(!other.HasParentObject)
return 1;
else if(other.HasParentObject && this.HasParentObject)
return ParentId.CompareTo(other.ParentId);
else if(other.HasParentObject)
return 1;
else
return -1;
}
}
It sounds like you would want the objects that are parents first in the list (Optional: Ordered by ID), then you want the objects that aren't parents to follow the parent objects in the list (Optional: Ordered by ID).
This isn't a one liner, but I think it does what you're asking
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Test> tests = new List<Test>
{
new Test() { Id = 6, ParentId = 2, HasParentObject = true },
new Test() { Id = 2, ParentId = 0, HasParentObject = false },
new Test() { Id = 1, ParentId = 0, HasParentObject = true },
new Test() { Id = 4, ParentId = 1, HasParentObject = true }
};
// Get the parents sorted
List<Test> sortedTests = tests.Where(t => tests.FindIndex(t2 => t2.ParentId == t.Id) != -1)
.OrderBy(t => t.Id)
.ToList();
// Add those that aren't parents sorted
sortedTests.AddRange(tests.Where(t => tests.FindIndex(t2 => t2.ParentId == t.Id) == -1)
.OrderBy(t => t.Id));
sortedTests.ForEach(t => Console.WriteLine("ID: {0} ParentId: {1}", t.Id, t.ParentId));
}
}
class Test
{
public int Id { get; set; }
public int ParentId { get; set; }
public bool HasParentObject { get; set; }
}
Results:
ID: 1 ParentId: 0
ID: 2 ParentId: 0
ID: 4 ParentId: 1
ID: 6 ParentId: 2
Fiddle Demo
You could implement the IComparable itherface for your class and then use the Sort method of the list.
class Test : IComparable<Test>
{
public int Id { get; set; }
public int ParentId { get; set; }
public bool HasParentObject { get; set; }
public int CompareTo(Test other)
{
if(other.HasParentObject && this.HasParentObject)
return ParentId.CompareTo(other.ParentId);
else if(other.HasParentObject)
return 1;
else
return -1;
}
}
Please look here for a working example based on your post's data.
if one can assume the hasParentObject property is public
public List<test> l;
public List<test> sortList(List<test> _l)
{
return _l.OrderByDescending(a => a.hasParentObject).ToList();
}
You can use new Comparison overload of Sort method - .Sort(new Comparison<test>(expression))
For instance :
List<test> list = new List<test>()
{
new test() { id=1, hasParentObject = false, parentId = 0 },
new test() { id=2, hasParentObject = true, parentId = 1 },
new test() { id=3, hasParentObject = false, parentId = 0 },
new test() { id=4, hasParentObject = true, parentId = 3 },
};
list.Sort((s1, s2) => s1.parentId > s2.parentId ? 1 : (s1.parentId < s2.parentId ? -1 : 0));
foreach (var item in list)
Console.WriteLine(item.id);
Console.ReadKey();

Categories

Resources