Посмотрите значение в Perl на основе диапазона - PullRequest
2 голосов
/ 01 ноября 2011

У меня есть две переменные, id и date. Существуют миллионы различных id с, но всего несколько сотен различных дат. id s являются последовательными, а даты увеличиваются с id. Как то так:

id    date
1     1/1/2000
2     1/1/2000
3     1/1/2000
4     1/2/2000
5     1/2/2000

В Perl мне нужно создать функцию, которая будет возвращать date с учетом id. Моей первой мыслью было просто создать хеш-таблицу. Это сработает, но, учитывая, что у меня есть миллионы записей, я подумал, что может иметь смысл работать с датами диапазоны . Таким образом, в приведенном выше примере вместо хранения 5 записей я мог бы хранить 2 записи: по одной для каждой даты с самой ранней и самой поздней датой, соответствующей id:

date       first_id  last_id
1/1/2000   1         3
1/2/2000   4         5

(В моих реальных данных это позволит мне хранить только несколько тысяч записей, а не миллионы.)

Мой вопрос, учитывая id, как лучше всего найти дату, учитывая эту структуру? Поэтому, учитывая id=2, я хочу вернуть 1/1/2000, потому что 2 находится между 1 и 3 и, следовательно, соответствует первой записи.

Спасибо за любой совет.

Ответы [ 5 ]

2 голосов
/ 01 ноября 2011

Использовать [полу] разреженный массив. Производительность должна быть в порядке. Вы смотрите на использование нескольких мегабайт памяти на миллион записей. Если вы преобразуете дату в целочисленную эпоху до ее сохранения, это даже лучше.

use Time::Local;

my @date_by_id;
while (<FILE>) {
  chomp;

  my ($id, $date) = split /\s+/;
  my ($mon, $mday, $year) = split /\//, $date;

  $mon--;
  $year -= 1900;

  $date_by_id[$id] = timelocal 0, 0, 0,  
    $mday, $mon, $year;
}

Производительность должна быть достаточно хорошей, чтобы вам не нужно было заключать ее в функцию. Просто используйте $date_by_id[<ID>], где это необходимо, помните, что это может быть undef

2 голосов
/ 01 ноября 2011

Я бы, вероятно, поместил данные в базу данных SQLite , сделав поле id первичным ключом таблицы.Используйте DBD :: SQLite до DBI .

Если вы сначала prepare запрос, который содержит заполнитель для id и многократновыполнить его для различных значений id, производительность должна быть адекватной.

1 голос
/ 01 ноября 2011

Как уже говорили другие, вы можете попробовать базу данных. Другая возможность: использовать более сложную структуру данных.

Например, если ваша хеш-таблица указана по датам, каждая запись в хэше может быть ссылкой на массив идентификаторов.

Используя ваш пример:

$hash{1/1/2000} = [ 1, 2, 3];
$hash{1/2/2000} = [ 4, 5 ];

Таким образом, если вы найдете дату, вы можете быстро найти все идентификаторы на эту дату. Сортировка ключей позволит вам найти диапазон дат. Это особенно верно, если вы храните даты в более сортируемом формате. Например, в формате ГГГГММДД или в стандартном формате даты / времени Unix.

Например:

$hash{20000101} = [ 1, 2, 3];
$hash{20000102} = [ 4, 5];

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

Вы знакомы с такими вещами, как хэши массивов? Вы можете посмотреть документацию по Perl для очень короткого учебника Марка о ссылках и perldsc , который фактически показывает, как настроить хэши массивов.

Теперь ищем дату по идентификатору ...

Представьте себе более сложную структуру. Первый уровень будет иметь два элемента DATES и IDS. Затем часть IDS может быть ссылкой на хэш идентификаторов, а ключ DATES будет иметь ту же структуру, что и упомянутая выше. Вам придется синхронизировать эти две структуры, хотя ...

$dataHash->{DATES}->{20020101}->[0] = 1;
$dataHash->{DATES}->{20020101}->[2] = 2;
$dataHash->{DATES}->{20020101}->[3] = 3;
$dateHash->{IDS}->{1} = 20020101;
$dateHash->{IDS}->{2} = 20020101;
$dateHash->{IDS}->{3} = 20020101;

Хм ... Это становится сложным. Возможно, вам стоит взглянуть на учебник Perl по объектно-ориентированному программированию .

Снятие материала с головы без всякого тестирования:

package DataStruct;

sub new {
   my $class = shift;

   my $self = {};
   bless $self, $class;

  my $self->_Id;
  my $self->_Date;

  return $self;
}

