比较字典中的值

本文关键字:字典 比较 | 更新日期: 2023-09-27 18:25:20

我有一本字典,它有以下内容:

Dictionary<string, Dictionary<string, double>> 
<A, <2011, 100>>
    <2012, 125>>
    <2013, 142>>
<B, <2011, 350>>
    <2012, 340>>
    <2013, 400>>
<C, <2011, 75>>
    <2012, 80>>
    <2013, 102>>

我该如何循环浏览这本字典来计算年份或年份增长?其中增长计算为:

Growth = (current year count - (previous year count))/(previous year count)
       = (125 - 100)/100
       = 0.25

因此,新词典的结果是:

<A, <2011, ->>
    <2012, 0.25>>
    <2013, 0.14>>
<B, <2011, ->>
    <2012, -0.03>>
    <2013, 0.18>>
<C, <2011, ->>
    <2012, 0.07>>
    <2013, 0.28>>

现在,我对计算增长的兴趣不如学习如何循环使用这本字典来比较同一本字典中的值,然后将结果输出到一本新字典中。还有一大堆其他应用程序,这些应用程序会非常有用。我最初的想法是在for循环中做一个for循环,但后来我不知道如何回到前一年,因为键不是标准的0,1,2,3。我在C#MVC4中进行开发。

感谢大家的帮助。

比较字典中的值

首先,我很感兴趣的是为什么您希望/需要在Dictionary中创建这个结构。字典是非常具体的集合,设计用于特定的情况,我不确定你是否需要。所以我建议你考虑创建一个简单的类型列表:

new List<MyType>
{
    new MyType { Letter = "A", Year = 2011, Value = 100D },
    new MyType { Letter = "A", Year = 2012, Value = 125D }
};

如果你没有考虑到这一点,你可以创建一个匿名类型的列表来计算:

var objList = dic
    .SelectMany(s => s.Value.Select(t => new
    {
        Letter = s.Key,
        Year = t.Key,
        Value = t.Value
    }))
    .ToList();

然后你可以这样计算你想要什么:

var result = objList
    .Select(current =>
    {
        var last = objList
            .FirstOrDefault(f => f.Letter == current.Letter
                && f.Year == current.Year - 1);
        return new
        {
            Letter = current.Letter,
            Year = current.Year,
            Calculated = last != null && last.Value != 0
                ? (current.Value - last.Value) / last.Value
                : (double?)null
        };
    })
    .ToList();

如果你最终真的需要一本字典,你可以这样做:

var newDic = result
    .GroupBy(g => g.Letter)
    .ToDictionary(k => k.Key, v => v.ToDictionary(
        k2 => k2.Year, v2 => v2.Calculated));

如果只考虑表现和记忆,这不是更好的答案。但如果你想要一个更动态、更能自我解释的解决方案,这个答案就很好了。

不幸的是,除了将键复制到数组并在数组中反向索引之外,没有简单的方法可以在Dictionary(或SortedDictionary)中反向迭代。(Linq方法Enumerable.Reverse实际上就是这样做的。)

如果你需要在几年中向后和向前迭代,而不是使用Dictionary<string, Dictionary<string, double>>,我建议使用SortedList<TKey, TValue>作为你的内部集合:

Dictionary<string, SortedList<string, double>>     

或者更好的

Dictionary<string, SortedList<DateTime, double>>

这样做的好处是Keys属性实现了Ilist<TKey>,因此您可以执行IndexOf,它将是log-n。如果你需要进行邻近度查询(即找到最接近给定年份的年份),你可以滚动自己的二进制搜索:

    public static int BinarySearch<T>(this IList<T> list, T value, IComparer<T> comparer)
    {
        // Adapted from http://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs
        if (list == null)
            throw new ArgumentNullException("list");
        comparer = comparer ?? Comparer<T>.Default;
        int lo = 0;
        int hi = list.Count - 1;
        while (lo <= hi)
        {
            int i = lo + ((hi - lo) >> 1);
            int order = comparer.Compare(list[i], value);
            if (order == 0)
                return i;
            if (order < 0)
            {
                lo = i + 1;
            }
            else
            {
                hi = i - 1;
            }
        }
        return ~lo;
    }

Dictionary相比,SortedList的缺点是,如果无序插入大量项目,则插入时间为n平方。根据你的描述,我怀疑这对你来说不是问题。

更新

考虑到您每年的SortedList值,以下是您可能使用的一些扩展:

    /// <summary>
    /// Enumerate through all items in the list between first and last, inclusive.  First and last need not be in the list.
    /// </summary>
    public static IEnumerable<KeyValuePair<TKey, TValue>> Between<TKey, TValue>(this SortedList<TKey, TValue> list, TKey first, TKey last)
    {
        if (list == null)
            throw new ArgumentNullException();
        var comparer = list.Comparer;
        var index = list.Keys.BinarySearch(first, comparer);
        if (index < 0) // There can be no duplicated keys in SortedList.
            index = ~index;
        for (int count = list.Count; index < count; index++)
        {
            var key = list.Keys[index];
            if (comparer.Compare(key, last) > 0)
                break;
            yield return new KeyValuePair<TKey, TValue>(key, list.Values[index]);
        }
    }
    public static SortedList<TKey, double> ComputeGrowth<TKey>(this SortedList<TKey, double> list)
    {
        var count = list.Count;
        SortedList<TKey, double> newList = new SortedList<TKey, double>(count, list.Comparer);
        for (int i = 0; i < count; i++)
        {
            if (i == 0)
                newList.Add(list.Keys[i], 0.0); // Is this what you want?
            else
                newList.Add(list.Keys[i], (list.Values[i] - list.Values[i - 1]) / list.Values[i - 1]); // Any need to check for division by zero?
        }
        return newList;
    }