字典可以用不同的键排序吗?

时间:2010-10-18 17:34:38

标签: c# design-patterns data-structures

我需要按键从数据结构中检索项目。但我还要求使用键以外的字段按排序顺序遍历该数据结构。

像这样的东西(伪代码,仅用于说明目的):

var list = new SortedDictionary<TKey, TSortField, TItem>();

我该怎么做?有没有办法使用单一数据结构,还是我需要自己滚动?

注意: TItem已经衍生自(和实施)IComparable<T>

4 个答案:

答案 0 :(得分:2)

如果您不打算修改排序结果,可以使用IEnumerable.OrderBy和ToDictionary的组合:

var sortedResult = sortedDictionary.OrderBy(kvp => kvp.Value.SortField)
                                   .ToDictionary(kvp => kvp.Key,
                                                 kvp => kvp.Value);

请记住,这实际上是在创建一个新的集合,而不是对原始集合进行排序(将在SortedDictionary中进行维护)。

答案 1 :(得分:2)

SortedDictionary类有一个Values property“获取包含SortedDictionary中值的集合”。该集合是IEnumerable,您可以对该值集合进行排序。

以下是C#中的一个示例程序(我作为示例快速编写了它。它可能会根据您的特定需求进行改进和/或更改。)

using System;
using System.Collections.Generic;
using System.Linq;


namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            SortedDictionary<string, int> sd = new SortedDictionary<string, int>();

            sd.Add("a", 10);
            sd.Add("c", 4);
            sd.Add("b", 20);

            Console.WriteLine("___KEYS____");
            foreach (string key in sd.Keys)
            {
                Console.WriteLine(key);
            }

            foreach(int sortedVal in sd.Values.OrderBy(value => value))
            {
                Console.WriteLine(sortedVal);
            }
        }
    }
}

答案 2 :(得分:0)

将项目添加到SortedList只会存储对内存中已有对象的引用,因此不会占用更多空间。

答案 3 :(得分:0)

您可以使用LINQ对字典的Values属性进行排序,但如果列表很大或排序功能很昂贵,这可能比您希望的要慢 - 特别是如果您经常访问它。

这是我对排序字典的快速而又脏的实现。它使用Dictionary进行直接访问,但也保留ListDictionary实例以便对键或值进行有序访问。

您可以对任何字段或表达式进行排序。但是,如果排序包括方法调用,则函数应该是确定性的(因此,给定一组值,它们总是以相同的方式排序)。另请注意,项目在插入时进行排序 - 如果事后更新了对象,则排序顺序不会自动更新。如果这是可能的话,最好使用LINQ按需排序。