sub _Id {
   my $self = shift;
   my $id   = shift;
   my $date = shift;

   $self->{IDS} = {} if not exists $self->{IDS};

   if (defined $id and defined $date) {
      $self->{IDS}->{$id} = $date;
   }

   if (defined ($id) {
      return $self->{IDS}->{$id};
   else {
       return keys %{self->{IDS}};
   }
}

sub _Date {
   my $self = shift;
   my $date = shift;
   my $id   = shift;

   $self->{DATES} = {} if not exists $self->{DATES};

   if (defined $date and defined $id) {
      $self->{DATES}->{$date} = [] if not defined $self->{DATES}->{$date};
      push @{$self->{DATES}->{$date}}, $id;
   };

   if ($date) {
       return @{$self->{DATES}->{$date}};
   }
   else {
       return keys %{$self->{DATES};
   }
}

sub Define {
    my $self = shift;
    my $id   = shift;
    my $date = shift;

    $self->_Id($id, $date);
    $self->_Date($date, $id);

    return $self->_Date($date);
}

sub FetchId {
    my $self = shift;
    my $id   = shift;

    return $self->_Id($id);
}

sub FetchDate {
    my $self = shift;
    my $id   = shift;

    return $self->_Date;
}

В приведенном выше примере вы создаете исходную структуру данных с помощью:

my $struct = DataStruct->new;

Теперь, чтобы добавить дату и идентификатор, вы должны позвонить:

$struct->Define($id, $date);

Это, в свою очередь, вызовет $struct->_Id($id, $date); и $struct->_Date($date, $Id);. Поскольку они начинаются с подчеркивания, они private и могут быть вызваны только другими методами DataStruct. В основном вы используете $ struct-Set для ввода ваших данных.

Чтобы получить определенную дату (или весь диапазон дат), вы используете метод $dataStruct->FetchDate($date), а для получения определенного идентификатора вы используете $dataStruct->FetchId($id);

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

Там есть все, что вам нужно! Все, что вам нужно сделать, это исправить мои многочисленные ошибки и, вероятно, иметь некоторую подпрограмму, которая преобразует дату в стиле M/D/Y в дату в стиле YYYYMMDD или в стандартную структуру внутреннего хранилища даты и времени. Таким образом, вам не нужно беспокоиться об исправлении даты перед вызовом этих подпрограмм. О, и вы, вероятно, тоже захотите какую-то обработку ошибок. Что если я дам вам неправильную дату или идентификационный номер?

Как уже говорили другие, вам лучше использовать структуру базы данных, даже если вы используете искусственную структуру базы данных, такую ​​как SQLite.

Однако я хотел сообщить вам, что Perl на самом деле вполне способен создавать некоторые очень интегрированные структуры данных, которые могут помочь в подобных случаях.

Из того, как вы сформулировали свой вопрос, я предположил, что вы действительно не были знакомы с созданием этих сложных структур данных. Если нет, то в Perl есть несколько превосходных обучающих программ , встроенных в сам Perl. И команда perldoc (которая устанавливается вместе с Perl) может вызвать всю документацию Perl. Попробуйте perldoc perlreftut и посмотрите, не приводит ли он учебник Марка к ссылкам.

Как только вы начнете изучать более сложные структуры данных, вы научитесь использовать объектно-ориентированное программирование, чтобы упростить их обработку. Опять же, есть несколько отличных учебных пособий, встроенных прямо в Perl (или вы можете перейти на веб-страницу Perldoc ).

Если вы уже знали все это, я прошу прощения. Однако, по крайней мере, у вас есть основания для хранения и работы с вашими данными.

0 голосов
/ 01 ноября 2011

Попытка реализовать идею Фрэнка:

Учитывая

sub getDateForId {
  use integer;
  my ($id, $data) = @_;
  my $lo = 0;
  my $sz = scalar @$data;
  my $hi = $sz - 1;
  while ( $lo <= $hi ) {
    my $mi = ($lo + $hi) / 2;
    if ($data->[$mi]->[0] < $id) {
      $lo = $mi + 1;
    } elsif ($data->[$mi]->[0] > $id) {
      $hi = $mi - 1;
    } else {
      return $data->[$mi]->[1];
    }
  }
  # $lo > $hi: $id belongs to $hi range
  if ($hi < 0) {
    return sprintf "** id %d < first id %d **", $id, $data->[0]->[0];
  } elsif ($lo >= $sz) {
    return sprintf "** id %d > last  id %d **", $id, $data->[$sz-1]->[0];
  } else {
    return sprintf "%s (<== lo %d hi %d)", $data->[$hi]->[1], $lo, $hi;
  }
}

и данные

my @data = (
    [2, '1/1/2000' ]
  , [4, '1/2/2000' ]
  , [5, '1/3/2000' ]
  , [8, '1/4/2000' ]
);

, тест

for my $id (0..9) {
  printf "%d => %s\n", $id, getDateForId( $id, \@data );
}

печатает

0 => ** id 0 < first id 2 **
1 => ** id 1 < first id 2 **
2 => 1/1/2000
3 => 1/1/2000 (<== lo 1 hi 0)
4 => 1/2/2000
5 => 1/3/2000
6 => 1/3/2000 (<== lo 3 hi 2)
7 => 1/3/2000 (<== lo 3 hi 2)
8 => 1/4/2000
9 => ** id 9 > last  id 8 **
0 голосов
/ 01 ноября 2011

Если вы хотите использовать такой подход, я думаю, что было бы наиболее целесообразно выполнять запросы на уровне базы данных. Затем, например, в MySQL вы можете запросить с помощью функции BETWEEN что-то вроде SELECT date WHERE $id BETWEEN first_id AND last_id

Затем вы можете создать функцию в Perl, где вы передадите идентификатор и будете использовать запрос для получения даты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...