C # Sortable collection ซึ่งอนุญาตให้มีคีย์ซ้ำกัน


97

ฉันกำลังเขียนโปรแกรมเพื่อกำหนดลำดับที่วัตถุต่างๆจะปรากฏในรายงาน ลำดับคือตำแหน่ง Y (เซลล์) บนสเปรดชีต Excel

ส่วนสาธิตของโค้ดอยู่ด้านล่าง สิ่งที่ฉันต้องการทำให้สำเร็จคือการมีคอลเลกชันซึ่งจะทำให้ฉันสามารถเพิ่มวัตถุได้หลายชิ้นและฉันจะได้รับคอลเลกชันที่เรียงตามลำดับ

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>>รายการที่ซ้ำกันและพยายามแล้ว

ขอบคุณ.


1
คอลเลกชันจำเป็นต้องรองรับการแทรก / การลบหลังจากได้รับรายชื่อสมาชิกเริ่มต้นหรือไม่?
Ani

2
อะไรไม่ได้ผลเมื่อคุณลองList?
diceguyd30

ฉันไม่ต้องการเพียงแค่จัดเรียงและรับวัตถุ แต่ฉันต้องการรับรายการที่จัดเรียงทั้งหมด ดังนั้นในตัวอย่างด้านล่างทั้งวัตถุส่วนหัวควรมีอยู่และอยู่ในลำดับที่หนึ่งด้านล่างอื่น ๆ ถ้าฉันเพิ่มวัตถุส่วนหัวอื่นด้วย XPos = 2 ฉันควรมี 3 วัตถุในรายการ 2 วัตถุที่มี XPos = 1 และที่สามเป็น XPos = 2
Mayur Kotlikar

หมายเหตุ: เมื่อฉันพบสถานการณ์ประเภทนี้ฉันพบว่ารายการทั่วไปร่วมกับพฤติกรรม BinarySearch ที่ไม่ค่อยมีใครรู้จักสำหรับรายการที่ไม่พบนั้นทำงานได้อย่างมหัศจรรย์
J Trana

คำตอบ:


81

ใช้ IComparer ของคุณเอง!

เช่นเดียวกับที่ระบุไว้ในคำตอบอื่น ๆ คุณควรใช้คลาสตัวเปรียบเทียบของคุณเอง ด้วยเหตุนี้ฉันจึงใช้คลาส IComparer ทั่วไปซึ่งทำงานกับทุกสิ่งที่ใช้ 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
}

คุณจะใช้เมื่อสร้าง SortedList ใหม่ SortedDictionary ฯลฯ :

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

นี่คือกุญแจสำคัญที่สามารถทำซ้ำได้


43
แต่คุณจะไม่สามารถลบคีย์ใด ๆ ออกจากคีย์ได้
Shashwat

11
ใช่แล้วชัชวัฒน์! คุณไม่สามารถใช้ Remove (คีย์) หรือ IndexOfKey (คีย์) ได้เนื่องจากตัวเปรียบเทียบไม่เคยส่งกลับ 0 เพื่อส่งสัญญาณความเท่าเทียมกัน แต่คุณอาจ RemoveAt (ดัชนี) เพื่อลบรายการหากคุณมีดัชนี
Knasterbax

1
SortedDictionaryฉันยังพบปัญหาเดียวกันผมใช้ ช่วยให้สามารถกำจัดได้เช่นกัน
Shashwat

10
โปรดทราบว่าคุณกำลังทำลายการสะท้อนกลับของตัวเปรียบเทียบด้วยวิธีนี้ มันสามารถ (และจะ) ทำลายสิ่งต่างๆใน BCL
ghord

2
สิ่งนี้ควรกลับมา -1 เพื่อรักษาคำสั่งซื้อ
M.kazem Akhgary

16

คุณสามารถใช้รายการ <> ได้อย่างปลอดภัย รายการมีวิธีการเรียงลำดับซึ่งเกินพิกัดซึ่งยอมรับ IComparer คุณสามารถสร้างคลาสตัวเรียงลำดับของคุณเองเป็น. นี่คือตัวอย่าง:

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);
    }
}

1
ฉันไม่ต้องการเพียงแค่จัดเรียงและรับวัตถุ แต่ฉันต้องการรับรายการที่จัดเรียงทั้งหมด ดังนั้นในตัวอย่างด้านล่างทั้งวัตถุส่วนหัวควรมีอยู่และอยู่ในลำดับที่หนึ่งด้านล่างอื่น ๆ ถ้าฉันเพิ่มวัตถุส่วนหัวอื่นด้วย XPos = 2 ฉันควรมี 3 วัตถุในรายการ 2 วัตถุที่มี XPos = 1 และที่สามเป็น XPos = 2
Mayur Kotlikar

