perl - 通过在 Perl 中使用矩阵结构打印特殊字符 say * 和空格在控制台中绘制图形

标签 perl graph

我的任务是在 X 和 Y 轴/坐标图中绘制机器学习算法每次迭代的最小均方误差 (LSME) 值。我决定使用循环在控制台上打印特殊字符(比如 *)。我不想使用任何库来绘制图形,而是通过打印特殊字符序列来简化,这样我就可以将 X-Y 坐标的第一象限打印到控制台上。

我记得我最初的 Java 编程任务是使用 for 和 while 循环在控制台上打印不同的形状,如金字塔、方形、矩形、圆形等。另外,我熟悉NDC在图形编程中查看端口映射。但是我无法实现这样的嵌套循环来在控制台的第一象限中打印我需要的图形,就像我们在纸上绘制的一样。

在控制台上,原点 (0,0) 是控制台的左上角。但在纸面上,如果我们只绘制第一象限,则原点位于左下角。为了克服这个问题,我想到了一个想法,即我使用二维矩阵结构和它的一些转置操作,并使用字符(空格和 *)来绘制我的图形。我开发了以下代码,它有两个数组,一个带有错误值 (LMSE),另一个带有空格数。

use strict;
use warnings;
use Data::Dumper;

$|= 1;

my @values = (0.7,0.9,2,0.1,1.2,2.4,0.4,3.5,4.9); # Float error values with 1 decimal place
my @values2;

my $XAxis_LMSE = scalar @values;
my ($minLMSE_Graph, $maxLMSE_Graph) = (sort {$a <=> $b} @values)[0, -1];

for (my $i = 0; $i < scalar @values; $i++) {
    my $rem = $maxLMSE_Graph - $values[$i];
    push (@values2, $rem);
}

我计算了我的错误值数组的最大值,并将最大值与原始错误值的差分配给另一个数组。我能够想到的逻辑是,我用空格和 * 填充矩阵,当打印在控制台上时,它在控制台上描绘了 X-Y 第一象限图。我的方法有前途吗?有人可以确认我的方法是正确的,以及如何构建这样一个“”和“*”字符的矩阵吗?

Y(x) 值由数组@values 给出,X 是迭代次数。迭代可以从 1 到 100。而 Y(x) 也仍然是一个整数。它是一个简单的柱形条形图。下面是 Excel 中的示例图,但栏条在控制台上将是一系列字符“*”。这将是一个垂直条形图。

enter image description here

最佳答案

需求的更新改变了游戏规则。请参阅下面的讨论和代码。


一种方式,使用最初发布的整数数据(请参阅下面的更新要求)

use warnings;
use strict;
use feature 'say';

use List::Util qw(max min);

# =================================
# Data posted originally (integer): 
#   in code; with a negative value added; in Excel graph
# ======================================================
my @vals = (7,9,2,0,1,2,4,3,9);
#my @vals = (7,9,2,0,1,2,4,3,-2,9);
#my @vals = (38, 32, 28, 29, 34, 31, 15, 43, 43, 11, 4, 34);

my $max_y = max @vals;
my $min_y = min @vals;

my $min_y_to_show = ($min_y >= 0) ? 1 : $min_y;

for my $y (reverse $min_y_to_show .. $max_y) {
    printf "%2d | ", $y;  # y-axis: value for this row (and "axis")
    say join '',
        map { $_ >= $y ? ' * ' : ' 'x3 } @vals;
}
# x-axis, with its values
say ' 'x4, '-'x(3*@vals);
say ' 'x4, join '', map { sprintf "%3d", $_ } 1..@vals; 

打印

 9 |     *                    * 
 8 |     *                    * 
 7 |  *  *                    * 
 6 |  *  *                    * 
 5 |  *  *                    * 
 4 |  *  *              *     * 
 3 |  *  *              *  *  * 
 2 |  *  *  *        *  *  *  * 
 1 |  *  *  *     *  *  *  *  * 
    ---------------------------
      1  2  3  4  5  6  7  8  9

I've made a few presentational choices of substance: to always plot down to 1 (even if all data are greater) and to not show zero -- unless there are negative values, when all is shown (add a negative value to @vals to test). These are changed fairly easily.

There's also some trivial formatting choices, for layout/spacing etc.

Otherwise there isn't anything manual really. Change @vals to plot a different data set, hopefully in the same style. This wasn't tested much.


Update in the question introduces floating point (decimal) values. This is further elaborated in comments, what altogether amounts to a library-grade project. And some of these wants are just not possible in ASCII in a terminal, where "plotting" goes by character and we only have a 100 or so. Here is code updated for what is feasible here, and some discussion.

To accommodate floating point values (with one digit of precision we are told), the y-axis now need be plotted in smaller increments ("divisions" -- "ticks"), lest we fail to show a lot of data if they are lumped within an integer.

Then, how to divide it? Below I show all data within 20 rows, and with a row for the smallest value added if needed. From that a division is worked out, for the given data set (updated in the question). If the data are clustered around some value far from zero then this isn't good of course (imagine data between 2.8 and 3.9, going by 0.1; why would we plot bars all the way from zero?). But one has to make decisions for a given data set, what can be done automatically as well.

This necessarily leads to some imprecision in how data is shown. Showing every data point correctly isn't feasible in general in a terminal.

use warnings;
use strict;
use feature 'say';

use List::Util qw(max min);
    
my @vals = (0.7, 0.9, 2, 0.1, 1.2, 2.4, 0.4, 3.5, 4.9);

my $n_rows = 20;

my $max_y = max @vals;
my $min_y = min @vals;

