根据范围在Perl中查找值

时间:2011-10-31 20:00:57

标签: perl hashtable

我有两个变量iddate。有数百万个不同的id,但只有几百个不同的日期。 id是顺序的,日期随着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。我的第一个想法是制作哈希表。这将有效,但鉴于我有数百万条记录,我认为使用日期范围可能更有意义。因此,在上面的示例中,我可以存储2条记录,而不是存储5条记录:每条记录一条记录,其中最早和最晚的日期对应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 个答案:

答案 0 :(得分:2)

我可能会将数据放在SQLite database中,使id字段成为表的主键。使用DBD::SQLiteDBI

如果您首先prepare包含id的{​​{3}}并且针对id的各种值重复执行该查询,则效果应该足够。

答案 1 :(得分:2)

使用[半]稀疏数组。表现应该没问题。您正在查看每百万条记录中几兆字节的内存使用情况。如果在存储之前将日期转换为整数纪元,那就更好了。

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 :(得分:1)

正如其他人所说,您可能想尝试数据库。另一种可能性:使用更复杂的数据结构。

例如,如果您的哈希表是按日期排列的,那么您可以让哈希中的每个条目成为一个引用到一个ID数组。

使用您的示例:

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

这样,如果找到日期,您可以快速找到该日期的所有ID。对键进行排序将允许您查找一系列日期。如果以更可排序的格式存储日期,则尤其如此。例如,以YYYYMMDD格式或标准Unix日期/时间格式。

例如:

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

你说有几百个日期,所以排序日期会相当快。

你熟悉数组哈希之类的东西吗?您可以查看Mark's very short tutorial about referencesperldsc的Perl文档,它实际上向您展示了如何设置数组的哈希值。

现在,通过id查找日期......

想象一个更复杂的结构。第一个级别将包含两个元素DATESIDS。然后,您可以让IDS部分成为ID哈希的引用,并且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;
嗯......这变得越来越复杂了。也许您应该查看object oriented programming上的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;

现在,要添加日期和ID,请致电:

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

这将依次调用$struct->_Id($id, $date);$struct->_Date($date, $Id);。由于它们以下划线开头,因此它们是私有,并且只能由其他DataStruct方法调用。您主要使用$ struct-Set将数据放入。

要获取特定日期(或整个日期范围),请使用$dataStruct->FetchDate($date)方法,并获取您使用$dataStruct->FetchId($id);

的特定ID

现在,DataStruct包将用于保持ID散列和Dates散列彼此同步,并将复杂性保留在程序的主要部分之外。

你需要的一切!您所要做的就是修复我的大量错误,并且可能有一些例程可以将M/D/Y样式日期转换为YYYYMMDD样式日期,或者转换为标准日期/时间内部存储结构。这样,在调用这些例程之前,您不必担心修复日期。哦,你可能也想要某种错误处理。如果我给你一个错误的日期或身份证号码该怎么办?

正如其他人所说,即使您使用像SQLite这样的虚假数据库结构,最好还是使用数据库结构。

但是,我想告诉您,Perl实际上非常能够创建一些非常集成的数据结构,这可以在这种情况下提供帮助。

我假设你提出问题的方式,你真的不熟悉创建这些复杂的数据结构。如果没有,Perl在Perl中内置了一些优秀的tutorials。而且,命令perldoc(与Perl一起安装)可以提取所有Perl文档。试试perldoc perlreftut,看看它是否提供了Mark的参考教程。

一旦开始涉及更复杂的数据结构,您将学习使用面向对象的编程来简化其处理。同样,在Perl上有一些优秀的教程(或者你可以转到Perldoc webpage)。

如果您已经知道所有这些,我道歉。但是,至少您有存储和处理数据的基础。

答案 3 :(得分:0)

如果您采用这样的方法,我认为在数据库级别进行查询最有意义。然后,使用MySQL,您可以使用BETWEEN

之类的SELECT date WHERE $id BETWEEN first_id AND last_id函数进行查询

然后你可以在Perl中创建一个函数,你传递id并使用查询来检索日期。

答案 4 :(得分:0)

试图实施弗兰克的想法:

鉴于

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 **