1
ถูกต้องคุณหมายถึงว่าเมื่อมีการแทรกองค์ประกอบในรายการควรแทรกในตำแหน่งที่ถูกต้องตามการจัดเรียง กรุณาแก้ไขฉันถ้าผิด ให้ฉันดูจะกลับมาในช่วงเวลาสั้น ๆ
Dipti Mehta

โปรดทราบว่า List <T> .ort ใช้อัลกอริทึมการจัดเรียงหลายแบบขึ้นอยู่กับขนาดคอลเลกชันและไม่ใช่ทั้งหมดที่เป็นประเภทที่เสถียร ดังนั้นออบเจ็กต์ที่เพิ่มในคอลเล็กชันที่เปรียบเทียบกับเทียบเท่าอาจไม่ปรากฏตามลำดับที่เพิ่ม
เสียงเงียบ

ฉันใช้ตัวเลือกนี้ดังนั้นฉันจึงสามารถหยุดสร้าง KeyValuePairs ในปริมาณที่ไม่เหมาะสมด้วยการใช้ฟังก์ชันลดลงในพจนานุกรม
Chris Marisic

10

ฉันใช้สิ่งต่อไปนี้:

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

Tuple ไม่มีให้ใช้งานใน. NET ทุกรุ่น แต่สามารถใช้แทน KeyValuePair <K, V>
Reuben

7

ปัญหาคือการออกแบบโครงสร้างข้อมูลไม่ตรงกับความต้องการ: จำเป็นต้องจัดเก็บส่วนหัวหลาย ๆ ส่วนสำหรับ 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

โปรดทราบว่าการเพิ่มคีย์ "ตลก" เช่นการเพิ่มตัวเลขสุ่มหรือการแสร้งทำเป็นว่า XPos 2 ตัวที่มีค่าเดียวกันจะนำไปสู่ปัญหาอื่น ๆ อีกมากมาย ตัวอย่างเช่นมันกลายเป็นเรื่องยากหรือเป็นไปไม่ได้เลยที่จะลบส่วนหัวเฉพาะ

นอกจากนี้ทราบว่าผลการดำเนินงานการจัดเรียงจะดีกว่ามากถ้าเพียงไม่กี่จะต้องมีการเรียงกว่าทุกList<Header> Headerตัวอย่าง: หากมี 100 XPos และแต่ละคนมี 100 หัว 10000 Headerจำเป็นต้องเรียงลำดับเมื่อเทียบกับ List<Header>100

แน่นอนว่าโซลูชันนี้ก็มีข้อเสียเช่นกัน: หากมี XPos จำนวนมากที่มีเพียง 1 Header เท่านั้นเนื่องจากต้องสร้าง Lists จำนวนมากซึ่งเป็นค่าใช้จ่ายบางส่วน


นี่เป็นวิธีแก้ปัญหาที่ตรงไปตรงมาที่สุด ตรวจสอบ SortedDictionary มันคล้ายกันเร็วกว่าในบางกรณี
Hogan

นี่เป็นทางออกที่ดีจริงๆ หนึ่งสามารถรวมฟังก์ชันนั้นไว้ในวัตถุคอลเลกชันที่กำหนดเองได้อย่างง่ายดายและจะมีประโยชน์มาก คิดดีขอบคุณสำหรับการแบ่งปันปีเตอร์!
Adam P

5

วิธีแก้ปัญหาที่ง่ายที่สุด (เทียบกับทั้งหมดข้างต้น): ใช้SortedSet<T>มันยอมรับIComparer<SortableKey>คลาสจากนั้นใช้วิธีการเปรียบเทียบด้วยวิธีนี้:

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();

}

4
ฉันใช้ SortedSet <T> และ T มีรหัส int ที่เพิ่มขึ้นของตัวเองซึ่งเพิ่มขึ้นในแต่ละอินสแตนซ์เพื่อให้แน่ใจว่าแต่ละ T ไม่ซ้ำกันแม้ว่าฟิลด์อื่นจะเหมือนกันก็ตาม
Skychan

3
GetHashCode สำหรับการเปรียบเทียบนั้นอันตราย อาจนำไปสู่การทำซ้ำเท็จโดยไม่คาดคิด อาจใช้งานได้เกือบตลอดเวลา แต่ฉันจะไม่ใช้สิ่งนี้เพื่ออะไรที่ร้ายแรง
Hogan

4

ขอบคุณมาก ๆ สำหรับความช่วยเหลือของคุณ. ในขณะที่ค้นหาเพิ่มเติมฉันพบวิธีนี้ (มีให้ใน Stackoverflow.com ในคำถามอื่น ๆ )

