我有两个变量id
和date
。有数百万个不同的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之间,因此对应于第一条记录。
感谢您的任何建议。
答案 0 :(得分:2)
我可能会将数据放在SQLite database中,使id
字段成为表的主键。使用DBD::SQLite到DBI。
如果您首先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 references和perldsc的Perl文档,它实际上向您展示了如何设置数组的哈希值。
现在,通过id查找日期......
想象一个更复杂的结构。第一个级别将包含两个元素DATES
和IDS
。然后,您可以让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);
现在,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 **