Perl многомерная таблица с заголовками - PullRequest
6 голосов
/ 27 сентября 2011

Я пытаюсь реализовать многомерную таблицу с заголовками .

Вот пример для 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];
}

Ответы [ 3 ]

2 голосов
/ 27 сентября 2011

Требуется структура для таблицы измерений "N". Я сомневаюсь, что есть модуль CPAN, который может сделать это, потому что это не такая распространенная ситуация.

Проблема в том, что структура данных растет довольно быстро, а также и в сложности.

Вы можете сохранить N-мерную таблицу в одном списке, используя немного математики для преобразования N-мерного массива в одно измерение. Допустим, X представляет измерение X, а X 'представляет длину этого измерения. Для двумерной таблицы вы можете получить значение, выполнив:

X * Y` + Y.

Для трехмерной таблицы X, Y, Z ответом будет:

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

Для четырехмерной таблицы 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};

Конечно, вы бы делали это с настоящими объектно-ориентированными вызовами, а не с голыми ссылками, но это дает вам представление о том, как будет работать структура.

Теперь вам нужен способ преобразования списка целых чисел, который представлял бы местоположение ячейки, в местоположение ячейки вдоль одномерного массива, и у вас будет возможность получать и извлекать ваши данные.

Это то, что вы ищете?


EDIT

Близко к этому, но я на самом деле очень сильно изменяю размеры таблицы (я не могу определить их размер заранее), и, если я понял, ваше решение не подходит для этого.

Это добавляет много сложностей ...

Нам нужно выбросить Size в классе Dimension. И мы не можем использовать одномерный массив для хранения наших данных.

Надеюсь, вы не измените размерность таблицы.

Мы могли бы сделать что-то вроде этого:

  • N-таблица (класс)
    • Список заголовков измерений {DIMENSION} -> []
    • Список данных {DATA} -> [] (это может быть ссылка на другие списки)

Список {DATA} представляет собой ссылку на списки в зависимости от глубины таблицы. Например:

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

Количество измерений scalar @{$table->{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-мерной таблицы.

1 голос
/ 27 сентября 2011

Вы можете использовать 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  |
+------+---------+---------+

Я не уверен, что это именно то, что вы искали. Вы должны изменить заголовок, оставив первый столбец пустым.

0 голосов
/ 27 сентября 2011

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
           dimension1
dimension0 column0 column1
           row0    data00
           row1    data10
...