perl -e '%h=(a=>1,b=>2); for (keys %h) { s/a/b/ }; print keys %h'

A

A. Farber

Hi,

I have a hash where keys and values are file paths.
I'm going to use that hash to generate a GNU Makefile.
For some parts of the file paths I have shortcuts
(like $(TOPDIR)) which I'd like to substitute into the
paths. I.e. I'd like to go through the hash keys and
perform a substitution on them, like in this test case:

perl -e '%h=(a=>1,b=>2); for (keys %h) { s/a/b/ }; print keys %h'
ab

The code above doesn't work as expected - I'd need it to
print "bb" and not "ab". Looks like the hash keys aren't
the "lvalue" described in the "perldoc perlsyn":

If any element of LIST is an lvalue, you can modify it by
modifying VAR inside the loop. Conversely, if any element
of LIST is NOT an lvalue, any attempt to modify that ele-
ment will fail. In other words, the "foreach" loop index
variable is an implicit alias for each item in the list
that you're looping over.

What could I do to solve this problem, please?

I'd prefer a solution with for() or while() because
I need to perform few more operations on the hash keys.

Regards
Alex
 
B

Ben Morrow

Quoth (e-mail address removed) (A. Farber):
I have a hash where keys and values are file paths.
I'm going to use that hash to generate a GNU Makefile.
For some parts of the file paths I have shortcuts
(like $(TOPDIR)) which I'd like to substitute into the
paths. I.e. I'd like to go through the hash keys and
perform a substitution on them, like in this test case:

perl -e '%h=(a=>1,b=>2); for (keys %h) { s/a/b/ }; print keys %h'
ab

The code above doesn't work as expected - I'd need it to
print "bb" and not "ab".

What could I do to solve this problem, please?

You could try (untested)

my %h = ( a => 1, b => 2 );
%h = map {
(my $k = $_) =~ s/a/b/;
( $k => $h{$_} );
} keys %h;
I'd prefer a solution with for() or while() because
I need to perform few more operations on the hash keys.

You can do what you like to the keys inside the map loop as well.

Ben
 
A

Anno Siegel

A. Farber said:
Hi,

I have a hash where keys and values are file paths.
I'm going to use that hash to generate a GNU Makefile.
For some parts of the file paths I have shortcuts
(like $(TOPDIR)) which I'd like to substitute into the
paths. I.e. I'd like to go through the hash keys and
perform a substitution on them, like in this test case:

perl -e '%h=(a=>1,b=>2); for (keys %h) { s/a/b/ }; print keys %h'
ab

I don't see at all how that helps substituting parts of file names
with makefile variables. A templating solution comes to mind.
Lot's of modules for that on CPAN, maybe too many.
The code above doesn't work as expected - I'd need it to
print "bb" and not "ab".

Do you expect the hash to have the key "b" twice? It can't,
hash keys are unique.
Looks like the hash keys aren't
the "lvalue" described in the "perldoc perlsyn":

[...]

No, the elements returned from "keys" aren't lvalues. "perldoc
-f keys" explicitly says so:

The returned values are copies of the original
keys in the hash, so modifying them will not
affect the original hash. Compare "values".

("values" *does* return lvalues.)
What could I do to solve this problem, please?

Nothing, as long as your solution requires a hash to contain the
same key twice. What is it you really want to achieve?

Anno
 
C

ctcgag

No, the elements returned from "keys" aren't lvalues. "perldoc
-f keys" explicitly says so:

The returned values are copies of the original
keys in the hash, so modifying them will not
affect the original hash. Compare "values".

("values" *does* return lvalues.)

I think that they are lvalues, they just aren't lvalues tied
to the original keys. They are anonymous (if you don't
count the alias) independent lvalues.

Xho
 
A

Anno Siegel

I think that they are lvalues, they just aren't lvalues tied
to the original keys. They are anonymous (if you don't
count the alias) independent lvalues.

You are right in that they are assignable (and a good thing that is too).
What I meant was, of course, "assignable with an effect on the original
hash".

Lvalue hash keys, in that sense, would be a bad idea anyway. If a
key is assigned a string that already exists in the hash, one of the
corresponding values has to go. It is by no means clear which one,
but whichever you choose, there is an occasional side effect on the
hash's values. Not an operation I'd like to see in a language.

Anno
 
G

gnari

A. Farber said:
I have a hash where keys and values are file paths.
I'm going to use that hash to generate a GNU Makefile.
For some parts of the file paths I have shortcuts
(like $(TOPDIR)) which I'd like to substitute into the
paths. I.e. I'd like to go through the hash keys and
perform a substitution on them, like in this test case:

perl -e '%h=(a=>1,b=>2); for (keys %h) { s/a/b/ }; print keys %h'

perl -e '%h=(a=>1,b=>2); my @k=keys %h; for (@k) { s/a/b/ }; print @k'

gnari
 
A

A. Farber

gnari said:
perl -e '%h=(a=>1,b=>2); my @k=keys %h; for (@k) { s/a/b/ }; print @k'

Thanks, but that doesn't help me.

What I have in my program (a converter of some weird build system to
GNU Makefile) is a hash with files (KEY=destination,VAL=source path):

$prj_exports = {
'/mnt/cali5/epoc32/wins/c/nokia/sounds/Digital/Clock_alert.mid'
=> '"../data/Clock alert.mid"',
'/mnt/cali5/tcf/cmmphonebookstoremesshandler.h' =>
'../inc/cmmphonebookstoremesshandler.h',
'/mnt/cali5/tcf/cmmphonebookstoreextinterface.h' =>
'../inc/cmmphonebookstoreextinterface.h'
};

