Как отсортировать список с дубликатами ключей?


У меня есть набор элементов/ключей, которые я читаю из двух разных конфигурационных файлов. Таким образом, ключи могут быть одинаковыми, но с разными значениями, связанными с каждым из них.

Я хочу перечислить их в отсортированном порядке. Что я могу сделать ? Я пробовал с классом SortedList, но он не позволяет дублировать ключи.

Как я могу это сделать?

Например, предположим, что у меня есть 3 элемента с ключами 1,2,3. Затем я получаю еще один элемент, имеющий ключ 2 (но другое значение). Затем я хочу, чтобы новый ключ был вставлен после существующий ключ 2, но до 3. Если я пытаюсь найти элемент с ключом 2, то он должен идти после самого последнего добавленного ключа 2.

Обратите внимание, что я использую .NET 2.0

10 12

10 ответов:

Я предпочитаю использовать LINQ для этого типа вещей:

using System.Linq;

...

var mySortedList = myList.Orderby(l => l.Key)
                         .ThenBy(l => l.Value);

foreach (var sortedItem in mySortedList) {
    //You'd see each item in the order you specified in the loop here.
}

Примечание: Для этого необходимо использовать .NET 3.5 или более позднюю версию.

Вам нужна функция сортировки с пользовательским IComparer. Теперь у вас есть icomparer по умолчанию, когда вы используете сортировку. это позволит проверить значение поля.

При создании пользовательского IComparer (вы делаете это в своем классе, реализуя интерфейс Icomparable). вот что он делает: ваш объект проверяет себя на каждый другой объект в списке, который вы сортируете.

