I have a use case that contains data stored in a values tables as shown below, that needs to be pivot based on a JobLevel and displayed in a DataGridControl, however in order to get this working I firstly need to transform the data table I receive from the database into a ViewModel object that can display the result as a row, to allow users can be able to modify the values column on the DataGridControl. The table stores information pertaining to employees in various levels and a count of employees in each level and what their color and gender are. If a value has no data, it should default to zero.
Job Level enum:
public enum JobLevel
{
Top,
Mid,
Low
}
public enum Color
{
Red,
Blue,
Green
}
public enum Gender
{
Female,
Male,
Other
}
Values entity class:
public class EmployeeGroupValue
{
[Key, Column(Order = 0)]
[Required]
public JobLevel JobLevel { get;set; }
[Key, Column(Order = 1)]
[Required]
public Gender Gender { get;set; }
[Key, Column(Order = 2)]
[Required]
public Color Color { get;set; }
[Required]
public int Value { get;set; }
}
Example of 'Values' data:
Id
JobLevel
Gender
Color
Value
1
Top
Male
Red
10
2
Top
Other
Red
5
3
Top
Female
Blue
20
4
Mid
Other
Blue
5
5
Mid
Female
Green
50
6
Low
Male
Green
5
7
Low
Other
Red
7
8
Low
Female
Green
12
Employee Group Values DataGrid View:
JobLevel
Female
Male
Other
xxxxxxxxxxxx
R
B
G
R
B
G
R
B
G
Top
0
20
0
10
0
0
5
0
0
Mid
0
0
50
0
0
0
0
5
0
Low
0
0
12
0
0
5
7
0
0
Suggested View Model:
public class EmployeeGroupValueViewModel
{
public string JobLevel { get;set; }
public int FemaleRed { get;set; }
public int FemaleBlue { get;set; }
public int FemaleGreen { get;set; }
public int MaleRed { get;set; }
public int MaleBlue { get;set; }
public int MaleGreen { get;set; }
public int OtherRed { get;set; }
public int OtherBlue { get;set; }
public int OtherGreen { get;set; }
}
How can I transform my data using linq into such a view model and default Value to zero where there are no rows in the database?
Using a custom enhanced Dictionary that returns default(TValue) for missing entries, you can remap the source data to a tree of dictionaries and avoid re-scanning the data for each property value.
Here is the enhanced Dictionary and some extension methods to make using it easier with LINQ:
#region Enhanced Dictionaries
public static class DictExt {
// DefaultDictionary that returns default(TValue) for missing entries
public static DefaultDictionary<TKey, TValue> ToDefaultDictionary<T, TKey, TValue>(this IEnumerable<T> items, Func<T, TKey> keyFn, Func<T, TValue> valFn) {
var nd = new DefaultDictionary<TKey, TValue>();
foreach (var item in items)
nd.Add(keyFn(item), valFn(item));
return nd;
}
public static DefaultDictionary<TKey, TValue> ToDefaultDictionary<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> keyFn) {
var nd = new DefaultDictionary<TKey, TValue>();
foreach (var item in items)
nd.Add(keyFn(item), item);
return nd;
}
public static DefaultDictionary<TKey, TValue> ToDefaultDictionary<TKey, TValue>(this IDictionary<TKey, TValue> srcd)
=> new DefaultDictionary<TKey, TValue>(srcd);
}
//***
// Enhanced Dictionary that returns default(TValue) for missing entries
//***
public class DefaultDictionary<TKey, TValue> : Dictionary<TKey, TValue> {
public DefaultDictionary(IDictionary<TKey, TValue> d) : base() {
foreach (var kvp in d)
Add(kvp.Key, kvp.Value);
}
public DefaultDictionary() : base() { }
public new TValue this[TKey key] {
get {
TryGetValue(key, out var val);
return val;
}
set => base[key] = value;
}
}
Once you have this, you can use it with LINQ to convert the original sparse data into dictionaries. I assumed the data might contain duplicate values at any level.
var jobLevelDict =
values
.GroupBy(v => v.JobLevel)
.ToDefaultDictionary(
vg => vg.Key,
vjlg => vjlg
.GroupBy(v => v.Gender)
.ToDefaultDictionary(
vgg => vgg.Key,
vgg => vgg
.GroupBy(v => v.Color)
.ToDefaultDictionary(
vcg => vcg.Key,
vcg => vcg.Sum(v => v.Value))));
One you have the tree of dictionaries, you can build the full List<EmployeeGroupValueViewModel> for the grid:
var ans = Enum.GetValues<JobLevel>()
.Select(jl => new EmployeeGroupValueViewModel {
JobLevel = jl.ToString(),
FemaleRed = jobLevelDict?[jl]?[Gender.Female]?[Color.Red] ?? 0,
FemaleBlue = jobLevelDict?[jl]?[Gender.Female]?[Color.Blue] ?? 0,
FemaleGreen = jobLevelDict?[jl]?[Gender.Female]?[Color.Green] ?? 0,
MaleRed = jobLevelDict?[jl]?[Gender.Male]?[Color.Red] ?? 0,
MaleBlue = jobLevelDict?[jl]?[Gender.Male]?[Color.Blue] ?? 0,
MaleGreen = jobLevelDict?[jl]?[Gender.Male]?[Color.Green] ?? 0,
OtherRed = jobLevelDict?[jl]?[Gender.Other]?[Color.Red] ?? 0,
OtherBlue = jobLevelDict?[jl]?[Gender.Other]?[Color.Blue] ?? 0,
OtherGreen = jobLevelDict?[jl]?[Gender.Other]?[Color.Green] ?? 0
})
.ToList();
Try the following query:
var databaseData = context.EmployeeGroupValues
.GroupBy(e => new { e.JobLevel, e.Gender, e.Color })
.Select(g => new
{
g.Key.JobLevel,
g.Key.Gender,
g.Key.Color,
Value = g.Sum(x => x.Value)
})
.ToList();
var enrichedData =
from jobLevel in new [] { JobLevel.Top, JobLevel.Mid, JobLevel.Low }
from gender in new [] { Gender.Female, Gender.Male, Gender.Other }
from color in new [] { Color.Red, Color.Blue, Color.Green }
join d in databaseData on
new { JobLevel = jobLevel, Gender = gender, Color = color}
equals new { d.JobLevel, d.Gender, d.Color } into gj
from d in gj.DefaultIfEmpty(new { JobLevel = jobLevel, Gender = gender, Color = color, Value = 0 })
group d by jobLevel into g
select new EmployeeGroupValueViewModel
{
JobLevel = g.Key.ToString(),
FemaleRed = g.Sum(x => x.Gender == Gender.Female && x.Color == Color.Red ? x.Value : 0),
FemaleBlue = g.Sum(x => x.Gender == Gender.Female && x.Color == Color.Blue ? x.Value : 0),
FemaleGreen = g.Sum(x => x.Gender == Gender.Female && x.Color == Color.Green ? x.Value : 0),
MaleRed = g.Sum(x => x.Gender == Gender.Male && x.Color == Color.Red ? x.Value : 0),
MaleBlue = g.Sum(x => x.Gender == Gender.Male && x.Color == Color.Blue ? x.Value : 0),
MaleGreen = g.Sum(x => x.Gender == Gender.Male && x.Color == Color.Green ? x.Value : 0),
OtherRed = g.Sum(x => x.Gender == Gender.Other && x.Color == Color.Red ? x.Value : 0),
OtherBlue = g.Sum(x => x.Gender == Gender.Other && x.Color == Color.Blue ? x.Value : 0),
OtherGreen = g.Sum(x => x.Gender == Gender.Other && x.Color == Color.Green ? x.Value : 0),
};
var result = enrichedData.ToList();
You can play with this in dotnetfiddle
Related
For sure very simple question for most of you.
But I am struggling with a solution at the moment.
Imagine you have a list of cats (List) where each cat has a list of babys (Kitten)
public class Cat
{
public string Name { get; set; }
public int Age { get; set; }
public string Race { get; set; }
public bool Gender { get; set; }
public List<Kitten> Babys { get; set; }
}
public class Kitten
{
public string Name { get; set; }
public double Age { get; set; }
public bool Gender { get; set; }
}
now I want to find the Cat that has the most matches for given requirements. It could easily be the case that a cat matches only 2 of 3 requirements. I simple want to find the cat that has the most matches to my requirements.
where my requirements could be:
Name has to be "Micky"
Age is 42
Has a Kitten named "Mini"
My actual solution would be to compare all properties and take the one with the highest count of matching properties. But this is not generic and I am sure there are mutch better ways to do it.
Thanks in advance
Well, I have no opportunity to test this solution, but you can try this:
Assume that you have a list of cats:
var cats = new List<Cat>();
Now you have defined what are your criteria:
var desiredName = "Micky";
var desiredAge = 42;
var desiredKitten = "Mini";
And then you have to get your desired cat:
var desiredCat = cats
.Select(c => new {
Rating =
Convert.ToInt32(c.Age == desiredAge) + // Here you check first criteria
Convert.ToInt32(c.Name == desiredName) + // Check second
Convert.ToInt32(c.Babys.Count(b => b.Name == desiredKitten) > 0), // And the third one
c })
.OrderByDescending(obj => obj.Rating) // Here you order them by number of matching criteria
.Select(obj => obj.c) // Then you select only cats from your custom object
.First(); // And get the first of them
Please check if this works for you.
And if you need more specific answer or some edits for me to add.
If you really will compare 2 ou 3 requirements you can simplify using Linq by:
// try to find with 3 requirements
var foundCats = catList.Where(t => t.Name == desiredName &&
t.Age == desiredAge &&
t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
if (foundCats.Any())
{
// you found the desired cat (or cats)
return foundCats;
}
// try to find with 2 requirements
foundCats = catList.Where(t =>
(t.Name == desiredName && t.Age == desiredAge) ||
(t.Name == desiredName && t.Babys.Any(k => k.Name == desiredKitten)) ||
(t.Age == desiredAge && t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
if (foundCats.Any())
{
// you found the desired cat (or cats)
return foundCats;
}
// try to find with only 1 requirement
foundCats = catList.Where(t => t.Name == desiredName ||
t.Age == desiredAge ||
t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
return foundCats;
So, I see that the problem is you don't know if in any near future you will have more properties, so I will suggest going to the hardway and make reflection, the following is ugly af but you can probably (you will) make it better and hopefully serves you well as guiadance:
public static List<Cat> CheckProperties(List<Cat> inCatList, Cat inQueryCat)
{
Dictionary<Cat, List<PropertyInfo>> dict = new Dictionary<Cat, List<PropertyInfo>>();
foreach (PropertyInfo pI in inQueryCat.GetType().GetProperties())
{
var value = pI.GetValue(inQueryCat);
if (value != null)
{
var cats = inCatList.Where(cat => cat.GetType().GetProperty(pI.Name).GetValue(cat).Equals(value));
foreach (Cat cat in cats)
{
if (dict.ContainsKey(cat))
{
dict[cat].Add(pI);
}
else
{
dict.Add(cat, new List<PropertyInfo>() {pI});
}
}
}
}
int max = Int32.MinValue;
foreach (KeyValuePair<Cat, List<PropertyInfo>> keyValuePair in dict)
{
if (keyValuePair.Value.Count > max)
{
max = keyValuePair.Value.Count;
}
}
return dict.Where(pair => pair.Value.Count == max).Select(pair => pair.Key).ToList();
}
While this is the most generic solution there is (need some edge case improvements):
public class ReflectCmpare
{
public PropertyInfo PropertyInfo { get; set; }
public dynamic Value { get; set; }
}
public Cat GetBestCat(List<Cat> listOfCats, List<ReflectCmpare> catParamsToCompare, List<ReflectCmpare> kittensParamsToCompare)
{
var bestScore = 0;
var ret = listOfCats[0];
foreach (var cat in listOfCats)
{
var score = catParamsToCompare.Sum(param => param.PropertyInfo.GetValue(cat, null) == param.Value ? 1 : 0);
foreach (var baby in cat.Babys)
{
score+= kittensParamsToCompare.Sum(param => param.PropertyInfo.GetValue(baby, null) == param.Value ? 1 : 0);
}
if (score <= bestScore) continue;
bestScore = score;
ret = cat;
}
return ret;
}
You should really think about just doing simple compare function
considering this objects is not dynamic this is the way to go:
public Cat GetBestCat(List<Cat> listOfCats, string name , int? age , bool? gender, string race ,string babyName,int? babyAge,bool? babyGender )
{
var ret = listOfCats[0];
var highestScore = 0;
foreach (var cat in listOfCats)
{
var score = 0;
score += name != null && cat.Name.Equals(name) ? 1 : 0;
score += age.HasValue && cat.Age.Equals(age.Value) ? 1 : 0;
score += gender.HasValue && cat.Gender.Equals(gender.Value) ? 1 : 0;
score += race != null && cat.Race.Equals(race) ? 1 : 0;
score += name != null && cat.Name.Equals(name) ? 1 : 0;
score += cat.Babys
.Where(k => babyName==null || k.Name.Equals(babyName))
.Where(k => !babyAge.HasValue || k.Age.Equals(babyAge.Value))
.Any(k => !babyGender.HasValue || k.Gender.Equals(babyGender.Value))?1:0;
if (score <= highestScore) continue;
highestScore = score;
ret = cat;
}
return ret;
}
I have a list with following structure,
TimeStamp Name Value
---------------------------------------
01/May/2018 Prop1 5
07/May/2018 Prop1 9
01/May/2018 Prop2 8
07/May/2018 Prop2 11
01/May/2018 Prop3 18
07/May/2018 Prop3 7
.....................................................
......................................................
07/May/2018 Prop(N) (N)Value
And I am trying to pivot/convert it to following structure,
TimeStamp Prop1 Prop2 Prop3 .... PropN Value
---------------------------------------------------------------------------------------------
01/May/2018 5 8 18 (N)th Value 31+(N)th Value
07/May/2018 9 11 7 (N)th Value 27+(N)th Value
Edit:
In reality the list is quite big and can have 100 of Names and won't be knowing there values till it's loaded in memory. So it's not possible to check for the names as mentioned by #TheGeneral, though it works if we have limited sets of values and we know them before hand.
I attempted doing this using grouping , but it is probably not the approach.
var pivotList= customersList.GroupBy(cust => new { cust.Name, cust.Timestamp, cust.Value }).Select(x => new {
TimeStamp = x.Key.Timestamp,
Circuit = x.Key.Name,
NumData = x.Sum(y=>x.Key.Value)
});
I have pasted the actual format and not the C# lists for brevity.
If you know how many props you have beforehand, you could do something like this
var query = myList
.GroupBy(c => c.TimeStamp)
.Select(g => new {
TimeStamp = g.Key,
Prop1 = g.Where(x => x.Name == "Prop1").Sum(x => x.Value),
Prop2 = g.Where(x => x.Name == "Prop2").Sum(x => x.Value),
Prop3 = g.Where(x => x.Name == "Prop3").Sum(x => x.Value),
Value = g.Sum(c => c.Value)
});
If you this list of props is dynamic, you are going to have to call pivot at the server and query this manually. or use a sublist and groupby Name
I'll add my 5 cents to this question. I have implemented a simple pivot table class which does exactly what you need.
public static class PivotTable
{
public static PivotTable<TRow, TColumn, TValue> Create<TRow, TColumn, TValue>(Dictionary<TRow, Dictionary<TColumn, TValue>> dictionary)
where TRow : IComparable, IEquatable<TRow>
where TColumn : IComparable, IEquatable<TColumn>
{
return new PivotTable<TRow, TColumn, TValue>(dictionary);
}
}
public class PivotTable<TRow, TColumn, TValue>
where TRow : IComparable, IEquatable<TRow>
where TColumn : IComparable, IEquatable<TColumn>
{
private readonly Dictionary<TRow, Dictionary<TColumn, TValue>> _dictionary;
public PivotTable(Dictionary<TRow, Dictionary<TColumn, TValue>> dictionary)
{
_dictionary = dictionary;
}
public bool HasValue(TRow row, TColumn col)
{
return _dictionary.ContainsKey(row) && _dictionary[row].ContainsKey(col);
}
public TValue GetValue(TRow row, TColumn col)
{
return _dictionary[row][col];
}
public string Print()
{
var separator = " ";
var padRight = 15;
var rows = _dictionary.Keys;
var columns = _dictionary.SelectMany(x => x.Value.Keys).Distinct().OrderBy(x => x).ToList();
var sb = new StringBuilder();
var columnsRow = new[] { "" }.ToList();
columnsRow.AddRange(columns.Select(x => x.ToString()));
sb.AppendLine(string.Join(separator, columnsRow.Select(x => x.PadRight(padRight))));
foreach (var row in rows.OrderBy(x => x))
{
sb.Append(row.ToString().PadRight(padRight)).Append(" ");
foreach (var col in columns.OrderBy(x => x))
{
var val = HasValue(row, col) ? GetValue(row, col).ToString() : default(TValue).ToString();
sb.Append(val.PadRight(padRight)).Append(" ");
}
sb.AppendLine();
}
return sb.ToString();
}
}
public class Element
{
public DateTime TimeStamp { get; set; }
public string Name { get; set; }
public int Value { get; set; }
}
public static class Extensions
{
public static PivotTable<TRow, TColumn, TValue> ToPivot<TItem, TRow, TColumn, TValue>(
this IEnumerable<TItem> source,
Func<TItem, TRow> rowSelector,
Func<TItem, TColumn> colSelector,
Func<IEnumerable<TItem>, TValue> aggregatFunc
)
where TRow : IComparable, IEquatable<TRow>
where TColumn : IComparable, IEquatable<TColumn>
{
var dic = source
.GroupBy(rowSelector)
.ToDictionary(x => x.Key, x => x.GroupBy(colSelector).ToDictionary(y => y.Key, y => aggregatFunc(y)));
return PivotTable.Create(dic);
}
And then you can call it on any type implementing IEnumerable. You can select how elements should be aggregated. It also supports pretty printing, getting elements by row/col.
In your case you need to do this:
var elements = new List<Element>();
//fill in with some data
var pivot = elements.ToPivot(x => x.TimeStamp, x => x.Name, x => x.Sum(y => y.Value));
The core function is this.
var dic = source
.GroupBy(rowSelector)
.ToDictionary(x => x.Key, x => x.GroupBy(colSelector).ToDictionary(y => y.Key, y => aggregatFunc(y)));
If you don't need the rest of functionality just do this:
var dic = elements //Dictionary<DateTime,Dictionary<string,int>>
.GroupBy(x=>x.TimeStamp)
.ToDictionary(x => x.Key, x => x.GroupBy(x=>x.Name).ToDictionary(y => y.Key, y => y=>y.Sum(z=>z.Value));
And then you can access your data by doing e.g this:
var date = new DateTime(2018,6,6);
var prop = "Prop1";
dic[date][prop] // will give you a sum for this particular date and prop
class Program
{
static void Main(string[] args)
{
var elements = new Element[]
{
new Element
{
TimeStamp = new DateTime(2018,5,1),
Name = "Prop1",
Value = 5,
},
new Element
{
TimeStamp = new DateTime(2018,5,7),
Name = "Prop1",
Value = 9,
},
new Element
{
TimeStamp = new DateTime(2018,5,1),
Name = "Prop2",
Value = 8,
},
new Element
{
TimeStamp = new DateTime(2018,5,7),
Name = "Prop2",
Value = 11,
},
new Element
{
TimeStamp = new DateTime(2018,5,1),
Name = "Prop3",
Value = 18,
},
new Element
{
TimeStamp = new DateTime(2018,5,7),
Name = "Prop3",
Value = 18,
},
};
var pivot = from line in elements
group line by line.TimeStamp into g
select new { g.Key, Props=g.Select(el=>new { el.Name, el.Value }).ToArray(), Total = g.Sum(line => line.Value) };
int propCount = pivot.Max(line => line.Props.Count());
string[] props = pivot.SelectMany(p => p.Props, (parent, c) => c.Name).Distinct().ToArray();
Console.Write($"Date\t");
for (int i = 0; i < propCount; i++)
{
Console.Write($"{props[i]}\t");
}
Console.Write($"Total");
Console.WriteLine();
foreach (var pivotLine in pivot)
{
Console.Write($"{pivotLine.Key.ToShortDateString()}\t");
for (int i = 0; i < propCount; i++)
{
Console.Write($"{pivotLine.Props.FirstOrDefault(p=>p.Name==props[i])?.Value}\t");
}
Console.Write($"{pivotLine.Total}\t");
Console.WriteLine();
}
}
class Element
{
public DateTime TimeStamp { get; set; }
public string Name { get; set; }
public int Value;
}
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?
I have 2 List classes below,
public class OldList
{
public string Name { get; set; }
public int Size { get; set; }
}
public class NewList
{
public string Name { get; set; }
public int Size { get; set; }
}
Now I have data from 2 class likes,
var oldList = new List<OldList> {
new OldList { Name = "F1", Size = 374 },
new OldList { Name = "F2", Size = 125 }
};
var newList = new List<NewList> {
new NewList { Name = "F1", Size = 374, },
new NewList { Name = "F2", Size = 126, },
new NewList { Name = "F3", Size = 13, }
};
I would like to filter newList to retrieve all new items (e.g. "F3") and also all existing items where Size of newList is greater than Size of OldList (e.g. "F2").
For "F1" the Size is the same, hence I want to ignore.
Below code gave me result "F3", how to also get "F2"?
var X = newList.Where(p => !oldList.Any(l => p.Name == l.Name));
You can add an additional condition for "same name, increased size" into the .Where statement, like this:
var X = newList.Where(
p => !oldList.Any(l => p.Name == l.Name)
|| oldList.Any(l => p.Name == l.Name && p.Size > l.Size)
);
Try this?
var result = newList.Where(i => !oldList.Any(l => i.Name == l.Name)
|| i.Size > oldList.Where(x => x.Name == i.Name).Select(x => x.Size).Max());
Note: performance of this won't be great and this may not translate to SQL if you're using ORM.
I have a model, where I'm trying to calculate the weighted average:
public class ObScore
{
public int ObScoreId { get; set; }
public int AnalystId { get; set; }
public DateTime Date { get; set; }
public int PossScore { get; set; }
public int Weight { get; set; }
}
I can do this, using the following code:
//
// GET: /Test/
public ActionResult Test()
{
var scores = db.ObScores.ToList();
double weightedValueSum = scores.Sum(x=>x.PossScore * x.Weight);
double weightSum = scores.Sum(x => x.Weight);
if (weightSum != 0)
{
ViewBag.wa2 = weightedValueSum / weightSum;
}
return View();
}
However, I want to be able to add a GroupBy clause, so I can group by Date/AnalystId etc.
Is it possible to combine the two LINQ statements, so that I can do this, while still avoiding a DIV/0 should the weightSum = 0?
Thanks, Mark
Use an expression like:
div = weightSum != 0 ? weightedValueSum / weightSum : -1
In the end it should be something like:
var res = scores.GroupBy(p => p.Date.Year)
.Select(p => new {
Year = p.Key,
weightedValueSum = p.Sum(x => x.PossScore * x.Weight),
weightSum = p.Sum(x => x.Weight)
})
.Select(p => new {
Year = p.Year,
wa2 = p.weightSum != 0 ? p.weightedValueSum / p.weightSum : -1
})
.ToArray();
That should be more or less equivalent to this in full LINQ syntax
var res2 = (from p in scores
group p by p.Date.Year into p
let Year = p.Key
let weightedValueSum = p.Sum(x => x.PossScore * x.Weight)
let weightSum = p.Sum(x => x.Weight)
select new {
Year = Year,
wa2 = weightSum != 0 ? weightedValueSum / weightSum : -1
}).ToArray();