Grabbing an element deep inside a nested hash

Grabbing an element deep inside a nested hash

am 03.08.2011 20:12:02 von sono-io

I'm trying to sort a shopping cart basket on the item numbers. =
The basket is stored in a hash. There is a hashref called =
%{$main::global->{cart}} that Data::Dumper prints out like so:

$VAR1 =3D {
'1' =3D> '1 1 SL-8206 =
73.15 Label 8.25" x 6" =
0.8 73.15 ',
'3' =3D> '3 1 WR-9253 =
13.98 Label - 3" x 9" =
0.5 13.98 ',
'2' =3D> '2 1 APT-46V =
96.43 4"x6" Label, hook =
1.8 96.43 '
};

I would like to grab the item numbers, i.e. SL-8206, to sort the =
cart on. Could someone point me to something to read up on this? What =
is the above actually? It looks to me like a hash of hashes of =
arrays(?).

I thought that converting it into an array would help, like so:

my $n =3D keys($main::global->{cart});
my @cart_lineitems =3D @{$main::global->{cart}}{0 .. $n};

but dumping the output:

print {$tracelog_fh} Dumper($cart_lineitems[0]);
print {$tracelog_fh} Dumper($cart_lineitems[1]);
print {$tracelog_fh} Dumper($cart_lineitems[2]);
print {$tracelog_fh} Dumper($cart_lineitems[3]);

gives me this:

$VAR1 =3D '';
$VAR1 =3D '1 1 SL-8206 =
73.15 Label 8.25" x 6" =
0.8 73.15 ';
$VAR1 =3D '2 1 APT-46V =
13.98 Label - 3" x 9" =
1.8 13.98 ';
$VAR1 =3D '3 1 WR-9253 =
96.43 4"x6" Label, hook =
0.5 96.43 ';

Why the first one is empty is beyond me, but that's another =
story. I then tried to grab just the item numbers using both:

print {$tracelog_fh} Dumper($cart_lineitems[1][2]);

and

print {$tracelog_fh} Dumper($main::global->{cart}->{1}->{2});

but they produce these errors:

Can't use string ("1 1 SL-8206 =
73.15 Label "...) as an ARRAY ref =
while "strict refs" in use

Can't use string ("1 1 SL-8206 =
73.15 Label "...) as an HASH ref while =
"strict refs" in use

Anyway, maybe I'm going about this the wrong way. I know enough =
just to be dangerous at this point. The bottom line is, how would I go =
about grabbing the item numbers from the original hashref so that I can =
sort the cart on them? I'm over my head with this one, so any pointers =
would be extremely helpful. I'm reading about references in the =
"Beginning Perl" book, but so far the light hasn't come on.

Thanks,
Marc=

--
To unsubscribe, e-mail: beginners-unsubscribe@perl.org
For additional commands, e-mail: beginners-help@perl.org
http://learn.perl.org/

Re: Grabbing an element deep inside a nested hash

am 03.08.2011 21:19:09 von Jim Gibson

On 8/3/11 Wed Aug 3, 2011 11:12 AM, "Marc"
scribbled:

> I'm trying to sort a shopping cart basket on the item numbers. The basket is
> stored in a hash. There is a hashref called %{$main::global->{cart}} that
> Data::Dumper prints out like so:
>
> $VAR1 = {
> '1' => '1 1 SL-8206 73.15 Label 8.25" x 6" 0.8 73.15 ',
> '3' => '3 1 WR-9253 13.98 Label - 3" x 9" 0.5 13.98 ',
> '2' => '2 1 APT-46V 96.43 4"x6" Label, hook 1.8 96.43 '
> };
>
> I would like to grab the item numbers, i.e. SL-8206, to sort the cart on.
> Could someone point me to something to read up on this? What is the above
> actually? It looks to me like a hash of hashes of arrays(?).

No. It is just a one-level hash. $VAR1 is a reference to that hash. If it
included any embedded arrays, you would see brackets ([]) in the output. If
there were any embedded hashes, you would see more than one level of braces
({}).

Here is a program that prints out the items in order of item numbers. The
key is to extract and compare the item numbers from the hash value (I
shortened the data lines somewhat to avoid line wrap):

#!/usr/local/bin/perl
use strict;
use warnings;

my $var = {
'1' => '1 1 SL-8206 73.15 Label 8.25" x 6" 0.8 73.15',
'3' => '3 1 WR-9253 13.98 Label - 3" x 9" 0.5 13.98',
'2' => '2 1 APT-46V 96.43 4"x6" Label, hook 1.8 6.43 '
};

for my $item (
sort { substr($var->{$a},4,7) cmp substr($var->{$b},4,7) }
keys %$var )
{
printf(" %3s %s\n", $item, $var->{$item});
}

This can be made more efficient by extracting the keys only once in a method
known as the "Schwartzian transform".





--
To unsubscribe, e-mail: beginners-unsubscribe@perl.org
For additional commands, e-mail: beginners-help@perl.org
http://learn.perl.org/

Re: Grabbing an element deep inside a nested hash

am 03.08.2011 21:30:44 von Rob Dixon

