【问题标题】:Simple histogram generation of integer data in C#C#中整数数据的简单直方图生成
【发布时间】:2010-10-29 21:22:30
【问题描述】:

作为我正在构建的测试台的一部分,我正在寻找一个简单的类来计算整数值的直方图(算法解决问题所需的迭代次数)。答案应该是这样的:

Histogram my_hist = new Histogram();

for( uint i = 0; i < NUMBER_OF_RESULTS; i++ )
{

    myHist.AddValue( some_result );
}

for( uint j = 0; j < myHist.NumOfBins; j++ )
{
     Console.WriteLine( "{0} occurred {1} times", myHist.BinValues[j], myHist.BinCounts[j] );
}

我很惊讶谷歌搜索并没有找到一个很好的解决方案,但也许我没有找到正确的东西。有没有通用的解决方案,还是值得我自己推出?

【问题讨论】:

    标签: c# histogram


    【解决方案1】:

    你可以使用 SortedDictionary

    uint[] items = new uint[] {5, 6, 1, 2, 3, 1, 5, 2}; // sample data
    SortedDictionary<uint, int> histogram = new SortedDictionary<uint, int>();
    foreach (uint item in items) {
        if (histogram.ContainsKey(item)) {
            histogram[item]++;
        } else {
            histogram[item] = 1;
        }
    }
    foreach (KeyValuePair<uint, int> pair in histogram) {
        Console.WriteLine("{0} occurred {1} times", pair.Key, pair.Value);
    }
    

    不过,这会留下空的垃圾箱

    【讨论】:

    • +1:这看起来是一个好的开始。碰巧我只对包含数据的垃圾箱感兴趣:-)
    • SortedDictionary 逐一添加项目比使用普通字典并在末尾对KeyValuePair&lt;,&gt; 元素列表进行排序要慢得多。在 SortedDictionary 中插入和检索元素都是 O(log(N))。请参阅docs.microsoft.com/en-us/dotnet/api/… - 请参阅下面的答案以获取更快的代码示例。
    【解决方案2】:

    根据 BastardSaint 的建议,我想出了一个简洁且相当通用的包装器:

    public class Histogram<TVal> : SortedDictionary<TVal, uint>
    {
        public void IncrementCount(TVal binToIncrement)
        {
            if (ContainsKey(binToIncrement))
            {
                this[binToIncrement]++;
            }
            else
            {
                Add(binToIncrement, 1);
            }
        }
    }
    

    所以现在我可以这样做了:

    const uint numOfInputDataPoints = 5;
    Histogram<uint> hist = new Histogram<uint>();
    
    // Fill the histogram with data
    for (uint i = 0; i < numOfInputDataPoints; i++)
    {
        // Grab a result from my algorithm
        uint numOfIterationsForSolution = MyAlorithm.Run();
    
        // Add the number to the histogram
        hist.IncrementCount( numOfIterationsForSolution );
    }
    
    // Report the results
    foreach (KeyValuePair<uint, uint> histEntry in hist.AsEnumerable())
    {
        Console.WriteLine("{0} occurred {1} times", histEntry.Key, histEntry.Value);
    }
    

    我花了一段时间才弄清楚如何使它成为通用的(首先我只是覆盖了SortedDictionary 构造函数,这意味着你只能将它用于uint 键)。

    【讨论】:

    • BastardSaint 使用 Contains() 进行检查的方法比依赖异常更明智一些(很多)。每次存储新号码的频率时,这都会产生一个峰值。
    • 现在想一想,也许每次都做检查是检查存在的更好方法。我想这取决于您是否期望添加许多非常相似的项目(我就是),或者您是否期望具有更多独特整体的直方图。我的直觉是在我的情况下它会更快(?)
    • 将示例更改为使用 if-else 解决方案。
    • 你能想出一个好的方法来扩展这种方法来处理大于 1 的 bin 吗?
    • 您可以将键指定为 String^ 值,然后添加像“0-10”这样的键?
    【解决方案3】:

    你可以使用 Linq:

    var items = new[] {5, 6, 1, 2, 3, 1, 5, 2};
    items
        .GroupBy(i => i)
        .Select(g => new {
            Item = g.Key,
            Count = g.Count()
        })
        .OrderBy(g => g.Item)
        .ToList()
        .ForEach(g => {
            Console.WriteLine("{0} occurred {1} times", g.Item, g.Count);
        });
    

    【讨论】:

      【解决方案4】:

      我实现一个简单的扩展方法来创建直方图:

      public static IReadOnlyDictionary<T, int> ToHistogram<T>(this IEnumerable<T> enumerable)
         => enumerable.GroupBy(item => item).ToDictionary(grouping => grouping.Key, grouping => grouping.Count());
      

      【讨论】:

        【解决方案5】:

        这建立在公认的答案之上。问题是迭代地构建SortedDictionary 很慢,因为插入检索都花费O(log(N))

        如果您不需要在累积时显示直方图,则可以避免这种情况。

        我的修改使用普通的Dictionary,并且只在最后将其排序为SortedList

        对于 1000 万个项目的样本大小,这个版本大约快 11 倍(在我的机器上),代价是内存使用量稍高一些,直到 GC 启动(约 10% 额外内存)。

        //generate a random sample
        Random r = new Random();
        var items = Enumerable
            .Range(1, 10_000_000)
            .Select( _ => (uint)r.Next(100_000))
            .ToList();
        
        //build the histogram using a normal dictionary with O(1) lookups and insertions.
        var tempHistogram = new Dictionary<uint, int>();
        foreach (uint item in items)
        {
            if (tempHistogram.ContainsKey(item))
            {
                tempHistogram[item]++;
            }
            else
            {
                tempHistogram[item] = 1;
            }
        }
        
        //Sort it once. SortedList conveniently has a ctor that takes a dictionary.
        var sortedHistogram = new SortedList<uint, int>(tempHistogram);
        
        foreach (KeyValuePair<uint, int> pair in sortedHistogram.Take(100))
        {
            Console.WriteLine("{0} occurred {1} times", pair.Key, pair.Value);
        }
        

        对于非常大的样本(大于可用内存),有惊人的概率算法可以解决这个问题。
        它们也非常适合流式传输数据。 寻找“分位数草图”。这是 Apache 基金会的一个实现:https://datasketches.apache.org/

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-08-14
          • 2014-09-21
          • 2010-12-24
          • 2010-10-03
          • 2012-01-13
          • 1970-01-01
          • 2020-02-13
          相关资源
          最近更新 更多