C# Sortable collection which allows duplicate keys

前端 未结 16 2113
旧巷少年郎
旧巷少年郎 2020-11-28 10:06

I am writing a program to set a sequence in which various objects will appear in report. The sequence is the Y position (cell) on Excel spreadsheet.

A demo part of co

相关标签:
16条回答
  • 2020-11-28 10:51

    Linq.Lookup is cool and all, but if your target is to simply loop over the "keys" while allowing them to be duplicated you can use this structure:

    List<KeyValuePair<String, String>> FieldPatterns = new List<KeyValuePair<string, string>>() {
       new KeyValuePair<String,String>("Address","CommonString"),
       new KeyValuePair<String,String>("Username","UsernamePattern"),
       new KeyValuePair<String,String>("Username","CommonString"),
    };
    

    Then you can write:

    foreach (KeyValuePair<String,String> item in FieldPatterns)
    {
       //use item.Key and item.Value
    }
    

    HTH

    0 讨论(0)
  • 2020-11-28 10:55

    This is how I solved the problem. It's meant to be thread-safe though you can simply remove the locks if you don't need that. Also note arbitrary Insert at an index is not supported because that could violate the sort condition.

    public class ConcurrentOrderedList<Titem, Tsort> : ICollection<Titem>
    {
        private object _lock = new object();
        private SortedDictionary<Tsort, List<Titem>> _internalLists;
        Func<Titem, Tsort> _getSortValue;
        
        public ConcurrentOrderedList(Func<Titem,Tsort> getSortValue)
        {
            _getSortValue = getSortValue;
            _internalLists = new SortedDictionary<Tsort, List<Titem>>();            
        }
    
        public int Count { get; private set; }
    
        public bool IsReadOnly => false;
    
        public void Add(Titem item)
        {
            lock (_lock)
            {
                List<Titem> values;
                Tsort sortVal = _getSortValue(item);
                if (!_internalLists.TryGetValue(sortVal, out values))
                {
                    values = new List<Titem>();
                    _internalLists.Add(sortVal, values);
                }
                values.Add(item);
                Count++;
            }            
        }
    
        public bool Remove(Titem item)
        {
            lock (_lock)
            {
                List<Titem> values;
                Tsort sortVal = _getSortValue(item);
                if (!_internalLists.TryGetValue(sortVal, out values))
                    return false;
    
                var removed = values.Remove(item);
                if (removed)
                    Count--;
                return removed;
            }
        }
    
        public void Clear()
        {
            lock (_lock)
            {
                _internalLists.Clear();
            }
        }
    
        public bool Contains(Titem item)
        {
            lock (_lock)
            {
                List<Titem> values;
                Tsort sortVal = _getSortValue(item);
                if (!_internalLists.TryGetValue(sortVal, out values))
                    return false;
                return values.Contains(item);
            }
        }
    
        public void CopyTo(Titem[] array, int arrayIndex)
        {
            int i = arrayIndex;
            lock (_lock)
            {
                foreach (var list in _internalLists.Values)
                {
                    list.CopyTo(array, i);
                    i += list.Count;
                }
            }
        }
    
        public IEnumerator<Titem> GetEnumerator()
        {
            foreach (var list in _internalLists.Values)
            {
                foreach (var item in list)
                    yield return item;
            }
        }
    
        public int IndexOf(Titem item)
        {
            int i = 0;
            var sortVal = _getSortValue(item);
            lock (_lock)
            {               
                foreach (var list in _internalLists)
                {
                    if (object.Equals(list.Key, sortVal))
                    {
                        int intIndex = list.Value.IndexOf(item);
                        if (intIndex == -1)
                            return -1;
                        return i + intIndex;
                    }
                    i += list.Value.Count;
                }
                return -1;
            }           
        }
    
        public void Insert(int index, Titem item)
        {
            throw new NotSupportedException();
        }
    
        // Note this method is indeterminate if there are multiple
        // items in the same sort position!
        public void RemoveAt(int index)
        {
            int i = 0;
            lock (_lock)
            {
                foreach (var list in _internalLists.Values)
                {
                    if (i + list.Count < index)
                    {
                        i += list.Count;
                        continue;
                    }
                    else
                    {
                        list.RemoveAt(index - i);
                        return;
                    }
                }
            }
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
    
    0 讨论(0)
  • 2020-11-28 10:56

    Use your own IComparer!

    Like already stated in some other answers, you should use your own comparer class. For this sake I use a generic IComparer class, that works with anything that implements IComparable:

    /// <summary>
    /// Comparer for comparing two keys, handling equality as beeing greater
    /// Use this Comparer e.g. with SortedLists or SortedDictionaries, that don't allow duplicate keys
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    public class DuplicateKeyComparer<TKey>
                    :
                 IComparer<TKey> where TKey : IComparable
    {
        #region IComparer<TKey> Members
    
        public int Compare(TKey x, TKey y)
        {
            int result = x.CompareTo(y);
    
            if (result == 0)
                return 1;   // Handle equality as beeing greater
            else
                return result;
        }
    
        #endregion
    }
    

    You will use it when instancing a new SortedList, SortedDictionary etc:

    SortedList<int, MyValueClass> slist = new SortedList<int, MyValueClass>(new DuplicateKeyComparer<int>());
    

    Here int is the key that can be duplicate.

    0 讨论(0)
  • 2020-11-28 10:57

    The problem is that the data structure design doesn't match the requirements: It is necessary to store several Headers for the same XPos. Therefore, SortedList<XPos, value> should not have a value of Header, but a value of List<Header>. It's a simple and small change, but it solves all problems and avoids creating new problems like other suggested solutions (see explanation below):

    using System;
    using System.Collections.Generic;
    
    namespace TrySortedList {
      class Program {
    
        class Header {
          public int XPos;
          public string Name;
        }
    
        static void Main(string[] args) {
          SortedList<int, List<Header>> sortedHeaders = new SortedList<int,List<Header>>();
          add(sortedHeaders, 1, "Header_1");
          add(sortedHeaders, 1, "Header_2");
          add(sortedHeaders, 2, "Header_3");
          foreach (var headersKvp in sortedHeaders) {
            foreach (Header header in headersKvp.Value) {
              Console.WriteLine(header.XPos + ": " + header.Name);
            }
          }
        }
    
        private static void add(SortedList<int, List<Header>> sortedHeaders, int xPos, string name) {
          List<Header> headers;
          if (!sortedHeaders.TryGetValue(xPos, out headers)){
            headers = new List<Header>();
            sortedHeaders[xPos] = headers;
          }
          headers.Add(new Header { XPos = xPos, Name = name });
        }
      }
    }
    
    Output:
    1: Header_1
    1: Header_2
    2: Header_3
    

    Please note that adding a "funny" key, like adding a random number or pretending that 2 XPos with the same value are different lead to many other problems. For example it becomes difficult or even impossible to remove a particular Header.

    Also note that the sorting performance is much better if only few List<Header> have to be sorted than every Header. Example: If there are 100 XPos and each has 100 headers, 10000 Header need to be sorted as opposed to 100 List<Header>.

    Of course, also this solution has a disadvantage: If there are many XPos with only 1 Header, as many Lists need to be created, which is some overhead.

    0 讨论(0)
  • 2020-11-28 10:57

    Thanks a lot for your help. While searching more, I found this solution. (Available in Stackoverflow.com in other question)

    First, I created a class which would encapsulate my objects for classes (Headers,Footer etc)

    public class MyPosition
    {
        public int Position { get; set; }
        public object MyObjects{ get; set; }
    }
    

    So this class is supposed to hold on the objects, and PosX of each object goes as int Position

    List<MyPosition> Sequence= new List<MyPosition>();
    Sequence.Add(new MyPosition() { Position = 1, Headerobject });
    Sequence.Add(new MyPosition() { Position = 2, Headerobject1 });
    Sequence.Add(new MyPosition() { Position = 1, Footer });
    
    League.Sort((PosA, PosB) => PosA.Position.CompareTo(PosB.Position));
    

    What eventually I get is the sorted "Sequence" list.

    0 讨论(0)
  • 2020-11-28 10:57

    You can use the SortedList, use your value for the TKey, and int (count) for the TValue.

    Here's a sample: A function that sorts the letters of a word.

        private string sortLetters(string word)
        {
            var input = new System.Collections.Generic.SortedList<char, int>();
    
            foreach (var c in word.ToCharArray())
            {
                if (input.ContainsKey(c))
                    input[c]++;
                else
                    input.Add(c, 1);
            }
    
            var output = new StringBuilder();
    
            foreach (var kvp in input)
            {
                output.Append(kvp.Key, kvp.Value);
            }
    
            string s;
    
            return output.ToString();
    
        }
    
    0 讨论(0)
提交回复
热议问题