Как отсортировать список с дубликатами ключей?
У меня есть набор элементов/ключей, которые я читаю из двух разных конфигурационных файлов. Таким образом, ключи могут быть одинаковыми, но с разными значениями, связанными с каждым из них.
Я хочу перечислить их в отсортированном порядке. Что я могу сделать ? Я пробовал с классом SortedList
, но он не позволяет дублировать ключи.
Как я могу это сделать?
Например, предположим, что у меня есть 3 элемента с ключами 1,2,3. Затем я получаю еще один элемент, имеющий ключ 2 (но другое значение). Затем я хочу, чтобы новый ключ был вставлен после существующий ключ 2, но до 3. Если я пытаюсь найти элемент с ключом 2, то он должен идти после самого последнего добавленного ключа 2.Обратите внимание, что я использую .NET 2.0
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, чтобы найти объект, подлежащий удалению.