Since I have the value "/mnt/cali5" in my Makefile variable $(TOPDIR)
already, here is what I'd like to convert the hash above to:

$prj_exports = {
'$(TOPDIR)/epoc32/wins/c/nokia/sounds/Digital/Clock_alert.mid'
=> '"../data/Clock alert.mid"',
'$(TOPDIR)/tcf/cmmphonebookstoremesshandler.h' =>
'../inc/cmmphonebookstoremesshandler.h',
'$(TOPDIR)/tcf/cmmphonebookstoreextinterface.h' =>
'../inc/cmmphonebookstoreextinterface.h'
};
 
A

Anno Siegel

A. Farber said:
Thanks, but that doesn't help me.

What I have in my program (a converter of some weird build system to
GNU Makefile) is a hash with files (KEY=destination,VAL=source path):

$prj_Gexports = {
'/mnt/cali5/epoc32/wins/c/nokia/sounds/Digital/Clock_alert.mid'
=> '"../data/Clock alert.mid"',
'/mnt/cali5/tcf/cmmphonebookstoremesshandler.h' =>
'../inc/cmmphonebookstoremesshandler.h',
'/mnt/cali5/tcf/cmmphonebookstoreextinterface.h' =>
'../inc/cmmphonebookstoreextinterface.h'
};

Since I have the value "/mnt/cali5" in my Makefile variable $(TOPDIR)
already, here is what I'd like to convert the hash above to:

$prj_exports = {
'$(TOPDIR)/epoc32/wins/c/nokia/sounds/Digital/Clock_alert.mid'
=> '"../data/Clock alert.mid"',
'$(TOPDIR)/tcf/cmmphonebookstoremesshandler.h' =>
'../inc/cmmphonebookstoremesshandler.h',
'$(TOPDIR)/tcf/cmmphonebookstoreextinterface.h' =>
'../inc/cmmphonebookstoreextinterface.h'
};

So you want do a text replacement to the keys of a hash. The general
problem with this is the possibility of key clashes. From what you say
here, clashes can't occur, but that was far from obvious in your
original posting.

There is no way of directly replacing a hash key, as has been discussed
elsewhere in this thread. The general procedure is to delete the old
key and create the new one (untested):

$hash{ $newkey} = delete $hash{ $oldkey} if exists $hash{ $oldkey};

If you want to apply an s///-operation to all keys of the hash, as seems
to be your case, this is one way:

for ( keys %hash ) {
my $oldkey = $_;
$hash{ $_} = delete $hash{ $oldkey} if s{^/mnt/cali5} {\$(TOPDIR)};
}

An existence test is, of course, not needed here. The test of s///
is there for cleanliness, and, perhaps, efficiency. Without it the
value would be re-assigned to the unchanged key.

Anno
 
T

Tad McClellan

Which will create a new hash element, you will also need to delete
the old hash element, and you can't do it while foreach-ing over the hash.

It would be much better if you could do the s/// while populating
the hash in the first place, but you haven't shown how you're
building the hash in your real code...

$prj_exports = {
'/mnt/cali5/epoc32/wins/c/nokia/sounds/Digital/Clock_alert.mid'
=> '"../data/Clock alert.mid"',


You should have written it with shorter line lengths for posting
(or at least disabled word-wrapping).


here is what I'd like to convert the hash above to:

$prj_exports = {
'$(TOPDIR)/epoc32/wins/c/nokia/sounds/Digital/Clock_alert.mid'
=> '"../data/Clock alert.mid"',



----------------------------
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

my $prj_exports = {
'/mnt/cali5/epoc32/wins/c/nokia/sounds/Digital/Clock_alert.mid'
=> '"../data/Clock alert.mid"',
'/mnt/cali5/tcf/cmmphonebookstoremesshandler.h'
=> '../inc/cmmphonebookstoremesshandler.h',
'/mnt/cali5/tcf/cmmphonebookstoreextinterface.h'
=> '../inc/cmmphonebookstoreextinterface.h'
};

my $TOPDIR = quotemeta '/mnt/cali5'; # \Q needed if regex metachars
# might be in the path, though
# there are none in this example

my @destinations = keys %$prj_exports; # don't modify hash while foreach-ing
foreach my $dest ( @destinations ) {
(my $newdest = $dest ) =~ s/$TOPDIR/\$\(TOPDIR)/; # make new key
$prj_exports->{$newdest} = $prj_exports->{$dest}; # copy value
delete $prj_exports->{$dest}; # goodbye old key/value
}

print Dumper $prj_exports;
 
G

gnari

A. Farber said:
"gnari" <[email protected]> wrote in message

Thanks, but that doesn't help me.
[snip example]

if I understand you correctly, you want

my @newkeys=@oldkeys=keys %$prj_exports;
s[^/mnt/cali5/][$(TOPDIR)/] for (@newkeys);
my $new_export;
@{$new_export}{@newkeys}=@{$prj_export}{@oldkeys};

your original post was confusing because your example inplied
duplicate key 'b' in result.

gnari
 
A

A. Farber

It would be much better if you could do the s/// while populating
the hash in the first place, but you haven't shown how you're
building the hash in your real code...

Thanks, that's what I've ended up with
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top