On 03/08/2011 19:12, Marc wrote:
>
> I'm trying to sort a shopping cart basket on the item numbers. The
> basket is stored in a hash. There is a hashref called
> %{$main::global->{cart}} that Data::Dumper prints out like so:
>
> $VAR1 = {
> '1' => '1 1 SL-8206 73.15 Label 8.25" x 6" 0.8 73.15 ',
> '3' => '3 1 WR-9253 13.98 Label - 3" x 9" 0.5 13.98 ',
> '2' => '2 1 APT-46V 96.43 4"x6" Label, hook 1.8 96.43 '
> };
>
> I would like to grab the item numbers, i.e. SL-8206, to sort the cart
> on. Could someone point me to something to read up on this? What is
> the above actually? It looks to me like a hash of hashes of
> arrays(?).

Hi Marc

Take a look at

perldoc perlreftut

and

perldoc perlref

to read up on nested data structures. But what you have here is a simple
hash - there is no nesting at all unless you count {cart} which is an
element of the hash referenced by $global.

The hash %{$global->{cart}} has three elements, with keys '1', '3' and
'2' and values equal to the long string the other side of =>. There is
nothing special about those values - they are simple strings that happen
to have several prices of information in them, separated by whitespace.

> I thought that converting it into an array would help, like so:
>
> my $n = keys($main::global->{cart});

I think this can't be what you wrote - it wouldn't compile. You probably
want

my $n = keys %{$global->{cart}};

> my @cart_lineitems = @{$main::global->{cart}}{0 .. $n};

That is one way of doing things, but I am surprised you happened upon it
without knowing more about Perl. You have written a hash slice that
indexes the array using integers. But hash keys are strings, and it is
fortunate that Perl converts numbers to strings appropriately for this
hash. (For instance, the element $global->{cart}{'1'} is different and
separate from the element $global->{cart}{'01'}.)

I would write

my @cart_lineitems;
foreach my $i (keys %{global->{cart}}) {
$cart_lineitems[$i] = $global->{cart}{$i};
}

although the functionality is equivalent and some would say this way
involves unnecessary code.

> but dumping the output:
>
> print {$tracelog_fh} Dumper($cart_lineitems[0]);
> print {$tracelog_fh} Dumper($cart_lineitems[1]);
> print {$tracelog_fh} Dumper($cart_lineitems[2]);
> print {$tracelog_fh} Dumper($cart_lineitems[3]);
>
> gives me this:
>
> $VAR1 = '';
> $VAR1 = '1 1 SL-8206 73.15 Label 8.25" x 6" 0.8 73.15 ';
> $VAR1 = '2 1 APT-46V 13.98 Label - 3" x 9" 1.8 13.98 ';
> $VAR1 = '3 1 WR-9253 96.43 4"x6" Label, hook 0.5 96.43 ';
>
> Why the first one is empty is beyond me, but that's another story.

The first element is empty because you have assigned values at indices
[1], [2] and [3]. The first element is at [0] and you have left it
untouched.

> I then tried to grab just the item numbers using both:
>
> print {$tracelog_fh} Dumper($cart_lineitems[1][2]);
>
> and
>
> print {$tracelog_fh} Dumper($main::global->{cart}->{1}->{2});
>
> but they produce these errors:
>
> Can't use string ("1 1 SL-8206 73.15 Label "...) as an ARRAY ref while "strict refs" in use
>
> Can't use string ("1 1 SL-8206 73.15 Label "...) as an HASH ref while "strict refs" in use

Because the values of the array elements are simple strings, as I
explained above, you must find a way of splitting them into
individual fields. The simple way would be to use the 'split' operator
to divide the line at whitespace, but the presence of spaces in the
description field spoils that approach. Without knowing more about your
data, I can suggest that you split on sequences of two or more
whitespace characters, like this

my @cart_lineitems;
foreach my $i (keys %{$global->{cart}}) {
$cart_lineitems[$i] = [ split /\s{2,}/, $global->{cart}{$i} ];
}

which dumps like this

@cart_lineitems = (
undef,
[
'1 1 SL-8206',
'73.15',
'Label 8.25" x 6"',
'0.8 73.15 '
],
[
'2 1 APT-46V',
'96.43',
'4"x6" Label, hook',
'1.8 96.43 '
],
[
'3 1 WR-9253',
'13.98',
'Label - 3" x 9"',
'0.5 13.98 '
]
);

Which is reasonable, but you still have trailing spaces at the end of
the final field, which may or may not be a problem.

> Anyway, maybe I'm going about this the wrong way. I know enough just
> to be dangerous at this point. The bottom line is, how would I go
> about grabbing the item numbers from the original hashref so that I
> can sort the cart on them? I'm over my head with this one, so any
> pointers would be extremely helpful. I'm reading about references in
> the "Beginning Perl" book, but so far the light hasn't come on.

Grabbing just the item numbers (the third field) doesn't involve any
column that can contain embedded spaces, so you can just sort on the
value of

( split ' ', $lineitem )[2]

as shown in the program below. Depending on what else you want to do
with your data, it may well be better to store it in a different format,
but I hope this serves to help you with your initial problem.

Cheers,

Rob


use strict;
use warnings;