public class ArbitrarySortedDictionary<TKEY, TSORT, TVALUE> : IDictionary<TKEY, TVALUE> 
    where TSORT : IComparable
    where TKEY : IComparable
{
    /// <summary>
    /// Key class for use in keeping the _SortedKeys and _SortedValues in proper order.  Since the sorting 
    /// function could easily result in same values across instances, we'll use the key as secondary sort
    /// field - since it's unique, everything should always have a consistent, unambiguous sort order.
    /// </summary>
    class SortedDictionaryKey
    {
        public SortedDictionaryKey(TSORT sortVal, TKEY key)
        {
            SortVal = sortVal;
            Key = key;
        }

        public TSORT SortVal
        {
            get;
            private set;
        }

        public TKEY Key
        {
            get;
            private set;
        }
    }

    readonly Func<TVALUE, TSORT> _SortFunc;
    readonly Dictionary<TKEY, TVALUE> _InternalDict = new Dictionary<TKEY, TVALUE>();
    readonly SortedList<SortedDictionaryKey, TKEY> _SortedKeys;
    readonly SortedList<SortedDictionaryKey, TVALUE> _SortedValues;


    public ArbitrarySortedDictionary(Func<TVALUE, TSORT> sortFunc)
    {
        _SortFunc = sortFunc;

        Func<SortedDictionaryKey, SortedDictionaryKey, Int32> compFunc = (x, y) => {
            Int32 sortValComp = 0;

            if (x.SortVal != null)
                sortValComp = x.SortVal.CompareTo(y.SortVal);

            if (sortValComp != 0)
                return sortValComp;

            if (x.Key != null)
                return x.Key.CompareTo(y.Key);

            return y.Key == null ? 0 : -1;
        };

        var comparer = new LambdaComparer<SortedDictionaryKey>(compFunc);

        _SortedKeys = new SortedList<SortedDictionaryKey, TKEY>(comparer);
        _SortedValues = new SortedList<SortedDictionaryKey, TVALUE>(comparer);
    }

    public void Add(TKEY key, TVALUE value)
    {
        if (key == null)
            throw new ArgumentException("Null key is not allowed.");

        var sortKey = new SortedDictionaryKey(_SortFunc(value), key);

        _InternalDict.Add(key, value);
        _SortedKeys.Add(sortKey, key);
        _SortedValues.Add(sortKey, value);
    }

    public bool ContainsKey(TKEY key)
    {
        return _InternalDict.ContainsKey(key);
    }

    public ICollection<TKEY> Keys
    {
        get {
            return _SortedKeys.Values.ToList<TKEY>();
        }
    }

    public bool Remove(TKEY key)
    {
        return RemoveInternal(key, _InternalDict[key]);
    }

    public bool TryGetValue(TKEY key, out TVALUE value)
    {
        return _InternalDict.TryGetValue(key, out value);
    }

    public ICollection<TVALUE> Values
    {
        get {
            return _SortedValues.Values.ToList<TVALUE>();
        }
    }

    public TVALUE this[Int32 index]
    {
        get {
            return _InternalDict[_SortedKeys.Values[index]];
        }
        set {
            throw new NotImplementedException("Can't update ArbitrarySortedDictionary directly.");
        }
    }

    public TVALUE this[TKEY key]
    {
        get {
            return _InternalDict[key];
        }
        set {
            if (!ContainsKey(key)) {
                Add(key, value);
                return;
            }

            throw new NotImplementedException("To update items currently, remove and re-add.");
        }
    }

    public void Add(KeyValuePair<TKEY, TVALUE> item)
    {
        var sortKey = new SortedDictionaryKey(_SortFunc(item.Value), item.Key);

        _InternalDict.Add(item.Key, item.Value);
        _SortedKeys.Add(sortKey, item.Key);
        _SortedValues.Add(sortKey, item.Value);
    }

    public void Clear()
    {
        _SortedKeys.Clear();
        _SortedValues.Clear();
        _InternalDict.Clear();
    }

    public bool Contains(KeyValuePair<TKEY, TVALUE> item)
    {
        var val = _InternalDict[item.Key];

        if (val == null)
            return item.Value == null;

        return val.Equals(item.Value);
    }

    public void CopyTo(KeyValuePair<TKEY, TVALUE>[] array, int arrayIndex)
    {
        Int32 curIndex = arrayIndex;

        foreach (var item in this)
            array[curIndex++] = item;
    }

    public int Count
    {
        get { return _InternalDict.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(KeyValuePair<TKEY, TVALUE> item)
    {
        return RemoveInternal(item.Key, item.Value);
    }

    public IEnumerator<KeyValuePair<TKEY, TVALUE>> GetEnumerator()
    {
        var res =
            from k in _SortedKeys
            join v in _SortedValues on k.Key equals v.Key
            orderby k.Key
            select new KeyValuePair<TKEY, TVALUE>(k.Value, v.Value);

        return res.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }


    private Boolean RemoveInternal(TKEY key, TVALUE val)
    {
        if (!_InternalDict.Remove(key))
            return false;

        var sortKey = new SortedDictionaryKey(_SortFunc(val), key);
        var retVal = _SortedKeys.Remove(sortKey);
        return retVal && _SortedValues.Remove(sortKey);
    }
}





/// <summary>
/// Utility class - an IComparer based upon a lambda expression.
/// </summary>
public class LambdaComparer<T> : IComparer<T>
{
    private readonly Func<T, int> _lambdaHash;
    private readonly Func<T, T, int> _lambdaComparer;

    public LambdaComparer(Func<T, T, int> lambdaComparer) :
        this(lambdaComparer, o => 0)
    {
    }

    public LambdaComparer(Func<T, T, int> lambdaComparer, Func<T, int> lambdaHash)
    {
        if (lambdaComparer == null)
            throw new ArgumentNullException("lambdaComparer");

        if (lambdaHash == null)
            throw new ArgumentNullException("lambdaHash");

        _lambdaHash = lambdaHash;
        _lambdaComparer = lambdaComparer;
    }

    public int Compare(T x, T y)
    {
        return _lambdaComparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _lambdaHash(obj);
    }
}