【问题标题】:Perl multi-dimensional table with headers带标题的 Perl 多维表
【发布时间】:2011-11-25 21:04:58
【问题描述】:

我正在尝试实现一个带有标题的多维表

这是 2D 的示例:

                       < dimension1 >
    /\               'column0'  'column1'
dimension0   'row0'   data00     data10
    \/       'row1'   data01     data11

行和列的标题是文本,数据是任何东西。我希望能够做这样的事情(语法可以不同,我是 Perl 的初学者):

my $table = new table(2); # 2 is the number of dimensions

# the following line creates a new row/column if it didn't exist previously
$table['row0']['column0'] = data00;
$table['row0']['column1'] = data01;
$table['row1']['column0'] = data10;
$table['row1']['column1'] = data11;

# the following line returns the headers of the specified dimension
$table->headers(0);
 => ('row0', 'row1')

第一个问题:在 CPAN 中是否已经完成了类似的操作? (在你问之前,我确实搜索了很长时间,但没有找到类似的东西)


第二个问题:这是我的尝试,我知道它很难看,而且可能是错误的。有没有 Perl 专家愿意审查我的代码?

package table;

sub new {
  my $class = shift;
  my $dimensions = shift;
  my $self = bless({}, $class);
  $self->{dimensions} = $dimensions;
  $self->{data} = [];
  $self->{headers} = [];
  return $self;
}

sub get_dimensions {
  my $self = shift;
  return $self->{dimensions};
}

# This function creates a header or return its index if it already existed.
# Headers are encoded as an array of hashes so that this is O(1) amortized.

sub header {
  my $self = shift;
  my $dimension = shift;
  my $header = shift;
  my $headers = $self->{headers}[$dimension];
  if(!defined($headers)) {
    $headers = $self->{headers}[$dimension] = {};
  }
  if(!defined($headers->{$header})) {
    $headers->{$header} = scalar keys %$headers;
  }
  return $headers->{$header};
}

# This function returns the list of headers. Because the headers are
# stored as a hash (`header=>index`), I need to retrieve the keys
# and sort them by value.

sub get_headers {
  my $self = shift;
  my $dimension = shift;
  my $headers = $self->{headers}[$dimension];
  return [sort { $headers->{$a} cmp $headers->{$b} } keys %$headers];
}

# This last function stores/retrieves data from the table.

sub data {
  my $self = shift;
  my $data = $self->{data};
  my $dimensions = $self->{dimensions};
  for(my $i = 0; $i < $dimensions-1; ++$i) {
    my $index = $self->header($i, shift);
    if(!defined($data->[$index])) {
      $data->[$index] = [];
    }
    $data = $data->[$index];
  }
  my $index = $self->header($dimensions-1, shift);
  my $value = shift;
  if(defined($value)) {
    $data->[$index] = $value;
  }
  return $data->[$index];
}

【问题讨论】:

  • 如果您的数据是由数字组成的,您可能会发现Math::MatrixReal 非常有用。
  • @MarcoDeLellis 这只是 2D,没有任何维度,也没有标题,所以很遗憾这不是我想要的。
  • 我喜欢这个主意,今晚会尝试一下。
  • @JanHartung 太好了,如果您找到更好的方法,请告诉我。我已经在一个应用程序中实现了这个示例,经过一些分析后,我发现header 函数花费了太多时间。
  • @GiovanniFunchal:好的,您的代码似乎做得很好。当然可以进行一些调整,但为此我想了解更多信息。您分析了代码,所以我猜性能是一个问题。当我使用我的测试用例(大致是您的示例使用场景)进行分析时,data 函数花费了大约 60% 的时间。最好的事情是如果您可以提供一些示例数据和您想要使用它执行的实际操作的描述。同样有趣的是你对内存的慷慨,所以也许我们可以通过使用散列而不是数组来加快速度。

标签: perl cpan perl-module perl-data-structures perl-hash


【解决方案1】:

您需要一个“N”维表的结构。我怀疑是否有 CPAN 模块可以做到这一点,因为这种情况并不常见。

问题在于数据结构增长得非常快,复杂性也随之增加。

您可以将 N 维表存储在单个列表中,方法是使用一些数学方法将 N 维数组转换为单个维度。假设 X 表示 X 维度,X' 表示该维度的长度。对于二维表,您可以通过以下方式获取值:

