Re: Use a hashref as a key in another hashref?
by 2teez (Vicar) on Jun 05, 2014 at 04:13 UTC
|
hi mwb613,
..Is it possible to use hash reference as a key in another hash reference?..
Ordinarily, maybe not! (But that is a 'white' lie) ;) Until you consider module like Tie::RefHash, which allows you use reference as hash key.
use strict;
use warnings;
use Data::Dumper;
use Tie::RefHash;
tie my %h, "Tie::RefHash";
my $key_hash_1 =
{ 'day' => 1, 'month' => 1, 'year' => 2000, 'hour' => 4, 'minute' =>
+ 44 };
my $key_hash_2 =
{ 'day' => 1, 'month' => 1, 'year' => 2000, 'hour' => 4, 'minute' =>
+ 45 };
%h = (
$key_hash_1 =>
{ 'burgers_sold' => 5, 'fries_sold' => 3, 'sodas_sold' => 11 },
$key_hash_2 => { 'burgers_sold' => 2, 'fries_sold' => 4, 'sodas_so
+ld' => 7 }
);
print Dumper( $key_hash_1, $key_hash_2 );
print Dumper( \%h );
for ( keys %h ) {
print join ' ' => %{$_}, ' => ', %{ $h{$_} }, $/;
}
Output:
Secondly, I think where you are using chains of while loops, a for loop would have been better.
If you tell me, I'll forget.
If you show me, I'll remember.
if you involve me, I'll understand.
--- Author unknown to me
| [reply] [d/l] [select] |
|
|
| [reply] |
Re: Use a hashref as a key in another hashref?
by LanX (Saint) on Jun 05, 2014 at 03:32 UTC
|
> Is it possible to use hash reference as a key in another hash reference?
That's a classic! :)
Hash-keys are always stringified and there is no "direct" way to dereference the stringification.
BUT you are free to keep the original ref somewhere. Either as value in your data-structure or in a separate hash with $ref_of_str{"$key_hash"}=$key_hash. ¹
Thats much safer BTW cause a string doesn't increment the reference count ... i.e. keeping the reference somewhere protects the data from destruction if ref-count reaches 0.
(I hope it's clear now why dereferencing a string will hardly be ever possible)
HTH! :)
Cheers Rolf
(addicted to the Perl Programming Language)
update
¹) I rarely come into situations where I really need this trick. I'm sure your data-structure could be rewritten to avoid this, but tl;dr... | [reply] [d/l] |
|
|
The problem with this is that reference addresses are not guaranteed to be unique. If a hash is destroyed (e.g. has gone out of scope), then its address can be re-used when a new hash is created. On my machine at least, the following prints the same reference address twice:
{
my $x = { foo => 1 };
print $x, "\n";
}
{
my $y = { bar => 2 };
print $y, "\n";
}
use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
| [reply] [d/l] |
|
|
Sure that's why you have to keep the original unweakend ref for each key!
The keystrings are guaranteed to be unique as long as the ref count is positive.
And as soon as a hash entry is deleted the shadow entry should be deleted too.
This could best be synchronized with a tied hash or a dedicated object.
And that structure can be safely passed around then.(CPAN to rescue! ;)
But as I already said this is rarely worth the trouble, avoiding refs as keys is the better design decision.
Cheers Rolf
(addicted to the Perl Programming Language)
| [reply] |
|
|
Thanks Rolf,
Since it's a classic hopefully it at least brought a sense of nostalgia for you. ;)
What you said regarding the key being stringified and references possibly being lost to garbage collection makes sense.
My hope was to be able to traverse the hash and search for specific values in the "key hash" to determine whether I want the stats or not. Searching through a separate hash wherein I store the "keys" doesn't appeal to me much but I will test it as an option. Right now I'm reconsidering the idea of each "data set" having it's own key constructor and destructor subs that just ride around in the data structure with it. I could effectively have a sub that takes a search value and determines if it's part of the key similar a hash -- though I'm sure not as fast. For example
#imagine the config was loaded from a YAML file
my $string_search_function = $config->{'string_search_function'};
if(&{ $string_search_function }($key_string, $key_component_label, $va
+lue_to_search_for)){
#do something interesting
}
Thanks again for your thoughts! | [reply] [d/l] |
|
|
| [reply] |
Re: Use a hashref as a key in another hashref?
by GrandFather (Saint) on Jun 05, 2014 at 08:45 UTC
|
use strict;
use warnings;
my @dateFields = qw(year month day hour minute);
my @itemKeys = qw(burgers fries sodas);
my %sales = (
makeEntry(
'day' => 1,
'month' => 1,
'year' => 2000,
'hour' => 4,
'minute' => 44,
'burgers_sold' => 5,
'fries_sold' => 3,
'sodas_sold' => 11
),
makeEntry(
'day' => 1,
'month' => 1,
'year' => 2000,
'hour' => 4,
'minute' => 45,
'burgers_sold' => 2,
'fries_sold' => 4,
'sodas_sold' => 7
),
);
for my $key (sort keys %sales) {
print "$key\n";
printf " %-7s %s\n", $_, $sales{$key}{"${_}_sold"} for @itemKeys
+;
}
sub makeEntry {
my (%entry) = @_;
return
sprintf ("%04d/%02d/%02d %02d:%02d:00", @entry{@dateFields}),
\%entry;
}
Prints:
2000/01/01 04:44:00
burgers 5
fries 3
sodas 11
2000/01/01 04:45:00
burgers 2
fries 4
sodas 7
Perl is the programming world's equivalent of English
| [reply] [d/l] [select] |
Re: Use a hashref as a key in another hashref?
by GrandFather (Saint) on Jun 05, 2014 at 11:35 UTC
|
use strict;
use warnings;
use DBI;
my @date = qw(year month day hour minute);
my @items = qw(burgers fries sodas);
my @fields = (@date, @items);
my @entries = (
makeEntry(
'day' => 1,
'month' => 1,
'year' => 2000,
'hour' => 4,
'minute' => 44,
'burgers' => 5,
'fries' => 3,
'sodas' => 11
),
makeEntry(
'day' => 1,
'month' => 1,
'year' => 2000,
'hour' => 4,
'minute' => 45,
'burgers' => 2,
'fries' => 4,
'sodas' => 7
),
);
unlink 'delme.sqlite';
my $dbh = DBI->connect("dbi:SQLite:dbname=delme.sqlite","","");
my $sql = <<SQL;
CREATE TABLE Sales (
year INTEGER,
month INTEGER,
day INTEGER,
hour INTEGER,
minute INTEGER,
burgers INTEGER,
fries INTEGER,
sodas INTEGER
)
SQL
$dbh->do ($sql);
my $columns = join ', ', @fields;
my $places = join ', ', ('?') x @fields;
my $sth = $dbh->prepare ("Insert INTO Sales ($columns) VALUES ($places
+)");
$sth->execute(@$_) for @entries;
$sth = $dbh->prepare("select * from Sales order by Year, Month, Day, H
+our, Minute");
$sth->execute();
while (my $row = $sth->fetchrow_hashref()) {
printf "%04d/%02d/%02d %02d:%02d:00\n", @{$row}{@date};
printf " %-7s %s\n", $_, $row->{"${_}"} for @items;
}
sub makeEntry {
my %row = @_;
return [@row{@fields}];
}
Prints:
2000/01/01 04:44:00
burgers 5
fries 3
sodas 11
2000/01/01 04:45:00
burgers 2
fries 4
sodas 7
But now that you have the data in a database you have much better options for extracting interesting information. For example, you can now order the output by, say, soda sales with a simple change to the select (select * from Sales order by fries desc, Year, Month, Day, Hour, Minute) or look at sales during a particular time each day (select * from Sales Where minute = 45).
Perl is the programming world's equivalent of English
| [reply] [d/l] [select] |
|
|
Or 'Relational' like this perhaps
#!perl
use strict;
use warnings;
use DBI;
#create db
my $dbfile = 'database.sqlite';
unlink($dbfile);
my $dbh = DBI->connect('dbi:SQLite:dbname='.$dbfile , undef , undef ,
{RaiseError =>1, AutoCommit =>0}) or die $DBI::errstr;
$dbh->do('CREATE TABLE STATS (ID integer,
DTIME time,
PRIMARY KEY (ID))');
$dbh->do('CREATE TABLE SALES (ID integer,
PRODUCT,
VOLUME integer,
PRIMARY KEY (ID,PRODUCT))');
$dbh->do('CREATE TABLE PRODUCT (PRODUCT,
SECTOR,
PRIMARY KEY (PRODUCT))');
$dbh->commit;
# load data
my $sth = $dbh->prepare('INSERT INTO STATS VALUES (?,?)');
while (<DATA>){
chomp;
last if $_ eq 'PRODUCT';
my ($id,@f) = split ',';
my $dt = sprintf "%04d-%02d-%02d %02d:%02d:00",@f[2,1,0,3,4];
$sth->execute($id,$dt) if ($id);
print "STATS INSERT $id,$dt\n";
}
$dbh->commit;
$sth = $dbh->prepare('INSERT INTO PRODUCT VALUES (?,?)');
while (<DATA>){
chomp;
last if $_ eq 'SALES';
my (@f) = split ',';
$sth->execute(@f) if (@f==2 );
print "PRODUCT INSERT @f\n";
}
$dbh->commit;
$sth = $dbh->prepare('INSERT INTO SALES VALUES (?,?,?)');
while (<DATA>){
chomp;
my (@f) = split ',';
$sth->execute(@f) if (@f==3);
print "SALES INSERT @f\n";
}
$dbh->commit;
# report Sales in Sector By Year
print "\n";
my $sql = "SELECT strftime('%Y',DTIME) as year,SECTOR,SUM(VOLUME) AS t
+otal
FROM SALES AS sa
LEFT JOIN STATS AS st
ON sa.ID = st.ID
LEFT JOIN PRODUCT AS pr
ON sa.PRODUCT = pr.PRODUCT
GROUP BY year,SECTOR
ORDER BY total";
my $ar = $dbh->selectall_arrayref($sql);
print "REPORT\n";
printf "%4s %-10s %6s\n",qw(Year Sector Volume);
printf "---- ---------- ------\n";
for (@$ar){
printf "%4d %-10s %6d\n",@$_;
}
__DATA__
1,1,1,2000,4,44
2,1,1,2000,4,45
PRODUCT
burgers, food
fries, food
sodas, drink
SALES
1,burgers,5
1,fries,3
1,sodas,11
2,burgers,2
2,fries,4
2,sodas,7
Produces
REPORT
Year Sector Volume
---- ---------- ------
2000 food 14
2000 drink 18
poj
| [reply] [d/l] [select] |
Re: Use a hashref as a key in another hashref?
by locked_user sundialsvc4 (Abbot) on Jun 05, 2014 at 13:35 UTC
|
If you want to insist on continuing to use in-memory data structures to, in effect, represent database tables, then you will need to follow the same general design principles of third-normal form. For instance, a timestamp is an attribute of the data which, even though it may be thought of as “unique,” is not suitable for use as a key because it contains data. Instead, you need to assign each and every record an abstract foreign-key value ... in this confined case, an incrementing integer would do, since apparently the data is not stored ... and reference each record by the use of these keys. Where one record “refers to” another, a column in that table hashref in that object should contain the corresponding record’s key. (Circular storage references are thereby avoided.)
I recommend, further, that each type of record be declared as an object. Specifically so that “the data can ‘carry around its own transform functions,’ ” or, anything else that it needs to carry. A blessed-reference costs no more than an ordinary one.
A single hashref could contain a dictionary for all objects indiscriminately, and/or you could have a separate dictionary by type ... since the keys are in any case unique throughout the system, either or both alternatives would do as it suits you.
Having said all of that, however, I do suggest that this is an excellent application for an SQLite database disk file or in-memory file.
| |
Re: Use a hashref as a key in another hashref?
by perlfan (Parson) on Jun 05, 2014 at 13:01 UTC
|
No, but you can create a consistent serialization of the hashref you wish to use as a key - or some sort of consistent checksum "hash" of it. | [reply] |