C # colección ordenable que permite duplicar claves

Estoy escribiendo un progtwig para establecer una secuencia en la que varios objetos aparecerán en el informe. La secuencia es la posición Y (celda) en la hoja de cálculo de Excel.

Una parte de demostración del código está debajo. Lo que quiero lograr es tener una colección, lo que me permitirá agregar varios objetos y puedo obtener una colección ordenada basada en la secuencia

SortedList list = new SortedList(); Header h = new Header(); h.XPos = 1; h.name = "Header_1"; list.Add(h.XPos, h); h = new Header(); h.XPos = 1; h.name = "Header_2"; list.Add(h.XPos, h); 

Sé que SortedList no permitirá esto y he estado buscando alternativas. No quiero eliminar los duplicados y ya List<KeyValuePair> intentado con List<KeyValuePair> .

Gracias.

¡Usa tu propio IComparer!

Como ya se dijo en algunas otras respuestas, debe usar su propia clase de comparación. Por este motivo utilizo una clase genérica IComparer, que funciona con cualquier cosa que implemente IComparable:

 ///  /// Comparer for comparing two keys, handling equality as beeing greater /// Use this Comparer eg with SortedLists or SortedDictionaries, that don't allow duplicate keys ///  ///  public class DuplicateKeyComparer : IComparer where TKey : IComparable { #region IComparer 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 } 

Lo usarás al crear una nueva SortedList, SortedDictionary, etc.

 SortedList slist = new SortedList(new DuplicateKeyComparer()); 

Aquí int es la clave que puede duplicarse.

Puede usar de forma segura la Lista <>. La Lista tiene un método Sort, una sobrecarga de la cual acepta IComparer. Puede crear su propia clase de clasificador como. Aquí hay un ejemplo:

 private List Curves; this.Curves.Sort(new CurveSorter()); public class CurveSorter : IComparer { public int Compare(Curve c1, Curve c2) { return c2.CreationTime.CompareTo(c1.CreationTime); } } 

La solución más simple (comparada con todas las anteriores): use SortedSet , acepta una IComparer , luego implemente el método Compare de esta manera:

 public int Compare(SomeClass x, SomeClass y) { var compared = x.SomeSortableKeyTypeField.CompareTo(y.SomeSortableKeyTypeField); if (compared != 0) return compared; // to allow duplicates var hashCodeCompare = x.GetHashCode().CompareTo(y.GetHashCode()); if (hashCodeCompare != 0) return hashCodeCompare; if (Object.ReferenceEquals(x, y)) return 0; // for weird duplicate hashcode cases, throw as below or implement your last chance comparer throw new ComparisonFailureException(); } 

Yo uso lo siguiente:

 public class TupleList : List> where T1 : IComparable { public void Add(T1 item, T2 item2) { Add(new Tuple(item, item2)); } public new void Sort() { Comparison> c = (a, b) => a.Item1.CompareTo(b.Item1); base.Sort(c); } } 

Mi caso de prueba:

 [TestMethod()] public void SortTest() { TupleList list = new TupleList(); list.Add(1, "cat"); list.Add(1, "car"); list.Add(2, "dog"); list.Add(2, "door"); list.Add(3, "elephant"); list.Add(1, "coconut"); list.Add(1, "cab"); list.Sort(); foreach(Tuple tuple in list) { Console.WriteLine(string.Format("{0}:{1}", tuple.Item1,tuple.Item2)); } int expected_first = 1; int expected_last = 3; int first = list.First().Item1; //requires using System.Linq int last = list.Last().Item1; //requires using System.Linq Assert.AreEqual(expected_first, first); Assert.AreEqual(expected_last, last); } 

La salida:

 1:cab 1:coconut 1:car 1:cat 2:door 2:dog 3:elephant 

El problema es que el diseño de la estructura de datos no coincide con los requisitos: es necesario almacenar varios encabezados para los mismos XPos. Por lo tanto, SortedList no debe tener un valor de Header , sino un valor de List

. Es un cambio simple y pequeño, pero resuelve todos los problemas y evita la creación de nuevos problemas como otras soluciones sugeridas (ver explicación a continuación):

 using System; using System.Collections.Generic; namespace TrySortedList { class Program { class Header { public int XPos; public string Name; } static void Main(string[] args) { SortedList> sortedHeaders = new SortedList
>(); 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
> sortedHeaders, int xPos, string name) { List
headers; if (!sortedHeaders.TryGetValue(xPos, out headers)){ headers = new List
(); sortedHeaders[xPos] = headers; } headers.Add(new Header { XPos = xPos, Name = name }); } } } Output: 1: Header_1 1: Header_2 2: Header_3

Tenga en cuenta que agregar una clave “divertida”, como agregar un número aleatorio o pretender que 2 XPos con el mismo valor son diferentes, puede conducir a muchos otros problemas. Por ejemplo, resulta difícil o incluso imposible eliminar un Encabezado en particular.

También tenga en cuenta que el rendimiento de ordenamiento es mucho mejor si solo se deben clasificar List

que cada Header . Ejemplo: si hay 100 XPos y cada uno tiene 100 encabezados, es necesario ordenar 10000 Header en lugar de 100 List

.

Por supuesto, también esta solución tiene una desventaja: si hay muchos XPos con solo 1 encabezado, se deben crear tantas listas, lo cual es un gasto adicional.

Muchas gracias por tu ayuda. Mientras buscaba más, encontré esta solución. (Disponible en Stackoverflow.com en otra pregunta)

Primero, creé una clase que encapsularía mis objetos para clases (encabezados, pie de página, etc.)

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

Así que se supone que esta clase debe contener los objetos, y PosX de cada objeto va como int Posición

 List Sequence= new List(); 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)); 

