중복 키를 허용하는 C # 정렬 가능한 컬렉션
보고서에 다양한 개체가 나타나는 순서를 설정하는 프로그램을 작성 중입니다. 시퀀스는 Excel 스프레드 시트의 Y 위치 (셀)입니다.
코드의 데모 부분은 다음과 같습니다. 내가 달성하고 싶은 것은 컬렉션을 갖는 것입니다. 이렇게하면 여러 개체를 추가 할 수 있고 시퀀스에 따라 정렬 된 컬렉션을 얻을 수 있습니다.
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);
나는 SortedList가 이것을 허용하지 않을 것이라는 것을 알고 있으며 대안을 찾고 있습니다. 중복 을 제거 하고 싶지 않고 이미 시도했습니다 List<KeyValuePair<int, object>>
.
감사.
나만의 IComparer를 사용하세요!
다른 답변에서 이미 언급했듯이 자신의 비교 자 클래스를 사용해야합니다. 이를 위해 IComparable을 구현하는 모든 것과 함께 작동하는 일반 IComparer 클래스를 사용합니다.
/// <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
}
새 SortedList, SortedDictionary 등을 인스턴스화 할 때 사용합니다.
SortedList<int, MyValueClass> slist = new SortedList<int, MyValueClass>(new DuplicateKeyComparer<int>());
여기서 int는 중복 될 수있는 키입니다.
List <>를 안전하게 사용할 수 있습니다. List에는 IComparer를 허용하는 오버로드 인 Sort 메서드가 있습니다. 고유 한 분류기 클래스를. 예를 들면 다음과 같습니다.
private List<Curve> Curves;
this.Curves.Sort(new CurveSorter());
public class CurveSorter : IComparer<Curve>
{
public int Compare(Curve c1, Curve c2)
{
return c2.CreationTime.CompareTo(c1.CreationTime);
}
}
다음을 사용합니다.
public class TupleList<T1, T2> : List<Tuple<T1, T2>> where T1 : IComparable
{
public void Add(T1 item, T2 item2)
{
Add(new Tuple<T1, T2>(item, item2));
}
public new void Sort()
{
Comparison<Tuple<T1, T2>> c = (a, b) => a.Item1.CompareTo(b.Item1);
base.Sort(c);
}
}
내 테스트 케이스 :
[TestMethod()]
public void SortTest()
{
TupleList<int, string> list = new TupleList<int, string>();
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<int, string> 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);
}
출력 :
1:cab
1:coconut
1:car
1:cat
2:door
2:dog
3:elephant
가장 간단한 솔루션 (위의 모든 항목과 비교) :을 사용 SortedSet<T>
하고 IComparer<SortableKey>
클래스를 수락 한 다음 다음과 같이 Compare 메서드를 구현합니다.
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();
}
문제는 데이터 구조 설계가 요구 사항과 일치하지 않는다는 것입니다. 동일한 XPos에 대해 여러 헤더를 저장해야합니다. 따라서 SortedList<XPos, value>
의 값이 Header
아니라 값을 가져야합니다 List<Header>
. 간단하고 사소한 변경이지만 모든 문제를 해결하고 다른 제안 된 솔루션과 같은 새로운 문제를 생성하지 않습니다 (아래 설명 참조).
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
임의의 숫자를 추가하거나 동일한 값을 가진 2 개의 XPos가 다른 것처럼 가장하는 것과 같은 "재미있는"키를 추가하면 다른 많은 문제가 발생합니다. 예를 들어 특정 헤더를 제거하는 것이 어렵거나 불가능 해집니다.
또한 List<Header>
모든 .NET보다 몇 개만 정렬해야하는 경우 정렬 성능이 훨씬 더 좋습니다 Header
. 예 : XPos가 100 개이고 각 헤더에 100 개의 헤더가있는 경우 100이 아니라 10000 Header
을 정렬해야합니다 List<Header>
.
물론이 솔루션에도 단점이 있습니다. 헤더가 1 개 뿐인 XPos가 많으면 목록을 많이 만들어야하므로 약간의 오버 헤드가 발생합니다.
도와 주셔서 정말로 고맙습니다. 더 많이 검색하는 동안이 해결책을 찾았습니다. (다른 질문은 Stackoverflow.com에서 사용 가능)
먼저 클래스 (Headers, Footer 등)에 대한 개체를 캡슐화하는 클래스를 만들었습니다.
public class MyPosition
{
public int Position { get; set; }
public object MyObjects{ get; set; }
}
따라서이 클래스는 객체를 보유해야하며 각 객체의 PosX는 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));
결국 내가 얻는 것은 정렬 된 "시퀀스"목록입니다.
이 컬렉션 클래스는 중복을 유지하고 중복에 대한 정렬 순서를 삽입합니다. 트릭은 안정적인 정렬 순서를 유지하기 위해 삽입 될 때 고유 한 값으로 항목에 태그를 지정하는 것입니다. 그런 다음 모든 것을 ICollection 인터페이스로 래핑합니다.
public class SuperSortedSet<TValue> : ICollection<TValue>
{
private readonly SortedSet<Indexed<TValue>> _Container;
private int _Index = 0;
private IComparer<TValue> _Comparer;
public SuperSortedSet(IComparer<TValue> comparer)
{
_Comparer = comparer;
var c2 = new System.Linq.Comparer<Indexed<TValue>>((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<Indexed<TValue>>(c2);
}
public IEnumerator<TValue> 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; }
}
}
시험 수업
[Fact]
public void ShouldWorkWithSuperSortedSet()
{
// Sort points according to X
var set = new SuperSortedSet<Point2D>
(new System.Linq.Comparer<Point2D>((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=>p.X).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=>p.X).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=>p.X).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();
}
태깅 구조체
public struct Indexed<T>
{
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<T> Create<T>(int indexed, T value)
{
return new Indexed<T>(indexed, value);
}
}
람다 비교 자 도우미
public class Comparer<T> : IComparer<T>
{
private readonly Func<T, T, int> _comparer;
public Comparer(Func<T, T, int> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public int Compare(T x, T y)
{
return _comparer(x, y);
}
}
Lookup<TKey, TElement>
중복 키를 허용 하려고 시도 했습니까 http://msdn.microsoft.com/en-us/library/bb460184.aspx
이것의 핵심 (의도 된 말장난)은 IComparable
동등성과 해싱을 유지하지만 동일하지 않으면 0과 비교하지 않는 기반 클래스 를 만드는 것 입니다. 이 작업을 수행 할 수 있으며 몇 가지 보너스로 만들 수 있습니다. 안정적인 정렬 (즉, 정렬 된 목록에 먼저 추가 된 값이 위치를 유지함)하고 ToString()
단순히 실제 키 문자열 값을 반환 할 수 있습니다.
트릭을 수행해야하는 구조체 키는 다음과 같습니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace System
{
/// <summary>
/// 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.
/// </summary>
public struct StableKey : IComparable<StableKey>, IComparable
{
private static long s_Next;
private long m_Sequence;
private IComparable m_Key;
/// <summary>
/// Defined in Totlsoft.Util.
/// Constructs a StableKey with the given IComparable key.
/// </summary>
/// <param name="key"></param>
public StableKey( IComparable key )
{
if( null == key )
throw new ArgumentNullException( "key" );
m_Sequence = Interlocked.Increment( ref s_Next );
m_Key = key;
}
/// <summary>
/// Overridden. True only if internal sequence and the
/// Key are equal.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
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 );
}
/// <summary>
/// Overridden. Gets the hash code of the internal
/// sequence and the Key.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return m_Sequence.GetHashCode() ^ Key.GetHashCode();
}
/// <summary>
/// Overridden. Returns Key.ToString().
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Key.ToString();
}
/// <summary>
/// The key that will be compared on.
/// </summary>
public IComparable Key
{
get
{
if( null == m_Key )
return 0;
return m_Key;
}
}
#region IComparable<StableKey> Members
/// <summary>
/// Compares this Key property to another. If they
/// are the same, compares the incremented value.
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
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
}
}
문제는 키가 아닌 것을 키로 사용한다는 것입니다 (여러 번 발생하기 때문입니다).
따라서 실제 좌표가있는 경우 Point
SortedList의 키로 가져와야합니다.
또는 List<List<Header>>
첫 번째 목록 인덱스가 x 위치를 정의하고 내부 목록 인덱스가 y 위치를 정의 하는 위치 를 만듭니다 (또는 원하는 경우 그 반대도 마찬가지).
클래스를 만들고 목록을 쿼리합니다.
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<SortingAlgorithm> sortAlg = new List<SortingAlgorithm>();
//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 은 멋지지만 "키"를 복제하는 동안 단순히 반복하는 것이 목표라면 다음 구조를 사용할 수 있습니다.
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"),
};
그런 다음 다음과 같이 작성할 수 있습니다.
foreach (KeyValuePair<String,String> item in FieldPatterns)
{
//use item.Key and item.Value
}
HTH
SortedList를 사용하고 TKey에 값을 사용하고 TValue에 int (수)를 사용할 수 있습니다.
다음은 샘플입니다. 단어의 문자를 정렬하는 함수.
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();
}
트릭은 고유 키로 개체를 확장하는 것입니다. 통과하는 다음 테스트를 참조하십시오. 내 점수를 X 값으로 정렬하고 싶습니다. 내 비교 함수에서 벌거 벗은 Point2D를 사용하면 동일한 X 값을 가진 포인트가 제거됩니다. 그래서 Indexed라는 태그 지정 클래스에 Point2D를 래핑합니다.
[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<Indexed<Point2D>>(( 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<Indexed<Point2D>>(comparer);
int i=0;
// Create a helper function to wrap each point in a unique index
Action<Point2D> 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});
}
이 작업을 수행하는 유틸리티는
람다를 취하는 비교 자
public class Comparer<T> : IComparer<T>
{
private readonly Func<T, T, int> _comparer;
public Comparer(Func<T, T, int> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public int Compare(T x, T y)
{
return _comparer(x, y);
}
}
태그 지정 구조체
public struct Indexed<T>
{
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<T> Create<T>(int indexed, T value)
{
return new Indexed<T>(indexed, value);
}
}
참고URL : https://stackoverflow.com/questions/5716423/c-sharp-sortable-collection-which-allows-duplicate-keys
'your programing' 카테고리의 다른 글
그 전후에 어획물 배치 (0) | 2020.10.11 |
---|---|
Python의 중첩 함수 (0) | 2020.10.11 |
절대 위치 + 스크롤링 (0) | 2020.10.10 |
RecyclerView.ViewHolder-getLayoutPosition 대 getAdapterPosition (0) | 2020.10.10 |
`—` 또는`—` HTML 출력에 차이가 있습니까? (0) | 2020.10.10 |