# Show from at least the smallest y-division ("tick");
# at first use 0 and then work out the "tick" and adjust
my $min_y_to_show = $min_y >= 0 ? 0 : $min_y;
my $y_tick = ($max_y - $min_y_to_show) / $n_rows;
# Now once we have the y-division ("tick") adjust 
$min_y_to_show = $min_y >= $y_tick ? $y_tick : $min_y;

say "Smallest division for y = $y_tick\n";

my @y_axis = map { $y_tick * $_ } 1 .. $n_rows;
unshift @y_axis, $min_y_to_show if $min_y_to_show < $y_axis[0];

for my $y (reverse @y_axis) {
    printf "%4.2f | ", $y;
    say join '', 
        map { $_ >= $y ? ' * ' : ' 'x3 } @vals;
}
say ' 'x6, '-'x(3*@vals);
say ' 'x6, join '', map { sprintf "%3d", $_ } 1..@vals; 

打印

Smallest division for y = 0.245

4.90 |                          * 
4.66 |                          * 
4.41 |                          * 
4.17 |                          * 
3.92 |                          * 
3.68 |                          * 
3.43 |                       *  * 
3.19 |                       *  * 
2.94 |                       *  * 
2.70 |                       *  * 
2.45 |                       *  * 
2.21 |                 *     *  * 
1.96 |        *        *     *  * 
1.72 |        *        *     *  * 
1.47 |        *        *     *  * 
1.23 |        *        *     *  * 
0.98 |        *     *  *     *  * 
0.74 |     *  *     *  *     *  * 
0.49 |  *  *  *     *  *     *  * 
0.25 |  *  *  *     *  *  *  *  * 
0.10 |  *  *  *  *  *  *  *  *  * 
      ---------------------------
        1  2  3  4  5  6  7  8  9

在评论中的进一步讨论中解释说 x-values 实际上可能有数百个。这必须进行缩放(无法在 100 个字符宽的终端中显示 500 个数据点),但由于并非所有数据都可以显示,因此需要做出进一步的决定。

这对于 Stackoverflow 问答来说太多了。有太多细节需要指定和决定。希望上面的讨论和代码有助于人们设计出更精细的场景。

最后,如果所有这些加起来太多了,我可以推荐使用 Perl 之外的 gnuplot。它生成具有出版物质量的图表,并且对于简单的事情使用起来相当简单——一旦学会,有了所有资源和示例,这并不是一项糟糕的任务。

此外,还有许多其他 Perl 库可用于绘制各种图形。


这是针对问题原始版本中显示的数据(参见此处的代码)

使用从问题中显示的 Excel 图形图像中选取的值,而不是上面使用的 @vals(来自问题的代码),它打印

43 |                       *  *          
42 |                       *  *          
41 |                       *  *          
40 |                       *  *          
39 |                       *  *          
38 |  *                    *  *   
37 |  *                    *  *          
36 |  *                    *  *          
35 |  *                    *  *          
34 |  *           *        *  *        * 
33 |  *           *        *  *        * 
32 |  *  *        *        *  *        * 
31 |  *  *        *  *     *  *        * 
30 |  *  *        *  *     *  *        * 
29 |  *  *     *  *  *     *  *        * 
28 |  *  *  *  *  *  *     *  *        * 
27 |  *  *  *  *  *  *     *  *        * 
26 |  *  *  *  *  *  *     *  *        * 
25 |  *  *  *  *  *  *     *  *        * 
24 |  *  *  *  *  *  *     *  *        * 
23 |  *  *  *  *  *  *     *  *        * 
22 |  *  *  *  *  *  *     *  *        * 
21 |  *  *  *  *  *  *     *  *        * 
20 |  *  *  *  *  *  *     *  *        * 
19 |  *  *  *  *  *  *     *  *        * 
18 |  *  *  *  *  *  *     *  *        * 
17 |  *  *  *  *  *  *     *  *        * 
16 |  *  *  *  *  *  *     *  *        * 
15 |  *  *  *  *  *  *  *  *  *        * 
14 |  *  *  *  *  *  *  *  *  *        * 
13 |  *  *  *  *  *  *  *  *  *        * 
12 |  *  *  *  *  *  *  *  *  *        * 
11 |  *  *  *  *  *  *  *  *  *  *     * 
10 |  *  *  *  *  *  *  *  *  *  *     * 
 9 |  *  *  *  *  *  *  *  *  *  *     * 
 8 |  *  *  *  *  *  *  *  *  *  *     * 
 7 |  *  *  *  *  *  *  *  *  *  *     * 
 6 |  *  *  *  *  *  *  *  *  *  *     * 
 5 |  *  *  *  *  *  *  *  *  *  *     * 
 4 |  *  *  *  *  *  *  *  *  *  *  *  * 
 3 |  *  *  *  *  *  *  *  *  *  *  *  * 
 2 |  *  *  *  *  *  *  *  *  *  *  *  * 
 1 |  *  *  *  *  *  *  *  *  *  *  *  * 
    ------------------------------------
      1  2  3  4  5  6  7  8  9 10 11 12

关于perl - 通过在 Perl 中使用矩阵结构打印特殊字符 say * 和空格在控制台中绘制图形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73666050/

相关文章:

regex - sed:用匹配的模式替换第 n 个词?

python - 为图形构建创建 python GUI 的工具

graph - Gremlin 关系建议

arrays - 通过 Perl 中的哈希引用数组获取切片

python - Pydot 错误 : file format "png" not recognized

java - 二维数组的水容量

c++ - 如何检测图的边列表表示中的循环?

perl - 在单独的 git 存储库中维护/测试(moSTLy Perl)软件且它们之间存在依赖关系的最佳实践是什么?

perl - “utf8 ”\x96 Perl中的“does not map to Unicode at <somefile.pl> at line no - 321”错误

python - 从 Perl 调用 Node.js(或 python)