X * Y` + Y.

对于 3 维表 X、Y、Z,答案是:

X * (Y' * Z') + Y * Z' + Z

对于 4 维表 W、X、Y、Z,答案是:

W * (X' * Y' * Z') + X * (Y' + Z') + Y * Z' + Z'

(我希望数学是正确的)。

因此,我可以为 N 维表想象这样的结构。它将涉及两个不同的类:一个代表维度信息,另一个代表实际数据(包括所有维度)。

  • 维度(类)
    • 标题(字母数字字符串)
    • 维度大小(整数)
  • N 表(类)
    • 维度数组(维度类对象)
    • 数据数组(字母数字字符串)

你可以通过查看获得维度的数量:

my $numOfDimensions = scalar @{$ntable->{DIMENSIONS}};

并且,您可以通过查看获得维度$x 的标题:

my xDimensionHeading = $ntable->{DIMENSION}->[$x]->{HEADING};

并且,通过查看该维度的大小:

my xDimensionSize = $ntable->{DIMENSION}->[$x]->{SIZE};

当然,您会使用真正的面向对象调用而不是裸引用来执行此操作,但这会让您了解结构的工作原理。

现在,您需要一种将表示单元格位置的整数列表转换为沿一维数组的单元格位置的方法,并且您将有一种获取和检索数据的方法。

这就是你要找的吗?


编辑

接近它,但我实际上调整了很多表格尺寸(我无法提前确定它们的大小),如果我理解你的解决方案不适合这个。

这增加了很多复杂性......

我们需要在 Dimension 类中丢弃 Size。而且,我们不能使用一维数组来存储我们的数据。

希望您不要更改表格维度。

我们可以这样做:

  • N 表(类)
    • 维度标题列表 {DIMENSION}->[]
    • 数据列表 {DATA}->[](这可能是指向其他列表的链接)

{DATA} 列表是列表的链接,具体取决于表的深度。例如:

 my data_3D = $table_3D->{DATA}->[$x]->[$y]->[$z];
 my data_2D = $table_2D->{DATA}->[$x]->[$y];

维数为scalar @{$table-&gt;{DIMENSION}}

问题是我如何以不影响维度的方式访问数据。我可能需要 2、3、4 或更多维度,而且我必须有某种方式来构建我的地址才能将其提取出来。

我们可以有某种循环机制。我们得到@coordinates中的坐标列表,然后查看每个坐标。最后一个将指向数据。剩下的只是对另一个数组的另一个引用。

 my $data = pop @coordinates;    #First Coordinate
 $data = $table->[$data];        #Could be data if 1D table, could be a reference
 foreach my $coordinate (@coordinates) {
    die qq(Not enough coordinates) if ref $data ne 'ARRAY';
    $data = $data->[$coordinate];   #Could be data, could be a reference
 }

 # Cell value is in $data

也可以建立一个坐标列表,然后对其进行评估。再次完全未经测试:

 $coordinates = "[" . join ("]->[" => @coordinates . "]";

如果有三个坐标,那就是

 $coordinates = "[$x]->[$y]->[$z]";

我不确定一维数组是如何工作的......

从那里,您可以构建一个语句并在其上使用eval 并获取数据。

你必须有几种方法。

  • 设置尺寸
  • 设置单元格
  • 检索单元格
  • 验证表的完整性(我不知道这将如何工作。

这更像是一个大脑转储,但我认为这可能会奏效。您没有任何设置的表格尺寸,它可能适用于任何 N 维表格。

【讨论】:

  • 接近它,但我实际上调整了很多表格尺寸(我无法提前确定它们的大小),如果我理解你的解决方案不适合这个。
  • 表格大小调整增加了很多复杂性。如果您增加或减少表大小,则必须重组所有数据以包含更改。我会看看我能想出什么......
  • 感谢您的宝贵时间。我对 join+eval 解决方案有疑问,如果某些维度组合未定义(即稀疏)怎么办?
  • 表格不完整?另一个并发症。连接将产生所需的坐标,但 eval 将失败,您可以捕捉到。问题是每个坐标都是它自己的数据结构。因此,如果您必须一次构建一个坐标,则必须检查每个坐标以查看它是否存在。无论如何,你可能会更好地使用循环。
  • 我最终改用 Python。
【解决方案2】:

Text::Table 可能在这里有用。我在下面展示了一个简单的示例:您可以使用模块提供的各种选项来创建与您描述的内容相似的内容。

#!/usr/bin/perl

use warnings;
use strict;

use Text::Table;

my $inner_table = Text::Table->new(qw(column0 column1));

$inner_table->load(
    [ qw(row0 data00 data01) ],
    [ qw(row1 data10 data11) ],
);

my $outer_table = Text::Table->new(' ', 'dimension1');

$outer_table->load(
    ['dimension0', $inner_table->stringify ],
);

print $outer_table;

输出

C:\Temp> t
           维度1
维度0 列0 列1
           第0行数据00
           第1行数据10

【讨论】:

    【解决方案3】:

    您可以使用Text::TabularDisplay 来执行此操作。这是我对您的示例进行的快速试验。

    use strict;
    use warnings;
    use Text::TabularDisplay;
    
    my $t = Text::TabularDisplay->new(('', 'column0', 'column1'));
    $t->add('row0', 'data00', 'data10');
    $t->add('row1', 'data01', 'data11');
    print $t->render;
    

    显示:

    +------+---------+---------+
    |      | column0 | column1 |
    +------+---------+---------+
    | row0 | data00  | data10  |
    | row1 | data01  | data11  |
    +------+---------+---------+
    

    我不确定这是否正是您想要的。您必须通过将第一列留空来修改标题。

    【讨论】:

    • 这仅适用于 2D 数据,其目的是漂亮的打印,而不是存储/操作。
    猜你喜欢
    • 1970-01-01
    • 2015-01-17
    • 2016-12-24
    • 2013-06-02
    • 2011-08-17
    • 2012-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多