Это делается функцией. (не волнуйтесь, VS будет реализовывать его при упоминании вашего интерфейс

public class  ThisObjectCLass : IComparable{

    public int CompareTo(object obj) {
            ThisObjectCLass something = obj as ThisObjectCLass ;
            if (something!= null) 
                if(this.key.CompareTo(object.key) == 0){
                //then:
                   if .....
                }
                else if(this.value "is more important then(use some logic here)" something.value){
                 return 1
                }
                else return -1
            else
               throw new ArgumentException("I am a dumb little rabid, trying to compare different base classes");
        }
}

Прочитайте ссылки выше для получения более подробной информации.

Я знаю, что у меня были некоторые проблемы с пониманием этого в самом начале, поэтому для любой дополнительной помощи добавьте комментарий, и я уточню

Я сделал это, создав SortedList<int, List<string>>. Всякий раз, когда я нахожу дубликат ключа, я просто вставляю значение в существующий список, связанный с ключом, уже присутствующим в объекте SortedList. Таким образом, я могу иметь список значений для конкретного ключа.

Используйте свой собственный класс сравнения! Если ваши ключи в отсортированном списке являются целыми числами, вы можете использовать, например, такой компаратор:

public class DegreeComparer : IComparer<int>
{
    #region IComparer<int> Members

    public int Compare(int x, int y)
    {
        if (x < y)
            return -1;
        else
            return 1;
    }

    #endregion
}

Для создания нового списка сортировки с ключами int и строковыми значениями используйте:

var mySortedList = new SortedList<int, string>(new DegreeComparer());

Если вам действительно безразлична последовательность элементов с одинаковыми ключами, добавьте все в список, а затем отсортируйте его по ключу:

static void Main(string[] args)
{
   List<KeyValuePair<int, MyClass>> sortedList = 
      new List<KeyValuePair<int, MyClass>>() {
         new KeyValuePair<int, MyClass>(4, new MyClass("four")), 
         new KeyValuePair<int, MyClass>(7, new MyClass("seven")),
         new KeyValuePair<int, MyClass>(5, new MyClass("five")),
         new KeyValuePair<int, MyClass>(4, new MyClass("four-b")),
         new KeyValuePair<int, MyClass>(7, new MyClass("seven-b"))
      };
   sortedList.Sort(Compare);
}
static int Compare(KeyValuePair<int, MyClass> a, KeyValuePair<int, MyClass> b)
{
   return a.Key.CompareTo(b.Key);
}

Если вы действительно хотите, чтобы элементы, вставленные позже, были после вставленных ранее, отсортируйте их по мере их вставки:

class Sorter : IComparer<KeyValuePair<int, MyClass>>
{

static void Main(string[] args)
{
   List<KeyValuePair<int, MyClass>> sortedList = new List<KeyValuePair<int, MyClass>>();
   Sorter sorter = new Sorter();
   foreach (KeyValuePair<int, MyClass> kv in new KeyValuePair<int, MyClass>[] {
      new KeyValuePair<int, MyClass>(4, new MyClass("four")), 
      new KeyValuePair<int, MyClass>(7, new MyClass("seven")),
      new KeyValuePair<int, MyClass>(5, new MyClass("five")),
      new KeyValuePair<int, MyClass>(4, new MyClass("four-b")),
      new KeyValuePair<int, MyClass>(4, new MyClass("four-c")),
      new KeyValuePair<int, MyClass>(7, new MyClass("seven-b")) })
   {
      sorter.Insert(sortedList, kv);
   }
   for (int i = 0; i < sortedList.Count; i++)
   {
      Console.WriteLine(sortedList[i].ToString());
   }
}
void Insert(List<KeyValuePair<int, MyClass>> sortedList, KeyValuePair<int, MyClass> newItem)
{
   int newIndex = sortedList.BinarySearch(newItem, this);
   if (newIndex < 0)
      sortedList.Insert(~newIndex, newItem);
   else
   {
      while (newIndex < sortedList.Count && (sortedList[newIndex].Key == newItem.Key))
         newIndex++;
      sortedList.Insert(newIndex, newItem);
   }
}
#region IComparer<KeyValuePair<int,MyClass>> Members

public int Compare(KeyValuePair<int, MyClass> x, KeyValuePair<int, MyClass> y)
{
   return x.Key.CompareTo(y.Key);
}

#endregion
}

Или у вас может быть отсортирован список списков:

static void Main(string[] args)
{
   SortedDictionary<int, List<MyClass>> sortedList = new SortedDictionary<int,List<MyClass>>();
   foreach (KeyValuePair<int, MyClass> kv in new KeyValuePair<int, MyClass>[] {
      new KeyValuePair<int, MyClass>(4, new MyClass("four")), 
      new KeyValuePair<int, MyClass>(7, new MyClass("seven")),
      new KeyValuePair<int, MyClass>(5, new MyClass("five")),
      new KeyValuePair<int, MyClass>(4, new MyClass("four-b")),
      new KeyValuePair<int, MyClass>(4, new MyClass("four-c")),
      new KeyValuePair<int, MyClass>(7, new MyClass("seven-b")) })
   {
      List<MyClass> bucket;
      if (!sortedList.TryGetValue(kv.Key, out bucket))
         sortedList[kv.Key] = bucket = new List<MyClass>();
      bucket.Add(kv.Value);
   }
   foreach(KeyValuePair<int, List<MyClass>> kv in sortedList)
   {
      for (int i = 0; i < kv.Value.Count; i++ )
         Console.WriteLine(kv.Value[i].ToString());
   }
}

Я не уверен, что вы можете использовать инициализаторы списков в .NET 2.0, как я сделал в первом примере выше, но я уверен, что вы знаете, как заполнить список с помощью данные.

.NET не имеет огромной поддержки длястабильных сортировок (это означает, что эквивалентные элементы сохраняют свой относительный порядок при сортировке). Однако вы можете написать свой собственный stable-sorted-insert, используя List.BinarySearch и пользовательский IComparer<T> (который возвращает -1, если ключ меньше или равен цели, и +1, если больше).

Обратите внимание, что List.Sort не является стабильной сортировкой, поэтому вам придется либо написать свою собственную стабильную процедуру quicksort, либо просто использовать сортировку вставки для первоначального заполнения коллекция.

Как насчет этого

        SortedList<string, List<string>> sl = new SortedList<string, List<string>>();

        List<string> x = new List<string>();

        x.Add("5");
        x.Add("1");
        x.Add("5");
        // use this to load  
        foreach (string z in x)
        {
            if (!sl.TryGetValue(z, out x))
            {
                sl.Add(z, new List<string>());
            }

            sl[z].Add("F"+z);
        }
        // use this to print 
        foreach (string key in sl.Keys)
        {
            Console.Write("key=" + key + Environment.NewLine);

            foreach (string item in sl[key])
            {
                Console.WriteLine(item);
            }
        }

Рассматривали ли вы класс NameValueCollection , поскольку он позволяет хранить несколько значений на ключ? например, вы можете иметь следующее:

    NameValueCollection nvc = new NameValueCollection();
    nvc.Add("1", "one");
    nvc.Add("2", "two");
    nvc.Add("3", "three");

    nvc.Add("2", "another value for two");
    nvc.Add("1", "one bis");

И затем, чтобы получить значения, которые вы могли бы иметь:

    for (int i = 0; i < nvc.Count; i++)
    {
        if (nvc.GetValues(i).Length > 1)
        {
            for (int x = 0; x < nvc.GetValues(i).Length; x++)
            {
                Console.WriteLine("'{0}' = '{1}'", nvc.GetKey(i), nvc.GetValues(i).GetValue(x));
            }
        }
        else
        {
            Console.WriteLine("'{0}' = '{1}'", nvc.GetKey(i), nvc.GetValues(i)[0]);
        }

    }

Которые дают выход:

'1' = 'один'

'1' = 'один бис'

'2' = 'два'

'2' = 'другое значение для двух'

'3' = 'три'

В .NET 2.0 можно написать:

List<KeyValuePair<string, string>> keyValueList = new List<KeyValuePair<string, string>>();

// Simulate your list of key/value pair which key could be duplicate
keyValueList.Add(new KeyValuePair<string,string>("1","One"));
keyValueList.Add(new KeyValuePair<string,string>("2","Two"));
keyValueList.Add(new KeyValuePair<string,string>("3","Three"));

// Here an entry with duplicate key and new value
keyValueList.Add(new KeyValuePair<string, string>("2", "NEW TWO")); 

// Your final sorted list with one unique key
SortedList<string, string> sortedList = new SortedList<string, string>();

foreach (KeyValuePair<string, string> s in keyValueList)
{
    // Use the Indexer instead of Add method
    sortedList[s.Key] = s.Value;
}

Вывод:

[1, One]
[2, NEW TWO]
[3, Three]

У меня была похожая проблема, когда я разрабатывал игру, похожую на концепцию шахматной игры, в которой компьютер делает ход. Мне нужно было иметь возможность нескольких фигур, способных сделать ход, и, следовательно, мне нужно было иметь несколько состояний доски. Каждое государство совета должно было быть ранжировано на основе положения фигур. Для простоты и убедительности предположим, что моя игра была "крестики-нолики", и я был "крестики-нолики", и компьютер был "крестики-нолики". Если бы состояние доски показывало 3 в ряд из нулей тогда это лучшее состояние для меня, если оно показывает 3 в ряду крестиков, то это худшее состояние для меня и лучшее для компьютера. Есть другие состояния во время игры, которые более благоприятны для одного или другого, и, кроме того, есть много состояний, которые приводят к ничьей, так как я могу ранжировать его, когда есть равные Ранговые баллы. Это то, что я придумал (заранее извинитесь, если вы не программист VB).

Мой класс сравнения:

Class ByRankScoreComparer
    Implements IComparer(Of BoardState)

    Public Function Compare(ByVal bs1 As BoardState, ByVal bs2 As BoardState) As Integer Implements IComparer(Of BoardState).Compare
        Dim result As Integer = bs2.RankScore.CompareTo(bs1.RankScore) 'DESCENDING order
        If result = 0 Then
            result = bs1.Index.CompareTo(bs2.Index)
        End If
        Return result
    End Function
End Class

Мой заявления:

Dim boardStates As SortedSet(Of BoardState)(New ByRankScoreComparer)

Мой совет-государственная реализация:

Class BoardState
    Private Shared BoardStateIndex As Integer = 0
    Public ReadOnly Index As Integer
    ...
    Public Sub New ()
        BoardStateIndex += 1
        Index = BoardStateIndex
    End Sub
    ...
End Class

Как вы можете видеть, Ранговые баллы поддерживаются в порядке убывания, и любые 2 состояния, имеющие одинаковый ранговый балл, более позднее состояние идет на дно, поскольку оно всегда будет иметь больший присвоенный индекс, и, таким образом, это позволяет дублировать. Я также могу смело звонить в boardStates.Remove (myCurrentBoardState), который также использует компаратор, и компаратор должен возвращать значение 0, чтобы найти объект, подлежащий удалению.