ขั้นแรกฉันสร้างคลาสซึ่งจะห่อหุ้มวัตถุของฉันสำหรับคลาส (ส่วนหัวส่วนท้าย ฯลฯ )

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

ดังนั้นคลาสนี้ควรจะยึดวัตถุไว้และ PosX ของแต่ละวัตถุจะอยู่ในตำแหน่ง int

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));

ในที่สุดสิ่งที่ฉันได้รับคือรายการ "ลำดับ" ที่เรียงลำดับ


2

คุณพยายามLookup<TKey, TElement>ที่จะอนุญาตให้มีคีย์ซ้ำ http://msdn.microsoft.com/en-us/library/bb460184.aspx


ขอบคุณ. ปัญหาของฉันคือวัตถุจะไม่เป็นเพียงประเภทเดียว (ไม่ใช่แค่ส่วนหัว) ซึ่งอาจแตกต่างกันไป (สมมติว่า Footer, Sidebar ฯลฯ ) แต่แต่ละอันจะมี XPos
Mayur Kotlikar

นอกจากนี้Lookupฉันเชื่อว่าไม่มีผู้สร้างสาธารณะ วิธีที่ดีเกี่ยวกับเรื่องนี้?
Jeff B

1
@JeffBridgman คุณจะต้องพึ่งพา Linq คุณสามารถทำที่ใด ๆToLookup IEnumerable<T>
nawfal

8
ใช่อนุญาตให้ทำคีย์ซ้ำได้ แต่ไม่ได้จัดเรียงอะไรไว้!
Roman Starkov

2

คุณสามารถใช้ SortedList ใช้ค่าของคุณสำหรับ TKey และ int (count) สำหรับ TValue

นี่คือตัวอย่าง: ฟังก์ชันที่จัดเรียงตัวอักษรของคำ

    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();

    }

2

คลาสคอลเลกชันนี้จะรักษารายการที่ซ้ำกันและแทรกลำดับการจัดเรียงสำหรับรายการที่ซ้ำกัน เคล็ดลับคือการแท็กรายการที่มีค่าเฉพาะเมื่อมีการแทรกเพื่อรักษาลำดับการจัดเรียงให้คงที่ จากนั้นเรารวมทั้งหมดไว้ในอินเทอร์เฟซ 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);
    }
}

1

ปัญหาคือคุณใช้บางอย่างเป็นคีย์ที่ไม่ใช่คีย์ (ทำให้เกิดขึ้นหลายครั้ง)

ดังนั้นหากคุณมีพิกัดจริงคุณควรใช้Pointเป็นกุญแจสำคัญสำหรับ SortedList ของคุณ

หรือคุณสร้างList<List<Header>>ตำแหน่งที่ดัชนีรายการแรกของคุณกำหนดตำแหน่ง x และรายการด้านในทำดัชนีตำแหน่ง y (หรือในทางกลับกันถ้าคุณต้องการ)


คีย์อาจมีหลายอินสแตนซ์ได้ตราบเท่าที่ไม่ใช่คีย์หลัก อย่างน้อยนั่นคือสิ่งที่พวกเขาบอกฉันในชั้นฐานข้อมูลที่ฉันรับ
ควบรวม

1
คำตอบนี้สั้นไปหน่อย แต่อธิบายปัญหาได้อย่างถูกต้องและให้วิธีแก้ปัญหาที่ถูกต้องเช่นการใช้ SortedList <int, List <Header>> ซึ่งจะรักษาการจัดเรียงส่วนหัวและสามารถจัดเก็บส่วนหัวจำนวนมากไว้ที่ xPos เดียวกันได้ สำหรับตัวอย่างโค้ดมองหาคำตอบของฉัน ฉันตอบคำตอบนี้หนึ่งข้อเนื่องจากมันชี้ไปในทิศทางที่ถูกต้อง กรุณาบวก 1 คำตอบของฉันด้วยหากคุณรู้สึกว่ามีประโยชน์
Peter Huber

1

กุญแจสำคัญ (เล่นสำนวนตั้งใจ) สำหรับสิ่งนี้คือการสร้าง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
    }
}

นั่นเป็นความคิดที่ดี ฉันรวมแนวคิดไว้ใน ICollection ที่กำหนดเอง ดูstackoverflow.com/a/21625939/158285
bradgonesurfing

1

นี่คือวิธีที่ฉันแก้ไขปัญหา มันหมายถึงความปลอดภัยของเธรดแม้ว่าคุณจะสามารถลบlocks ได้ถ้าคุณไม่ต้องการสิ่งนั้น นอกจากนี้โปรดทราบว่าโดยพลการInsertที่ดัชนีไม่ได้รับการสนับสนุนเนื่องจากอาจละเมิดเงื่อนไขการจัดเรียง

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

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


