I've tried looking for an existing question but wasn't sure how to phrase this and this retrieved no results anywhere :(
Anyway, I have a class of "Order Items" that has different properties. These order items are for clothing, so they will have a size (string).
Because I am OCD about these sorts of things, I would like to have the elements sorted not by the sizes as alphanumeric values, but by the sizes in a custom order.
I would also like to not have this custom order hard-coded if possible.
To break it down, if I have a list of these order items with a size in each one, like so:
2XL
S
5XL
M
With alphanumeric sorting it would be in this order:
2XL
5XL
M
S
But I would like to sort this list into this order (from smallest size to largest):
S
M
2XL
5XL
The only way I can think of to do this is to have a hard-coded array of the sizes and to sort by their index, then when I need to grab the size value I can grab the size order array[i] value. But, as I said, I would prefer this order not to be hard-coded.
The reason I would like the order to be dynamic is the order items are loaded from files on the hard disk at runtime, and also added/edited/deleted by the user at run-time, and they may contain a size that I haven't hard-coded, for example I could hard code all the way from 10XS to 10XL but if someone adds the size "110cm" (aka a Medium), it will turn up somewhere in the order that I don't want it to, assuming the program doesn't crash and burn.
I can't quite wrap my head around how to do this.
Also, you could create a Dictionary<int, string> and add Key as Ordering order below. Leaving some gaps between Keys to accomodate new sizes for the future. Ex: if you want to add L (Large), you could add a new item as {15, "L"} without breaking the current order.
Dictionary<int, string> mySizes = new Dictionary<int, string> {
{ 20, "2XL" }, { 1, "S" },
{ 30, "5XL" }, { 10, "M" }
};
var sizes = mySizes.OrderBy(s => s.Key)
.Select(s => new {Size = s.Value})
.ToList();
You can use OrderByDescending + ThenByDescending directly:
sizes.OrderByDescending(s => s == "S")
.ThenByDescending( s => s == "M")
.ThenByDescending( s => s == "2XL")
.ThenByDescending( s => s == "5XL")
.ThenBy(s => s);
I use ...Descending since a true is similar to 1 whereas a false is 0.
I would implement IComparer<string> into your own TShirtSizeComparer. You might have to do some regular expressions to get at the values you need.
IComparer<T> is a great interface for any sorting mechanism. A lot of built-in stuff in the .NET framework uses it. It makes the sorting reusable.
I would really suggest parsing the size string into a separate object that has the size number and the size size then sorting with that.
You need to implement the IComparer interface on your class. You can google how to do that as there are many examples out there
you'll have to make a simple parser for this. You can search inside the string for elements like XS XL and cm" if you then filter that out you have your unit. Then you can obtain the integer that is the value. If you have that you can indeed use an IComparer object but it doesn't have that much of an advantage.
I would make a class out of Size, it is likely that you will need to add more functionality to this in the future. I added the full name of the size, but you could also add variables like width and length, and converters for inches or cm.
private void LoadSizes()
{
List<Size> sizes = new List<Size>();
sizes.Add(new Size("2X-Large", "2XL", 3));
sizes.Add(new Size("Small", "S", 1));
sizes.Add(new Size("5X-Large", "5XL", 4));
sizes.Add(new Size("Medium", "M", 2));
List<string> sizesShortNameOrder = sizes.OrderBy(s => s.Order).Select(s => s.ShortName).ToList();
//If you want to use the size class:
//List<Size> sizesOrder = sizes.OrderBy(s => s.Order).ToList();
}
public class Size
{
private string _name;
private string _shortName;
private int _order;
public string Name
{
get { return _name; }
}
public string ShortName
{
get { return _shortName; }
}
public int Order
{
get { return _order; }
}
public Size(string name, string shortName, int order)
{
_name = name;
_shortName = shortName;
_order = order;
}
}
I implemented TShirtSizeComparer with base class Comparer<object>. Of course you have to adjust it to the sizes and objects you have available:
public class TShirtSizeComparer : Comparer<object>
{
// Compares TShirtSizes and orders them by size
public override int Compare(object x, object y)
{
var _sizesInOrder = new List<string> { "None", "XS", "S", "M", "L", "XL", "XXL", "XXXL", "110 cl", "120 cl", "130 cl", "140 cl", "150 cl" };
var indexX = -9999;
var indexY = -9999;
if (x is TShirt)
{
indexX = _sizesInOrder.IndexOf(((TShirt)x).Size);
indexY = _sizesInOrder.IndexOf(((TShirt)y).Size);
}
else if (x is TShirtListViewModel)
{
indexX = _sizesInOrder.IndexOf(((TShirtListViewModel)x).Size);
indexY = _sizesInOrder.IndexOf(((TShirtListViewModel)y).Size);
}
else if (x is MySelectItem)
{
indexX = _sizesInOrder.IndexOf(((MySelectItem)x).Value);
indexY = _sizesInOrder.IndexOf(((MySelectItem)y).Value);
}
if (indexX > -1 && indexY > -1)
{
return indexX.CompareTo(indexY);
}
else if (indexX > -1)
{
return -1;
}
else if (indexY > -1)
{
return 1;
}
else
{
return 0;
}
}
}
To use it you just have a List or whatever your object is and do:
tshirtList.Sort(new TShirtSizeComparer());
The order you have "hard-coded" is prioritized and the rest is put to the back.
I'm sure it can be done a bit smarter and more generalized to avoid hard-coding it all. You could e.g. look for sizes ending with an "S" and then check how many X's (e.g. XXS) or the number before X (e.g. 2XS) and sort by that, and then repeat for "L" and perhaps other "main sizes".
Related
I have a class which has approx 50 properties, instances of this class is added to a list. This list is then added to a Velocity context. Now, I would like to sort this data. Which field, or if it is ascending or descending is not known until the template is being parsed.
Resources I've looked into:
Better way to use Velocity's GenericTools in a Standalone app?
Velocity foreach sort list
http://velocity.apache.org/tools/devel/generic/
Based on the resources listed here I can't figure out how to solve this. Is the GenericTools available for the Castle's Nvelocity? If not, how may I implement such a generic sort I'm asking for here?
My solution was to write my own sort-class and add this as a context to nvelocity. I'm passing the field to sort on as string and accessing it as reflection. I'm also setting sort ascending or descending by string value. I'm also passing in the name of the comparer and accessing this with reflection as well. I'm then using the List method OrderBy or OrderByDescending with the choosen field and comparer.
I did find parts of the code here: http://zootfroot.blogspot.co.uk/2009/10/dynamic-linq-orderby.html
public class NvelocitySort
{
public List<MyObject> Sort(List<MyObject> list, string fieldAndMode, string comparerName)
{
fieldAndMode = fieldAndMode.Trim();
// Split the incoming string to get the field name and sort ascending or descending
string[] split = fieldAndMode.Split(' ');
// Set default sort mode
string mode = "asc";
// If sort mode not specified, this will be the field name
string field = fieldAndMode;
// If sort mode added split length shall be 2
if (split.Length == 2)
{
field = split[0];
if (split[1].ToLower() == "asc" || split[1].ToLower() == "ascending") mode = "asc";
if (split[1].ToLower() == "desc" || split[1].ToLower() == "descending") mode = "desc";
}
// If length is more than 2 or 0, return same list as passed in
else if (split.Length > 2 || split.Length == 0)
{
return list;
}
// Get comparer based on comparer name
IComparer<string> comparer = (IComparer<string>)Activator.CreateInstance(Type.GetType(string.Format("Namespace.{0}", comparerName)));
// Choose the sort order
if (mode == "asc")
return list.OrderBy(item => item.GetReflectedPropertyValue(field), comparer).ToList();
if (mode == "desc")
return list.OrderByDescending(item => item.GetReflectedPropertyValue(field), comparer).ToList();
// If sort order not asc/desc return same list as passed in
return list;
}
}
This is the reflection method for retrieving the field.
public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}
Simple comparer example:
public class TextComparer : IComparer<string>
{
public int Compare(string x, string y)
{
return string.Compare(x, y);
}
}
Added to Nvelocity context like this:
this.velocityContext.Put("sorter", new NvelocitySort());
Accessed from Nvelocity template like this:
#foreach($item in $sorter.Sort($listObject, "Name desc", "TextComparer"))
$item.Name
#end
Hope it helps someone else...
EDIT:
Found an even better way to do it (implements multiple fields sorting):
http://www.codeproject.com/Articles/280952/Multiple-Column-Sorting-by-Field-Names-Using-Linq
I have a form where I collect data from users. When this data is collected, I pass it to various partners, however each partner has their own rules for each piece of data, so this has to be converted. I can make this happen, but my worries are about the robustness. Here's some code:
First, I have an enum. This is mapped to dropdown a dropdown list - the description is the text value, and the int mapped to the value.
public enum EmploymentStatusType
{
[Description("INVALID!")]
None = 0,
[Description("Permanent full-time")]
FullTime = 1,
[Description("Permanent part-time")]
PartTime = 2,
[Description("Self employed")]
SelfEmployed = 3
}
When the form is submitted, the selected value is converted to its proper type and stored in another class - the property looks like this:
protected virtual EmploymentStatusType EmploymentStatus
{
get { return _application.EmploymentStatus; }
}
For the final bit of the jigsaw, I convert the value to the partners required string value:
Dictionary<EmploymentStatusType, string> _employmentStatusTypes;
Dictionary<EmploymentStatusType, string> EmploymentStatusTypes
{
get
{
if (_employmentStatusTypes.IsNull())
{
_employmentStatusTypes = new Dictionary<EmploymentStatusType, string>()
{
{ EmploymentStatusType.FullTime, "Full Time" },
{ EmploymentStatusType.PartTime, "Part Time" },
{ EmploymentStatusType.SelfEmployed, "Self Employed" }
};
}
return _employmentStatusTypes;
}
}
string PartnerEmploymentStatus
{
get { return _employmentStatusTypes.GetValue(EmploymentStatus); }
}
I call PartnerEmploymentStatus, which then returns the final output string.
Any ideas how this can be made more robust?
Then you need to refactor it into one translation area. Could be something like a visitor pattern implementation. Your choices are distribute the code (as you are doing now) or visitor which would centralize it. You need to build in a degree of fragility so your covering tests will show problems when you extend in order to force you to maintain the code properly. You are in a fairly common quandry which is really a code organisational one
I did encounter such a problem in one of my projects and I solved it by using a helper function and conventions for resource names.
The function is this one:
public static Dictionary<T, string> GetEnumNamesFromResources<T>(ResourceManager resourceManager, params T[] excludedItems)
{
Contract.Requires(resourceManager != null, "resourceManager is null.");
var dictionary =
resourceManager.GetResourceSet(culture: CultureInfo.CurrentUICulture, createIfNotExists: true, tryParents: true)
.Cast<DictionaryEntry>()
.Join(Enum.GetValues(typeof(T)).Cast<T>().Except(excludedItems),
de => de.Key.ToString(),
v => v.ToString(),
(de, v) => new
{
DictionaryEntry = de,
EnumValue = v
})
.OrderBy(x => x.EnumValue)
.ToDictionary(x => x.EnumValue, x => x.DictionaryEntry.Value.ToString());
return dictionary;
}
The convention is that in my resource file I will have properties that are the same as enum values (in your case None, PartTime etc). This is needed to perform the Join in the helper function which, you can adjust to match your needs.
So, whenever I want a (localized) string description of an enum value I just call:
var dictionary = EnumUtils.GetEnumNamesFromResources<EmploymentStatusType>(ResourceFile.ResourceManager);
var value = dictionary[EmploymentStatusType.Full];
Given:
IMatchCriteria {
string PropA{get;}
string PropB{get;}
int? PropC {get;}
int? PropD {get;}
}
IReportRecord : IMatchCriteria {...}
IMatchCriteriaSet : IMatchCriteria {
int MatchId {get;}
double Limit{get;}
}
public class Worker{
private List<IMatchCriteriaSet> _matchers = GetIt();
//Expecting this list to be huge, ***upto 0.1m***. Some of the sample matchers:
// MatchId=1, Limit=1000, PropA=A, PropC=101, PropD=201
// MatchId=2, Limit=10, PropA=A
// MatchId=3, Limit=20, PropC=101
// MatchId=4, Limit=500, PropD=201
//Based on sample entries:
//Input: reportRecord{ PropA=A, PropC=101 }, Ouput: 1000, 20
//Input: reportRecord{ PropA=A1, PropC=102, PropD=201 }, Ouput: 500
public IEnumerable<double> GetMatchingLimits(IReportRecord reportRecord) {
//Bad, very bad option:
foreach(var matcher in _matchers){
var matchFound=true;
if(reportRecord.PropA!=null && reportRecord.PropA!=matcher.PropA){
continue;
}
if(reportRecord.PropB!=null && reportRecord.PropA!=matcher.PropB){
continue;
}
if(reportRecord.PropC!=null && reportRecord.PropC.Value!=matcher.PropC.Value){
continue;
}
if(reportRecord.PropD!=null && reportRecord.PropD.Value!=matcher.PropD.Value){
continue;
}
yield return matcher.Limit;
}
}
}
Note: Expecting IMatchCriteriaSet to be 0.1m records.
Expecting GetMatchingLimits to be called 1m times.
The requirement is to do all this for a real-time application.
Essentially what I need is a way to index list of IMatchCriteria. But can not use Dictionary because my key is not defined.
Looking for some algorithm to tackle this problem efficiently.
Any suggested solution in scope of .net (not just c#) would be useful.
Thanks.
Use one dictionary for each indexable property, mapping to a set of matchers. Then you can do a dictionary lookup for every property that is set in your record (logarithmic complexity), and intersect the resulting sets. Start with the smallest result set and whittle it down to get the best run time.
I have a simple domain object:
class FavoriteFood
{
public string Name;
public int Ordinal;
}
I want to have a collection of this domain object that maintains the correct ordinal. For example, given 4 favorite foods:
Name: Banana, Ordinal: 1
Name: Orange, Ordinal: 2
Name: Pear, Ordinal: 3
Name: Watermelon, Ordinal: 4
If I change Pear's ordinal to 4 it should shift Watermelon's ordinal down to 3.
If I add a new favorite food (Strawberry) with ordinal 3 it should shift Pear up to 4 and Watermelon up to 5.
If I change Pear's ordinal to 2 it should shift Orange up to 3.
If I change Watermelon's ordinal to 1, Banana would bump up to 2, Orange would bump up to 3, and Pear would bump up to 4.
What's the best way to accomplish this?
UPDATE: The name property of the domain object is dynamic and based on user input. The object has to have this Ordinal property because a user can change the order in which their favorite foods are displayed. This ordinal value is saved in a database and when populating the structure I cannot guarantee the items are added in order of their ordinals.
The trouble I am running into is when the underlying domain object is changed, there isn't a good way of updating the rest of the items in the list. For example:
var favoriteFoods = new List<FavoriteFood>();
var banana = new FavoriteFood { Name = "Banana", Ordinal = 1};
favoriteFoods.Add(banana);
favoriteFoods.Add(new FavoriteFood { Name = "Orange", Ordinal = 2 });
banana.Ordinal = 2;
// at this point both Banana and Orange have the same ordinal in the list. How can we make sure that Orange's ordinal gets updated too?
So far I have tried doing the following which works :
class FavoriteFood : INotifyPropertyChanging
{
public string Name;
public int Ordinal
{
get { return this.ordinal; }
set
{
var oldValue = this.ordinal;
if (oldValue != value && this.PropertyChanging != null)
{
this.PropertyChanging(new FavoriteFoodChangingObject { NewOrdinal = value, OldOrdinal = oldValue }, new PropertyChangingEventArgs("Ordinal"));
}
this.ordinal = value;
}
}
internal struct FavoriteFoodChangingObject
{
internal int NewOrdinal;
internal int OldOrdinal;
}
// THIS IS A TEMPORARY WORKAROUND
internal int ordinal;
public event PropertyChangingEventHandler PropertyChanging;
}
public class FavoriteFoodCollection : IEnumerable<FavoriteFood>
{
private class FavoriteFoodOrdinalComparer : IComparer<FavoriteFood>
{
public int Compare(FavoriteFood x, FavoriteFood y)
{
return x.Ordinal.CompareTo(y.Ordinal);
}
}
private readonly SortedSet<FavoriteFood> underlyingList = new SortedSet<FavoriteFood>(new FavoriteFoodOrdinalComparer());
public IEnumerator<FavoriteFood> GetEnumerator()
{
return this.underlyingList.GetEnumerator();
}
public void AddRange(IEnumerable<FavoriteFood> items)
{
foreach (var i in items)
{
this.underlyingList.Add(i);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private void UpdateOrdinalsDueToRemoving(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal > item.Ordinal))
{
i.ordinal--;
}
}
public void Remove(FavoriteFood item)
{
this.underlyingList.Remove(item);
this.UpdateOrdinalsDueToRemoving(item);
}
public void Add(FavoriteFood item)
{
this.UpdateOrdinalsDueToAdding(item);
this.underlyingList.Add(item);
item.PropertyChanging += this.item_PropertyChanging;
}
private void item_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName.Equals("Ordinal"))
{
var ordinalsChanging = (FavoriteFood.FavoriteFoodChangingObject)sender;
this.UpdateOrdinalsDueToEditing(ordinalsChanging.NewOrdinal, ordinalsChanging.OldOrdinal);
}
}
private void UpdateOrdinalsDueToEditing(int newOrdinal, int oldOrdinal)
{
if (newOrdinal > oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal <= newOrdinal && x.Ordinal > oldOrdinal))
{
//i.Ordinal = i.Ordinal - 1;
i.ordinal--;
}
}
else if (newOrdinal < oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= newOrdinal && x.Ordinal < oldOrdinal))
{
//i.Ordinal = i.Ordinal + 1;
i.ordinal++;
}
}
}
private void UpdateOrdinalsDueToAdding(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= item.Ordinal))
{
i.ordinal++;
}
}
}
This works alright, but the use of the internal Ordinal field is a strange workaround. It's needed so that the PropertyChangingEvent wont be infinitely raised.
Just use a List<string>:
List<string> foods = new List<string> { "Banana", "Orange", "Pear" };
int ordinalOfOrange = foods.IndexOf("Orange");
It's not a good idea to 'store' that ordinal if it has to change the way you describe.
Sounds like you want a SortedList. Add each item using it's Ordinal as the key.
I'd do something like the following:
public class FavoriteFoods
{
StringComparer comparer ;
List<string> list ;
public FavoriteFoods()
{
this.list = new List<string>() ;
this.comparer = StringComparer.InvariantCultureIgnoreCase ;
return ;
}
public void Add( string food , int rank )
{
if ( this.list.Contains(food,comparer ) ) throw new ArgumentException("food") ;
this.list.Insert(rank,food) ;
return ;
}
public void Remove( string food )
{
this.list.Remove( food ) ;
return ;
}
public void ChangeRank( string food , int newRank )
{
int currentRank = this.list.IndexOf(food) ;
if ( currentRank < 0 ) throw new ArgumentOutOfRangeException("food") ;
if ( newRank < 0 ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank >= this.list.Count ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank != currentRank )
{
this.Remove(food) ;
this.Add( food , newRank ) ;
}
return ;
}
public int GetRank( string food )
{
int rank = this.list.IndexOf(food) ;
if ( rank < 0 ) throw new ArgumentOutOfRangeException("food");
return rank ;
}
public IEnumerable<string> InRankOrder()
{
foreach ( string food in this.list )
{
yield return food ;
}
}
}
Let me restate your problem.
You have a collection of strings. You have a collection of ordinals.
You want to be able to quickly look up the ordinal of a string. And the string of an ordinal. You'd also like to be able to insert a string with a given ordinal. And change the ordinal of a string.
There are two ways to go. The first, simple, approach is to store a collection of the strings in order, with knowledge of their ordinal. You can scan the list in time O(n). You can also lookup, insert, move, and delete in time O(n) each. If you don't actually care about performance then I would strongly suggest going this way.
If you do care about performance, then you'll need to build a custom data structure. The simplest idea is to have two trees. One tree stores the strings in alphabetical order, and tells you where in the other tree the string is. The other tree stores the strings in order of the ordinals, and stores counts of how much stuff is in various subtrees.
Now here are your basic operations.
Insert. Insert in the second tree at the correct position (if you choose to move anything else in the process, updating those things in the first tree), then insert the string in the first tree.
Lookup by string. Search the first tree, find where it is in the second tree, walk back in the second tree to find its ordinal.
Lookup by ordinal. Search the second tree, find the string.
Delete. Delete from both trees.
Move ordinal. Remove from the second tree in the old position. Insert into the second tree in the new position. Update all appropriate nodes in the first tree.
For the simple version you can just use trees. If you want to get fancy, you can look up B-Trees, Red-Black trees and other types of self-balancing trees, then pick one of those.
If you program this correctly you can guarantee that all operations take time O(log(n)). However there will be a lot of constant overhead, and for small collections the effort to be clever may be a loss relative to the simple approach.
Given a collection of records like this:
string ID1;
string ID2;
string Data1;
string Data2;
// :
string DataN
Initially Data1..N are null, and can pretty much be ignored for this question. ID1 & ID2 both uniquely identify the record. All records will have an ID2; some will also have an ID1. Given an ID2, there is a (time-consuming) method to get it's corresponding ID1. Given an ID1, there is a (time-consuming) method to get Data1..N for the record. Our ultimate goal is to fill in Data1..N for all records as quickly as possible.
Our immediate goal is to (as quickly as possible) eliminate all duplicates in the list, keeping the one with more information.
For example, if Rec1 == {ID1="ABC", ID2="XYZ"}, and Rec2 = {ID1=null, ID2="XYZ"}, then these are duplicates, --- BUT we must specifically remove Rec2 and keep Rec1.
That last requirement eliminates the standard ways of removing Dups (e.g. HashSet), as they consider both sides of the "duplicate" to be interchangeable.
How about you split your original list into 3 - ones with all data, ones with ID1, and ones with just ID2.
Then do:
var unique = allData.Concat(id1Data.Except(allData))
.Concat(id2Data.Except(id1Data).Except(allData));
having defined equality just on the basis of ID2.
I suspect there are more efficient ways of expressing that, but the fundamental idea is sound as far as I can tell. Splitting the initial list into three is simply a matter of using GroupBy (and then calling ToList on each group to avoid repeated queries).
EDIT: Potentially nicer idea: split the data up as before, then do:
var result = new HashSet<...>(allData);
result.UnionWith(id1Data);
result.UnionWith(id2Data);
I believe that UnionWith keeps the existing elements rather than overwriting them with new but equal ones. On the other hand, that's not explicitly specified. It would be nice for it to be well-defined...
(Again, either make your type implement equality based on ID2, or create the hash set using an equality comparer which does so.)
This may smell quite a bit, but I think a LINQ-distinct will still work for you if you ensure the two compared objects come out to be the same. The following comparer would do this:
private class Comp : IEqualityComparer<Item>
{
public bool Equals(Item x, Item y)
{
var equalityOfB = x.ID2 == y.ID2;
if (x.ID1 == y.ID1 && equalityOfB)
return true;
if (x.ID1 == null && equalityOfB)
{
x.ID1 = y.ID1;
return true;
}
if (y.ID1 == null && equalityOfB)
{
y.ID1 = x.ID1;
return true;
}
return false;
}
public int GetHashCode(Item obj)
{
return obj.ID2.GetHashCode();
}
}
Then you could use it on a list as such...
var l = new[] {
new Item { ID1 = "a", ID2 = "b" },
new Item { ID1 = null, ID2 = "b" } };
var l2 = l.Distinct(new Comp()).ToArray();
I had a similar issue a couple of months ago.
Try something like this...
public static List<T> RemoveDuplicateSections<T>(List<T> sections) where T:INamedObject
{
Dictionary<string, int> uniqueStore = new Dictionary<string, int>();
List<T> finalList = new List<T>();
int i = 0;
foreach (T currValue in sections)
{
if (!uniqueStore.ContainsKey(currValue.Name))
{
uniqueStore.Add(currValue.Name, 0);
finalList.Add(sections[i]);
}
i++;
}
return finalList;
}
records.GroupBy(r => r, new RecordByIDsEqualityComparer())
.Select(g => g.OrderByDescending(r => r, new RecordByFullnessComparer()).First())
or if you want to merge the records, then Aggregate instead of OrderByDescending/First.