Lo que eventualmente obtengo es la lista ordenada de “Secuencia”.

Esta clase de colección mantendrá duplicados e insertará el orden de clasificación para el duplicado. El truco consiste en etiquetar los elementos con un valor único a medida que se insertan para mantener un orden de clasificación estable. Luego lo envolvemos todo en una interfaz ICollection.

 public class SuperSortedSet : ICollection { private readonly SortedSet> _Container; private int _Index = 0; private IComparer _Comparer; public SuperSortedSet(IComparer comparer) { _Comparer = comparer; var c2 = new System.Linq.Comparer>((p0, p1) => { var r = _Comparer.Compare(p0.Value, p1.Value); if (r == 0) { if (p0.Index == -1 || p1.Index == -1) return 0; return p0.Index.CompareTo(p1.Index); } else return r; }); _Container = new SortedSet>(c2); } public IEnumerator GetEnumerator() { return _Container.Select(p => p.Value).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Add(TValue item) { _Container.Add(Indexed.Create(_Index++, item)); } public void Clear() { _Container.Clear();} public bool Contains(TValue item) { return _Container.Contains(Indexed.Create(-1,item)); } public void CopyTo(TValue[] array, int arrayIndex) { foreach (var value in this) { if (arrayIndex >= array.Length) { throw new ArgumentException("Not enough space in array"); } array[arrayIndex] = value; arrayIndex++; } } public bool Remove(TValue item) { return _Container.Remove(Indexed.Create(-1, item)); } public int Count { get { return _Container.Count; } } public bool IsReadOnly { get { return false; } } } 

una clase de prueba

 [Fact] public void ShouldWorkWithSuperSortedSet() { // Sort points according to X var set = new SuperSortedSet (new System.Linq.Comparer((p0, p1) => p0.X.CompareTo(p1.X))); set.Add(new Point2D(9,10)); set.Add(new Point2D(1,25)); set.Add(new Point2D(11,-10)); set.Add(new Point2D(2,99)); set.Add(new Point2D(5,55)); set.Add(new Point2D(5,23)); set.Add(new Point2D(11,11)); set.Add(new Point2D(21,12)); set.Add(new Point2D(-1,76)); set.Add(new Point2D(16,21)); var xs = set.Select(p=>pX).ToList(); xs.Should().BeInAscendingOrder(); xs.Count.Should() .Be(10); xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,5,9,11,11,16,21}); set.Remove(new Point2D(5,55)); xs = set.Select(p=>pX).ToList(); xs.Count.Should() .Be(9); xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,9,11,11,16,21}); set.Remove(new Point2D(5,23)); xs = set.Select(p=>pX).ToList(); xs.Count.Should() .Be(8); xs.ShouldBeEquivalentTo(new[]{-1,1,2,9,11,11,16,21}); set.Contains(new Point2D(11, 11)) .Should() .BeTrue(); set.Contains(new Point2D(-1, 76)) .Should().BeTrue(); // Note that the custom compartor function ignores the Y value set.Contains(new Point2D(-1, 66)) .Should().BeTrue(); set.Contains(new Point2D(27, 66)) .Should().BeFalse(); } 

La estructura de etiquetado

 public struct Indexed { public int Index { get; private set; } public T Value { get; private set; } public Indexed(int index, T value) : this() { Index = index; Value = value; } public override string ToString() { return "(Indexed: " + Index + ", " + Value.ToString () + " )"; } } public class Indexed { public static Indexed Create(int indexed, T value) { return new Indexed(indexed, value); } } 

El ayudante comparador lambda

 public class Comparer : IComparer { private readonly Func _comparer; public Comparer(Func comparer) { if (comparer == null) throw new ArgumentNullException("comparer"); _comparer = comparer; } public int Compare(T x, T y) { return _comparer(x, y); } } 

¿ Lookup que permitirá duplicar claves http://msdn.microsoft.com/en-us/library/bb460184.aspx

El problema es que utiliza algo como clave que no es una tecla (porque ocurre varias veces).

Entonces, si tienes coordenadas reales, tal vez deberías tomar el Point como la clave para tu SortedList.

O puede crear una List>

donde su primer índice de lista define la posición xy la lista interna indexan la posición y (o viceversa si lo desea).

Crea una clase y consulta la lista:

 Public Class SortingAlgorithm { public int ID {get; set;} public string name {get; set;} public string address1 {get; set;} public string city {get; set;} public string state {get; set;} public int age {get; set;} } //declare a sorting algorithm list List sortAlg = new List(); //Add multiple values to the list sortAlg.Add( new SortingAlgorithm() {ID = ID, name = name, address1 = address1, city = city, state = state, age = age}); sortAlg.Add( new SortingAlgorithm() {ID = ID, name = name, address1 = address1, city = city, state = state, age = age}); sortAlg.Add( new SortingAlgorithm() {ID = ID, name = name, address1 = address1, city = city, state = state, age = age}); //query and order by the list var sortedlist = (from s in sortAlg select new { s.ID, s.name, s.address1, s.city, s.state, s.age }) .OrderBy(r => r.ID) .ThenBy(r=> r.name) .ThenBy(r=> r.city) .ThenBy(r=>r.state) .ThenBy(r=>r.age); 

Linq.Lookup es genial y todo, pero si tu objective es simplemente pasar por encima de las “teclas” mientras les permite duplicarse, puedes usar esta estructura:

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

Entonces puedes escribir:

 foreach (KeyValuePair item in FieldPatterns) { //use item.Key and item.Value } 

HTH

La clave (juego de palabras intencionado) para esto es crear una clase basada en IComparable que mantenga igualdad y hashing, pero nunca se compara con 0 si no es igual. Esto se puede hacer y se puede crear con un par de bonificaciones: la clasificación estable (es decir, los valores añadidos a la lista ordenada primero mantendrán su posición), y ToString() simplemente puede devolver el valor real de la cadena clave.

Aquí hay una clave de estructura que debería hacer el truco:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace System { ///  /// Defined in Totlsoft.Util. /// A key that will always be unique but compares /// primarily on the Key property, which is not required /// to be unique. ///  public struct StableKey : IComparable, IComparable { private static long s_Next; private long m_Sequence; private IComparable m_Key; ///  /// Defined in Totlsoft.Util. /// Constructs a StableKey with the given IComparable key. ///  ///  public StableKey( IComparable key ) { if( null == key ) throw new ArgumentNullException( "key" ); m_Sequence = Interlocked.Increment( ref s_Next ); m_Key = key; } ///  /// Overridden. True only if internal sequence and the /// Key are equal. ///  ///  ///  public override bool Equals( object obj ) { if( !( obj is StableKey ) ) return false; var dk = (StableKey)obj; return m_Sequence.Equals( dk.m_Sequence ) && Key.Equals( dk.Key ); } ///  /// Overridden. Gets the hash code of the internal /// sequence and the Key. ///  ///  public override int GetHashCode() { return m_Sequence.GetHashCode() ^ Key.GetHashCode(); } ///  /// Overridden. Returns Key.ToString(). ///  ///  public override string ToString() { return Key.ToString(); } ///  /// The key that will be compared on. ///  public IComparable Key { get { if( null == m_Key ) return 0; return m_Key; } } #region IComparable Members ///  /// Compares this Key property to another. If they /// are the same, compares the incremented value. ///  ///  ///  public int CompareTo( StableKey other ) { var cmp = Key.CompareTo( other.Key ); if( cmp == 0 ) cmp = m_Sequence.CompareTo( other.m_Sequence ); return cmp; } #endregion #region IComparable Members int IComparable.CompareTo( object obj ) { return CompareTo( (StableKey)obj ); } #endregion } } 

Puede usar SortedList, usar su valor para TKey e int (count) para TValue.

Aquí hay una muestra: una función que ordena las letras de una palabra.

  private string sortLetters(string word) { var input = new System.Collections.Generic.SortedList(); 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(); } 

El truco es boost tu objeto con una clave única. Ver la siguiente prueba que pasa. Quiero mantener mis puntos ordenados por su valor de X. Solo usar un Point2D desnudo en mi función de comparación causará que los puntos con el mismo valor de X sean eliminados. Así que envuelvo el Point2D en una clase de etiquetado llamada indexada.

 [Fact] public void ShouldBeAbleToUseCustomComparatorWithSortedSet() { // Create comparer that compares on X value but when X // X values are uses the index var comparer = new System.Linq.Comparer>(( p0, p1 ) => { var r = p0.Value.X.CompareTo(p1.Value.X); return r == 0 ? p0.Index.CompareTo(p1.Index) : r; }); // Sort points according to X var set = new SortedSet>(comparer); int i=0; // Create a helper function to wrap each point in a unique index Action index = p => { var ip = Indexed.Create(i++, p); set.Add(ip); }; index(new Point2D(9,10)); index(new Point2D(1,25)); index(new Point2D(11,-10)); index(new Point2D(2,99)); index(new Point2D(5,55)); index(new Point2D(5,23)); index(new Point2D(11,11)); index(new Point2D(21,12)); index(new Point2D(-1,76)); index(new Point2D(16,21)); set.Count.Should() .Be(10); var xs = set.Select(p=>p.Value.X).ToList(); xs.Should() .BeInAscendingOrder(); xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,5,9,11,11,16,21}); } 

Las utilidades para hacer este trabajo son

Un comparer que toma una lambda

 public class Comparer : IComparer { private readonly Func _comparer; public Comparer(Func comparer) { if (comparer == null) throw new ArgumentNullException("comparer"); _comparer = comparer; } public int Compare(T x, T y) { return _comparer(x, y); } } 

Una estructura de etiquetado

 public struct Indexed { public int Index { get; private set; } public T Value { get; private set; } public Indexed(int index, T value) : this() { Index = index; Value = value; } public override string ToString() { return "(Indexed: " + Index + ", " + Value.ToString () + " )"; } } public class Indexed { public static Indexed Create(int indexed, T value) { return new Indexed(indexed, value); } }