我用C++写了一段代码,它首先对一个十进制值数组进行排序,然后询问用户他们想要直方图(稍后绘制)的分割数,使用它来计算类宽度,从而计算每个类中值的频率。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<float> lengths = {
2.1, 2.5, 1.8, 2.2, 2.9, 2.0, 1.5, 2.8, 2.3, 2.6,
3.1, 2.9, 2.7, 1.8, 2.2, 2.4, 1.9, 2.3, 2.0, 2.5};
vector<int> hist;
int divisions;
int main(void)
{
sort(lengths.begin(), lengths.end());
cin >> divisions;
float class_w = static_cast<float>(lengths[lengths.size() - 1]) / divisions;
float range_top = class_w;
int freq = 0;
for (float x : lengths)
{
while (x > range_top) //allows range_top to catch up without x moving on to next value
{
hist.push_back(freq);
freq = 0;
cout << range_top << " ";
range_top += class_w;
}
if (x <= range_top)
{
freq++;
}
}
hist.push_back(freq);
cout << endl;
for (int x : hist)
{
cout << x << " ";
}
cout << endl;
for (float y : lengths)
{
cout << y << " ";
}
}
字符串
当数组的最大/最终值等于range_top值时,问题出现。
下面是一个例子,当我把divisions作为10时:长度(排序):1.5 1.8 1.8 1.9 2 2.1 2.2 2.2 2.3 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.9 3.1 range_top:0.31 0.62 0.93 1.24 1.55 1.86 2.17 2.48 2.79 3.1历史:0 0 0 0 1 2 4 5 4 3 1
它应该是。历史:0 0 0 1 2 4 5 4
怎么解决?
2条答案
按热度按时间p8ekf7hl1#
正如你所说的,问题是最大值等于range_top值(甚至可能由于舍入问题而稍大)。一个基本的解决方案是有效地确定最后一个直方图值没有上限。一种方法是跟踪当前的除法值,并确保它永远不会大于最大值。我通过以下更改做到了这一点:
1.在
main
函数中,添加int currDivision = 1;
行。1.将
while (x > range_top)
更改为while (currDivision < division && x > range_top)
。1.在该循环的底部,在
range_top += class_w;
行之后,添加currDivision++;
行。1.由于最大值实际上可能比最后一个
range_top
值稍大,因此删除if (x <= range_top)
行。这甚至与您的初始代码是多余的,但我们现在不需要或不希望它确保每个x
值总是在freq
变量中计数,特别是在最后一组中。通过这些更改,以下是使用divisions值10的输出:
字符串
如您所见,直方图输出现在是正确的值集。
bnl4lu3b2#
当数组的最大/最终值等于
range_top
值时,就会出现问题。[这会导致向直方图添加额外的直方图桶,其中包含等于range_top
值的值。]您是浮点值不精确表示的受害者。
您的程序通过将先前的
range_top
值与class_w
相加来计算每个range_top
值。当您到达第10个直方图桶时,你已经做了9次求和。累积的回合-该计算的off错误导致range_top
的值比您期望的3.1
小一点点。它小于3.1
从向量lengths
,这导致创建新的直方图桶。我认为这与基数为10的值
0.1
在转换为基数为2时是一个无限重复的“十进制”有关。基数为2的值必须被截断以存储在类型float
中,这导致了上面描述的舍入错误。解决办法是什么?
解决方法很简单。填充每个直方图桶,除了最后一个。剩下的任何东西都放在最后一个直方图桶中。
你可以做的另一件事是使用类型
double
而不是类型float
执行浮点计算。类型float
适合6到7位小数的精度。类型double
给你15到16。两者都受到舍入错误的影响,但是类型float
更快地击中你。类型
float
可能是合适的--在计算完成后--当你需要存储一个大的结果向量时。它很少用于计算本身。是否使用从零开始的直方图范围?
程序中的直方图从0.0开始,一直延伸到向量
lengths
。因此,直方图中的前几个桶是空的,因为没有一个长度“接近”0.0。直方图桶的宽度(即
class_w
)通过首先对向量lengths
进行排序,然后将最大长度(即lengths[lengths.size() - 1]
)除以桶的数量(即divisions
)来找到。字符串
您可能希望直方图从向量
lengths
的最小值开始,这将改变class_w
的计算。型
下面的程序使用了一个标志,它允许您选择其中一种方法:
型
填充每个直方图桶,最后一个除外
存储桶的数量由变量
divisions
给出。在省略最后一个存储桶的循环中,迭代的总次数为divisions - 1
。我们将其保存在变量imax
中。型
这很好,但这个循环也跟踪了向量
lengths
的迭代器。如果我们到达了向量lengths
的末尾,我们就停止循环。型
剩下的很简单。内部循环扫描向量
lengths
,当它发现一个不属于当前直方图桶的值时停止。在循环内部(值属于当前直方图桶),它递增桶计数。hist
中。i
索引。*it < range_top[i]
。++hist[i];
型
所有剩余的内容都将放入最后一个直方图桶中
这里不需要循环。最后一个直方图桶的计数可以通过简单的迭代器减法来找到。这是因为迭代器
it
在前一个代码块完成时仍然是活动的。型
完成的函数
main
其中大部分在上面已经讨论过了。一个新的东西是向量
range_top
。它的元素通过重复将class_w
添加到初始值threshold
来初始化。当histogram_is_zero_based
时,变量threshold
从0.0
开始。否则,它从lengths.front()
开始,即向量lengths
中的最小值。型
输出
首先,
histogram_is_zero_based
时的输出。这与OP的预期输出相匹配。型
histogram_is_zero_based
为false时的输出。型
辅助函数:
get_int
get_int
是一个方便的函数,从键盘输入一个整数(即从std::cin
)。它捕获无效(非数字)条目,也捕获超出范围的条目。使用函数
get_int
可以很容易地输入直方图中使用的分割数。例如,下面的代码将值限制为1到20之间的整数。结果保存为类型std::size_t
。Helper函数:
put
和operator<<
函数
put
显示向量中的值。它有两个参数:ost
-对std::ostream
对象的引用。通常,ost
是std::cout
。v
-一个常量引用,指向一个包含T
类型对象的向量。因为
put
是作为函数模板编写的,所以它可以用来输出函数main
中使用的三个向量中的任何一个。给定函数
put
,编写一个调用它的流操作符是一件小事。型
现在,您可以像这样显示
lengths
、range_top
和hist
:型
旁注
专业程序员尽量避免使用“全局”变量。在一个大型程序中,很难跟踪全局变量在何时何地改变了它的值。这使得很难证明程序的正确性(在所有执行路径上)。更糟糕的是,它几乎不可能调试。
所以,我把所有的全局变量都移到了函数
main
中,它们在这里是“局部的”。考虑到大量的教科书都使用
using namespace std;
,您可能会惊讶地发现,专业程序员几乎从不在生产代码中使用using namespace std;
,您也不应该使用它。相反,专业人士每次需要从标准库引用名称时只需输入
std::
。有一些例外,主要围绕着所谓的 * 参数依赖查找 ,但这里没有足够的空间来介绍它们。( 提示:*std::swap
的重载经常使用ADL。)所以,我排除了
using namespace std;
。类型
float
容易出现过多的舍入错误。除非有压倒性的理由使用类型float
-而不是类型double
-否则应该使用类型double
。float
适合的一个地方是存储浮点值的 * 大 * 向量(在使用类型double
计算之后)。不过,我已经将
float
更改为double
。