0

เคล็ดลับคือการเพิ่มวัตถุของคุณด้วยคีย์เฉพาะ ดูการทดสอบต่อไปนี้ที่ผ่าน ฉันต้องการให้คะแนนของฉันเรียงตามค่า X เพียงใช้ Point2D เปล่าในฟังก์ชันการเปรียบเทียบของฉันจะทำให้คะแนนที่มีค่า X เท่ากันถูกกำจัด ดังนั้นฉันจึงห่อ Point2D ในคลาสการแท็กที่เรียกว่า Indexed

[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);
    }
}

ดูคำตอบอื่น ๆ ของฉันเพื่อสรุปแนวคิดข้างต้นทั้งหมดในคลาส ICollection ที่กำหนดเอง
bradgonesurfing

-1

สร้างชั้นเรียนและสอบถามรายการ:

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);

-1

นี่คือสิ่งที่ฉันทำ โปรดทราบว่านี่อาจเป็นมังกร C # ยังค่อนข้างใหม่สำหรับฉัน

  • อนุญาตให้ใช้คีย์ที่ซ้ำกันค่าจะถูกเก็บไว้ในรายการ
  • ฉันใช้มันเป็นคิวที่เรียงลำดับดังนั้นชื่อและวิธีการ

การใช้งาน:

SortedQueue<MyClass> queue = new SortedQueue<MyClass>();
// new list on key "0" is created and item added
queue.Enqueue(0, first);
// new list on key "1" is created and item added
queue.Enqueue(1, second);
// items is added into list on key "0"
queue.Enqueue(0, third);
// takes the first item from list with smallest key
MyClass myClass = queue.Dequeue();
class SortedQueue<T> {
  public int Count;
  public SortedList<int, List<T>> Queue;

  public SortedQueue() {
    Count = 0;
    Queue = new SortedList<int, List<T>>();
  }

  public void Enqueue(int key, T value) {
    List<T> values;
    if (!Queue.TryGetValue(key, out values)){
      values = new List<T>();
      Queue.Add(key, values);
      Count += 1;
    }
    values.Add(value);
  }

  public T Dequeue() {
    if (Queue.Count > 0) {
      List<T> smallest = Queue.Values[0];
      if (smallest.Count > 0) {
        T item = smallest[0];
        smallest.Remove(item);
        return item;
      } else {
        Queue.RemoveAt(0);
        Count -= 1;
        return Dequeue();
      }
    }
    return default(T);
  }
}

มีคลาสอยู่แล้วQueueใน BCL ซึ่งแสดงถึงคอลเล็กชันของไอเท็มแบบเข้าก่อนออกก่อน ความหมายของชั้นเรียนของคุณแตกต่างกัน ชั้นเรียนของคุณมีจุดเริ่มต้น (ซึ่งรายการจะถูกยกเลิกการจัดคิว) แต่ไม่มีจุดสิ้นสุด (สามารถแทรกรายการได้ทุกที่) ดังนั้นEnqueueวิธีการในชั้นเรียนของคุณจึงไม่มีความหมาย IMHO
Theodor Zoulias

@TheodorZoulias ใช่การตั้งชื่อค่อนข้างแย่ที่นี่ แต่ฉันแทบจะไม่คิดว่ามันสมควรได้รับการโหวตลดลงมันมีสิ่งที่ OP ต้องการและเป็นเพียงเรื่องของการเปลี่ยนชื่อและนำวิธีการป้อนข้อมูล / เอาต์พุตมาใช้ใหม่ ทำไมถึงเรียกแบบนี้? ฉันต้องการโครงสร้างที่ฉันสามารถว่างได้ตั้งแต่เริ่มต้นในขณะที่วนซ้ำและเพิ่มรายการใหม่ตามค่าลำดับความสำคัญ ดังนั้นPriorityQueueจะเป็นชื่อที่เหมาะสมกว่า
Solo

OP ต้องการคอลเลกชันที่เรียงลำดับได้ซึ่งอนุญาตให้มีคีย์ที่ซ้ำกัน ชั้นเรียนของคุณไม่ใช่คอลเล็กชันเนื่องจากไม่สามารถแจกแจงได้ ฉันยังไม่ชอบการใช้ฟิลด์สาธารณะ อย่าลงคะแนนเป็นการส่วนตัว คุณสามารถซ่อมแซมความเสียหายด้านชื่อเสียงของ 5 downvote ได้ด้วยการเพิ่มคะแนนเพียงครั้งเดียว ( -2 * 5 == +10) ดังนั้นจึงไม่ใช่เรื่องใหญ่ :-)
Theodor Zoulias
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.