my %hash1 = (
a=>192.168.0.1,
b=>192.168.0.1,
c=>192.168.2.2,
d=>192.168.2.3,
e=>192.168.3.4,
f=>192.168.3.4
);
i have a perl hash like given above. keys are device names and values are ip addresses.How do i create a hash with no duplicate ip addresses (like %hash2) using %hash1? (devices that have same ips are removed)
my %hash2 = ( c=>192.168.2.2, d=>192.168.2.3 );
First of all you need to quote your IP addresses, because 192.168.0.1 is V-String in perl, means chr(192).chr(168).chr(0).chr(1).
And my variant is:
my %t;
$t{$_}++ for values %hash1; #count values
my #keys = grep
{ $t{ $hash1{ $_ } } == 1 }
keys %hash1; #find keys for slice
my %hash2;
#hash2{ #keys } = #hash1{ #keys }; #hash slice
How about:
my %hash1 = (
a=>'192.168.0.1',
b=>'192.168.0.1',
c=>'192.168.2.2',
d=>'192.168.2.3',
e=>'192.168.3.4',
f=>'192.168.3.4',
);
my (%seen, %out);
while( my ($k,$v) = each %hash1) {
if ($seen{$v}) {
delete $out{$seen{$v}};
} else {
$seen{$v} = $k;
$out{$k} = $v;
}
}
say Dumper\%out;
output:
$VAR1 = {
'c' => '192.168.2.2',
'd' => '192.168.2.3'
};
A solution using the CPAN module List::Pairwise:
use strict;
use warnings;
use List::Pairwise qw( grep_pairwise );
use Data::Dumper;
my %hash1 = (
a => '192.168.0.1',
b => '192.168.0.1',
c => '192.168.2.2',
d => '192.168.2.3',
e => '192.168.3.4',
f => '192.168.3.4'
);
my %count;
for my $ip ( values %hash1 ) { $count{ $ip }++ }
my %hash2 = grep_pairwise { $count{ $b } == 1 ? ( $a => $b ) : () } %hash1;
print Dumper \%hash2;
It's pretty straightforward. First you count the IPs in an auxiliary hash. And then you select only those IPs with a count of one using grep_pairwise from List::Pairwise. The syntax of grep_pairwise is like grep:
my #result = grep_pairwise { ... } #list;
The idea of grep_pairwise is to select the elements of #list two by two, with $a representing the first element of the pair, and $b the second (in this case the IP). (Remember that a hash evaluates to a list of ($key1, $value1, $key2, $value2, ...) pairs in list context).
Related
I have a series of strings for example
my #strings;
$strings[1] = 'foo/bar/some/more';
$strings[2] = 'also/some/stuff';
$strings[3] = 'this/can/have/way/too/many/substrings';
What I would like to do is to split these strings and store them in a hash as keys like this
my %hash;
$hash{foo}{bar}{some}{more} = 1;
$hash{also}{some}{stuff} = 1;
$hash{this}{can}{have}{way}{too}{many}{substrings} = 1;
I could go on and list my failed attempts, but I don't think they add to the value to the question, but I will mention one. Lets say I converted 'foo/bar/some/more' to '{foo}{bar}{some}{more}'. Could I somehow store that in a variable and do something like the following?
my $var = '{foo}{bar}{some}{more}';
$hash$var = 1;
NOTE: THIS DOESN'T WORK, but I hope it only doesn't due to a syntax error.
All help appreciated.
Identical logic to Shawn's answer. But I've hidden the clever hash-walking bit in a subroutine. And I've set the final value to 1 rather than an empty hash reference.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my #keys = qw(
foo/bar/some/more
also/some/stuff
this/can/have/way/too/many/substrings
);
my %hash;
for (#keys) {
multilevel(\%hash, $_);
}
say Dumper \%hash;
sub multilevel {
my ($hashref, $string) = #_;
my $curr_ref = $hashref;
my #strings = split m[/], $string;
for (#strings[0 .. $#strings - 1]) {
$curr_ref->{$_} //= {};
$curr_ref = $curr_ref->{$_};
}
$curr_ref->{#strings[-1]} = 1;
}
You have to use hash references to walk down thru the list of keys.
use Data::Dumper;
my %hash = ();
while( my $string = <DATA> ){
chomp $string;
my #keys = split /\//, $string;
my $hash_ref = \%hash;
for my $key ( #keys ){
$hash_ref->{$key} = {};
$hash_ref = $hash_ref->{$key};
}
}
say Dumper \%hash;
__DATA__
foo/bar/some/more
also/some/stuff
this/can/have/way/too/many/substrings
Just use a library.
use Data::Diver qw(DiveVal);
my #strings = (
undef,
'foo/bar/some/more',
'also/some/stuff',
'this/can/have/way/too/many/substrings',
);
my %hash;
for my $index (1..3) {
my $root = {};
DiveVal($root, split '/', $strings[$index]) = 1;
%hash = (%hash, %$root);
}
__END__
(
also => {some => {stuff => 1}},
foo => {bar => {some => {more => 1}}},
this => {can => {have => {way => {too => {many => {substrings => 1}}}}}},
)
I took the easy way out w/'eval':
use Data::Dumper;
%hash = ();
#strings = ( 'this/is/a/path', 'and/another/path', 'and/one/final/path' );
foreach ( #strings ) {
s/\//\}\{/g;
$str = '{' . $_ . '}'; # version 2: remove this line, and then
eval( "\$hash$str = 1;" ); # eval( "\$hash{$_} = 1;" );
}
print Dumper( %hash )."\n";
This question already has an answer here:
Create a multidimesional key of hash from array?
(1 answer)
Closed 5 years ago.
I have a set of keys stored in an array that I would like to splice into a nested hash. For example I might have:
$hash->{$key1} = $value;
And what I would like to do is add in additional dimensions to the hash, eg:
my #array = ( $key2, $key3 ) ;
to give
$hash->{$key1}->{$key2}->{$key3} = $value;
I do not know beforehand how many keys will be in the array.
Is this what you mean?
use strict;
use warnings 'all';
my #keys = qw/ a b c /;
my $val = 99;
my $hash = { };
{
my $h = $hash;
$h = $h->{ shift #keys } = {} while #keys > 1;
$h->{ shift #keys } = $val;
}
use Data::Dumper;
print Dumper $hash;
output
$VAR1 = {
'a' => {
'b' => {
'c' => 99
}
}
};
use Data::Diver qw( DiveVal );
DiveVal($hash, map \$_, $key1, #array) = $value;
-or-
DiveVal($hash->{$key1}, map \$_, #array) = $value;
or
sub dive_val :lvalue { my $p = \shift; $p = \( $$p->{$_} ) for #_; $$p }
dive_val($hash, $key1, #array) = $value;
-or-
dive_val($hash->{$key1}, #array) = $value;
I have three hashes that I would like to explode each one into a Hash of Arrays. Each one could be passed into a subroutine with a split function, but this requires the same subroutine to be called 3 times. I have tried to iterate through each hash and them split it but without success. Is it possible to do this without calling a subroutine? The code I have tried is:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %hash1 = (
A => "AAAAAAAAAA",
B => "AAAAANAAAA",
C => "AAAAAXAAAA",
D => "AAXAAAAAAA",
E => "AAAAAAAAAX",
);
my %hash2 = (
F => "BBBBBBBBBB",
G => "BBBBBBBBBB",
H => "BBXBBBBBBB",
I => "BBBBBBBBBB",
J => "AAAAANAAAA",
);
foreach my $ref ( \%hash1, \%hash2 ) {
while ( my ($key, $value) = each %$ref) {
#{ $ref->{$key} } = split (//, $value );
}
}
print Dumper %hash1;
but gives the warning:
Can't use string ("AAAAAAAAAA") as an ARRAY ref while "strict refs" in use
Thanks in advance.
Your mistake is that you are taking the value of the key $ref->{$key} which is AAAAAAA etc, and using it as a reference with the #{ ... } braces. You need to assign to $ref->{$key} directly.
Since you are splitting the original value into a list, you need an array to store it. You can do this either by using a named array, lexically scoped, or an anonymous array. My choice would be to use an anonymous array, but a named array might appear more readable:
foreach my $ref ( \%hash1, \%hash2 ) {
while ( my ($key, $value) = each %$ref) {
$ref->{$key} = [ split (//, $value ) ]; # using anonymous array
# my #list = split (//, $value );
# $ref->{$key} = \#list; # using named array
}
}
This process would overwrite the original values, but I assume that is what you want.
A more direct way to achieve the same thing would be this:
$_ = [ split // ] for values %hash1, values %hash2;
Here we are (ab)using the fact that the elements in a for loop are aliased to the original variables, and simply overwriting the values the same way we did above.
If the compressed format is unwanted, a more verbose alternative would be:
for my $value (values %hash1, values %hash2) {
$value = [ split //, $value ];
}
Assuming I correctly understood what you want to do, you can use hash slicing and do something like this:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my %hash1 = (
A => "AAAAAAAAAA",
B => "AAAAANAAAA",
C => "AAAAAXAAAA",
D => "AAXAAAAAAA",
E => "AAAAAAAAAX",
);
my %hash2 = (
F => "BBBBBBBBBB",
G => "BBBBBBBBBB",
H => "BBXBBBBBBB",
I => "BBBBBBBBBB",
J => "AAAAANAAAA",
);
foreach my $ref ( \%hash1, \%hash2 ) {
my #keys = keys %$ref;
#$ref{#keys} = map { [split //, $ref->{$_}] } #keys;
}
print Dumper \%hash1;
print Dumper \%hash2;
This is like writing:
foreach my $ref ( \%hash1, \%hash2 ) {
my #keys = keys %$ref;
foreach my $key (#keys) {
my #arr = split //, $ref->{$key};
$ref->{$key} = \#arr;
}
}
just simpler.
Does anyone know how to make a hash with pairs of strings serving as keys in perl?
Something like...
{
($key1, $key2) => $value1;
($key1, $key3) => $value2;
($key2, $key3) => $value3;
etc....
You can't have a pair of scalars as a hash key, but you can make a multilevel hash:
my %hash;
$hash{$key1}{$key2} = $value1;
$hash{$key1}{$key3} = $value2;
$hash{$key2}{$key3} = $value3;
If you want to define it all at once:
my %hash = ( $key1 => { $key2 => $value1, $key3 => $value2 },
$key2 => { $key3 => $value3 } );
Alternatively, if it works for your situation, you could just concatenate your keys together
$hash{$key1 . $key2} = $value1; # etc
Or add a delimiter to separate the keys:
$hash{"$key1:$key2"} = $value1; # etc
You could use an invisible separator to join the coordinates:
Primarily for mathematics, the Invisible Separator (U+2063) provides a separator between characters where punctuation or space may be omitted such as in a two-dimensional index like ij.
#!/usr/bin/env perl
use utf8;
use v5.12;
use strict;
use warnings;
use warnings qw(FATAL utf8);
use open qw(:std :utf8);
use charnames qw(:full :short);
use YAML;
my %sparse_matrix = (
mk_key(34,56) => -1,
mk_key(1200,11) => 1,
);
print Dump \%sparse_matrix;
sub mk_key { join("\N{INVISIBLE SEPARATOR}", #_) }
sub mk_vec { map [split "\N{INVISIBLE SEPARATOR}"], #_ }
~/tmp> perl mm.pl |xxd
0000000: 2d2d 2d0a 3132 3030 e281 a331 313a 2031 ---.1200...11: 1
0000010: 0a33 34e2 81a3 3536 3a20 2d31 0a .34...56: -1.
Usage: Multiple keys of a single value in a hash can be used for implementing a 2D matrix or N-dimensional matrix!
#!/usr/bin/perl -w
use warnings;
use strict;
use Data::Dumper;
my %hash = ();
my ($a, $b, $c) = (2,3,4);
$hash{"$a, $b ,$c"} = 1;
$hash{"$b, $c ,$a"} = 1;
foreach(keys(%hash) )
{
my #a = split(/,/, $_);
print Dumper(#a);
}
I do this:
{ "$key1\x1F$key2" => $value, ... }
Usually with a helper method:
sub getKey() {
return join( "\x1F", #_ );
}
{ getKey( $key1, $key2 ) => $value, ... }
----- EDIT -----
Updated the code above to use the ASCII Unit Separator per the recommendation from #chepner above
Use $; implicitly (or explicitly) in your hash keys, used for multidimensional emulation, like so:
my %hash;
$hash{$key1, $key2} = $value; # or %hash = ( $key1.$;.$key2 => $value );
print $hash{$key1, $key2} # returns $value
You can even set $; to \x1F if needed (the default is \034, from SUBSEP in awk):
local $; = "\x1F";
I am looking for search implementation on hash using perl. I have following data in my hash
%hash = {0 => "Hello", 1=> "world"}.
Now i want to search the hash using the values (means world and hello) and return corresponding key.
Example: I want to search for world and the result should be 1
Iterate of the keys of the hash with a for ( keys %hash ) ... statement and check the values as you go. If you find what you are looking for, return
my $hash = { 0 => "World", 1 => "Hello" };
for ( keys %$hash ) {
my $val = $hash->{$_};
return $_ if $val eq 'World'; # or whatever you are looking for
}
another option would be to use while ( ... each ... )
my $hash = { 0 => "World", 1 => "Hello" };
while (($key, $val) = each %$hash) {
return $key if $val eq 'World'; # or whatever you are looking for
}
the use of { } literal creates a hash reference and not a hash
$h = { a => 'b', c => 'd' };
to create a literal hash you use ( )
%h = ( a => 'b', c => 'd' );
execution of while ... each on hashref
$h = { a => 'b', c => 'd' };
print "$k :: $v\n" while (($k, $v) = each %$h );
c :: d
a :: b
If:
The hash isn't very large, and
The values are unique
You can simply create a lookup hash with reverse:
my %lookup = reverse %hash;
my $key = $lookup{'world'}; # key from %hash or undef
use strict;
use warnings;
my %hash = (0 => "Hello", 1=> "world");
my $val = 'world';
my #keys = grep { $hash{$_} eq $val } keys %hash;
print "Keys: ", join(", ", #keys), "\n";
This will return all keys i.e. If the value is same for multiple keys.