use Data::Dumper;

our $global;
$global->{cart} = {
'1' => '1 1 SL-8206 73.15 Label 8.25" x 6" 0.8 73.15 ',
'3' => '3 1 WR-9253 13.98 Label - 3" x 9" 0.5 13.98 ',
'2' => '2 1 APT-46V 96.43 4"x6" Label, hook 1.8 96.43 ',
};

my @cart_lineitems = sort {
( split ' ', $a)[2] cmp ( split ' ', $b)[2];
} values %{$global->{cart}};

print Data::Dumper->Dump([\@cart_lineitems], ['*cart_lineitems']);

**OUTPUT**

@cart_lineitems = (
'2 1 APT-46V 96.43 4"x6" Label, hook 1.8 96.43 ',
'3 1 WR-9253 13.98 Label - 3" x 9" 0.5 13.98 ',
'1 1 SL-8206 73.15 Label 8.25" x 6" 0.8 73.15 '
);

--
To unsubscribe, e-mail: beginners-unsubscribe@perl.org
For additional commands, e-mail: beginners-help@perl.org
http://learn.perl.org/

Re: Grabbing an element deep inside a nested hash

am 03.08.2011 21:51:34 von Rob Dixon

On 03/08/2011 20:30, Rob Dixon wrote:
>
> Because the values of the array elements are simple strings, as I
> explained above, you must find a way of splitting them into
> individual fields. The simple way would be to use the 'split' operator
> to divide the line at whitespace, but the presence of spaces in the
> description field spoils that approach. Without knowing more about your
> data, I can suggest that you split on sequences of two or more
> whitespace characters, like this
>
> my @cart_lineitems;
> foreach my $i (keys %{$global->{cart}}) {
> $cart_lineitems[$i] = [ split /\s{2,}/, $global->{cart}{$i} ];
> }
>
> which dumps like this
>
> @cart_lineitems = (
> undef,
> [
> '1 1 SL-8206',
> '73.15',
> 'Label 8.25" x 6"',
> '0.8 73.15 '
> ],
> [
> '2 1 APT-46V',
> '96.43',
> '4"x6" Label, hook',
> '1.8 96.43 '
> ],
> [
> '3 1 WR-9253',
> '13.98',
> 'Label - 3" x 9"',
> '0.5 13.98 '
> ]
> );
>
> Which is reasonable, but you still have trailing spaces at the end of
> the final field, which may or may not be a problem.

Marc, I have since noticed that your original hash values contain tab
characters to separate the fields. THis makes things a lot easier, and
you can write

my @cart_lineitems = map [ split /\t+/ ], values %{$global->{cart}};

which now looks like like the dump below.

Cheers,

Rob


@cart_lineitems = (
[
'1',
'1',
'SL-8206',
'73.15',
'Label 8.25" x 6"',
'0.8',
'73.15'
],
[
'3',
'1',
'WR-9253',
'13.98',
'Label - 3" x 9" ',
'0.5',
'13.98'
],
[
'2',
'1',
'APT-46V',
'96.43',
'4"x6" Label, hook ',
'1.8',
'96.43'
]
);

--
To unsubscribe, e-mail: beginners-unsubscribe@perl.org
For additional commands, e-mail: beginners-help@perl.org
http://learn.perl.org/

Re: Grabbing an element deep inside a nested hash

am 04.08.2011 03:38:48 von sono-io

Jim and Rob,

Thanks to both of you for your responses and for the code you =
supplied. That was a big help. I was able to get it to work and I've =
pasted my code below.

> No. It is just a one-level hash.

I'm glad you both pointed this out. It appears that I was over =
thinking it. Once I was able to see that, a lot of other things fell =
into place.

>> my $n =3D keys($main::global->{cart});
>=20
> I think this can't be what you wrote - it wouldn't compile. You =
probably want
>=20
> my $n =3D keys %{$global->{cart}};

Surprisingly, it did compile, but I like your way better.

>> my @cart_lineitems =3D @{$main::global->{cart}}{0 .. $n};
>=20
> That is one way of doing things, but I am surprised you happened upon =
it without knowing more about Perl.

"Happened upon it" is exactly how I found it. =3D;) Google to =
the rescue! =
http://stackoverflow.com/questions/2907270/perl-convert-hash -to-array =
And it worked - that's why I used it, but I didn't remember that hash =
keys were strings and not numbers.

> Because the values of the array elements are simple strings, as I =
explained above, you must find a way of splitting them into individual =
fields.

I took Rob's advice, since the data was tab separated, merged it =
with Jim's code, and came up with this. Separating the sort into a sub =
helps, since I need to use it in multiple places.


foreach my $cartitem ( sort sortby_itemid keys %{$main::global->{cart}} =
) {
# list the items...
}


sub sortby_itemid {
(split /\t+/, $main::global->{cart}->{$a})[2] cmp (split /\t+/, =
$main::global->{cart}->{$b})[2]
}

Thanks again for your help, guys. I really appreciate it.

Marc=

--
To unsubscribe, e-mail: beginners-unsubscribe@perl.org
For additional commands, e-mail: beginners-help@perl.org
http://learn.perl.org/