0

My basic requirements are:

  1. I need to be able to add objects to a collection, sorted by an object property of my choosing, knowing that the value of this property may occur more than once.
  2. I need to be able to later remove objects from this collection, preferably using the IComparer implementation built into the sorted object.

(I think I'd actually settle for the ability to just add and remove the sorted duplicate values without storing their associated objects, if this makes things any easier.)

Note #1: I've been using the IComparer implementation for duplicate keys as recommended here: C# Sortable collection which allows duplicate keys

However, I run into a problem when I want to remove an object. Since I'm using a custom IComparer object, the dictionary or list fails to find the object by its intrinsic IComparer implementation.

Note #2: I've also tried using the PowerCollections OrderedMultiDictionary class. This didn't bring me much closer to a solution, as I still have to choose between the ability to have duplicate keys (via a custom IComparer implementation) and being able to remove objects from the collection afterwards.

  • How do you decide, which of the objects with the same key is the original and which is the duplicate? Do you want to remove *all* duplicate keys (ie none of the duplicates remain) or just the duplicates (ie one of the duplicates remains)? In general: You have overridded equality in a way there is no equality any more, so you can't use equality for removal ... – derpirscher Jul 07 '18 at 08:16
  • To clarify on why the collection must be sorted: The basic reason I need the object is to obtain a percentile statistic (i.e. give me the value at the 50% location). I want to maintain exactly x objects in the collection, while iterating over y objects. Consequently, after each iteration, I need to remove an old item and replace it with a new one. The bottom line is - I need to perform thousands of operations on this collection, and sorting after each operation will not be possible from an efficiency standpoint. – Ryan Hartstein Jul 08 '18 at 02:09
  • derpirscher - Correct, that is my conundrum. I need a way of allowing duplicates in a sorted collection, while retaining the ability to remove one duplicate at a time when I call the "remove" method. I wouldn't know in advance how many values will be duplicated. – Ryan Hartstein Jul 08 '18 at 02:11
  • Maybe you could use something like a `SortedDictionary>`? So you can add all values having the same key to a set. In this set you can use a different IComparer for removal. – derpirscher Jul 08 '18 at 02:35

2 Answers2

1

From the MS docs for List "Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists." As you can see duplicate properties are not a problem. I made a little class. It could have been a structure. In the main program I created a List and added the objects. I hope this mets most of your requirements. When removing items from the list iterating through the list use a for loop backwards because indexes change when items are removed.

private void TestList()
        {
            List<SomeObject> lst = new List<SomeObject>();
            //Add to the list
            lst.Add(new SomeObject("Mary", "Jones", 50));
            lst.Add(new SomeObject("John", "Smith", 42));
            lst.Add(new SomeObject("James", "Peterson",50));
            lst.Add(new SomeObject("Mary", "Hanes", 62));
            //Sort the list
            var sortedByAge = from obj in lst orderby obj.Age select obj;
            Debug.Print("*****List Sorted by Age*****");
            foreach (SomeObject obj in sortedByAge)
                Debug.Print($"{obj.FirstName} {obj.LastName}'s age is {obj.Age}");
            var sortByLastName = from obj in lst orderby obj.LastName select obj;
            Debug.Print("*****List Sorted by LastName*****");
            foreach (SomeObject obj in sortByLastName)
                Debug.Print($"{obj.LastName}, {obj.FirstName}");
            //Delete from list
            for (int i = lst.Count - 1; i > -1; i--)
                if (lst[i].FirstName == "Mary")
                { lst.RemoveAt(i); }
            Debug.Print("*****List after Deletes*****");
            foreach (SomeObject item in lst)
                Debug.Print($"{item.FirstName} {item.LastName} {item.Age}");
        }
        public class SomeObject
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int Age { get; set; }
            public SomeObject(string fname, string lname, int age)
            {
                FirstName = fname;
                LastName = lname;
                Age = age;
            }
        }
Mary
  • 12,341
  • 3
  • 17
  • 26
  • Thank you for your response. Maybe I should have specified the reasons for wanting a Sorted collection in my original post. The basic reason I need the object is to obtain a percentile statistic (i.e. give me the value at the 50% location). I want to maintain exactly x objects in the collection, while iterating over y objects. Consequently, after each iteration, I need to remove an old item and replace it with a new one. The bottom line is - I need to perform thousands of operations on this collection, and sorting after each operation will not be possible from an efficiency standpoint. – Ryan Hartstein Jul 08 '18 at 02:07
  • If I recall my Prob & Stat, that would be the Median. Yes? Finding the middle index should not be hard. https://blogs.msmvps.com/deborahk/linq-mean-median-and-mode/ – Mary Jul 08 '18 at 02:38
0

You cas use Distinct overriding first the equals method, example:

    public void TestMethod1 ( )
    {


        List<Model> listmodel = new List<Model>( )
        {
            new Model() { Prop1 = "one", Prop2 = 2 },
            new Model() { Prop1 = "one", Prop2 = 6 },
            new Model() { Prop1 = "two", Prop2 = 1 },
            new Model() { Prop1 = "three", Prop2 = 7 },
            new Model() { Prop1 = "four", Prop2 = 6 },
        };
        var output = listmodel.Distinct(  ).ToList( );
        output.ToList( ).ForEach( x => Console.WriteLine( x.ToString( ) ) );

    }

    public class Model
    {
        public string Prop1 { get; set; }
        public int Prop2 { get; set; }
        public string Prop3 { get; set; }
        public string Prop4 { get; set; }

        public override bool Equals ( object obj )
        {
            Model casted = obj as Model;
            if (casted == null) return false; //If cast an object to my model is null, is not my model and is not equal
            if (casted.Prop1 == this.Prop1) return true; //My logic define if the input.Prop1 is equal to my Prop1, this is equal
            return false;
        }

        public override int GetHashCode ( )
        {
            return 1;
        }

        public override string ToString ( )
        {
            return $"{this.Prop1} - {this.Prop2} - {this.Prop3} - {this.Prop4}";
        }

    }

Output:

one - 2 -  - 
two - 1 -  - 
three - 7 -  - 
four - 6 -  -

If you need the lower, you can modify the line:

var output = listmodel.Distinct(  ).ToList( );

To this:

var output = listmodel.OrderByDescending(x=> x.Prop2).Distinct(  ).ToList( );

The output change:

three - 7 -  - 
one - 6 -  - 
four - 6 -  - 
two - 1 -  -
D3ivid
  • 55
  • 2
  • Thank you for your response. There are efficiency reasons for why I need the collection to be sorted at all times (now stated above in the original request), and also, getting a distinct list isn't desired. I'm using this collection to obtain a percentile statistic, which should include duplicates. Thank you. – Ryan Hartstein Jul 08 '18 at 02:17