thanos1983 has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks,

I am trying to split a string based on white space elements and assign key value to a hash.

That would be easy if key value where even and not odd.

Sample of code:

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my $str = "one 1 two 2 three 3 odd_element"; my %hash = split / /, $str; print Dumper \%hash;

Sample of output:

Odd number of elements in hash assignment at test.pl line 7. $VAR1 = { 'three' => '3', 'one' => '1', 'odd_element' => undef, 'two' => '2' };

So I tried to split it into an array and the convert the array to a hash (overkill for no reason), but it gives the possibility to play with the array elements before assign it to hash.

Sample of code:

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my %hash; my $str = "one 1 two 2 three 3 odd_element"; my @array = split / /, $str; while (@array) { my $key = shift @array; my $value = shift @array; if (defined $key && defined $value) { $hash{$key} = $value; } else { $hash{$key} = undef; } } print Dumper \%hash;

Sample of output:

$VAR1 = { 'odd_element' => undef, 'one' => '1', 'two' => '2', 'three' => '3' };

It is working fine but there must be some internal function or another way to over come this overkill process.

So the question, is there any better way that I should come up with?

Thanks in advance everyone for their time and effort.

Seeking for Perl wisdom...on the process of learning...not there...yet!

Replies are listed 'Best First'.
Re: How to split a non even number of string elements into a hash
by Athanasius (Archbishop) on Feb 09, 2017 at 12:45 UTC

    Hello thanos1983,

    How about this?

    use strict; use warnings; use Data::Dumper; my $string = "one 1 two 2 three 3 odd_element"; my @elements = split / /, $string; push @elements, undef if @elements % 2; my %hash = @elements; print Dumper \%hash;

    Output:

    22:41 >perl 1750_SoPW.pl $VAR1 = { 'three' => '3', 'odd_element' => undef, 'two' => '2', 'one' => '1' }; 22:42 >

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Or even
      my @elements = split / /, $string; my %hash = (@elements, (undef) x (@elements % 2));

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      Hello Athanasius

      That is exactly what I was not able to come up with. Sort simple easy to follow.

      Thanks again for your time and effort.

      Seeking for Perl wisdom...on the process of learning...not there...yet!
Re: How to split a non even number of string elements into a hash
by Eily (Monsignor) on Feb 09, 2017 at 12:58 UTC

    One other way to do it, which is to me a little more explicit, is to use m// in list context. This way you explicitly read your string two elements at a time. Since you will have as many elements in the return list as there are capture groups, even if they don't match, you can be sure to obtain an even sized list:

    use Data::Dumper; my $string = "one 1 two 2 three 3 odd"; my %hash = $string =~ / (\w+) # one captured word (?: # followed by \s(\d+) # a space and a captured number )? # in an optional non capturing gro +up /gx; print Dumper \%hash; __DATA__ $VAR1 = { 'three' => '3', 'two' => '2', 'one' => '1', 'odd' => undef };
    Edit: added the output

      This approach has the advantage that it can, assuming the right constraints, handle an unpaired key at any position in the input string:


      Give a man a fish:  <%-{-{-{-<

      Hello Eily

      This is not a bad idea, but in cases that you will not need to parse a string beyond two elements at a time.

      I will keep that in mind for possible further implementations in future.

      Thank you for your time and effort reading and replying to my question.

      Seeking for Perl wisdom...on the process of learning...not there...yet!

        If the answer Athanasius gave you is easier to understand for you, it's the one you should use. Writing code that you can understand and modify by yourself should be your priority. But if you have trouble understanding what I did in my answer and are curious, feel free to ask for further details.

        This is not a bad idea, but in cases that you will not need to parse a string beyond two elements at a time.

        I don't understand this. Can you please explain "cases that you will not need to parse a string beyond two elements at a time"?


        Give a man a fish:  <%-{-{-{-<

Re: How to split a non even number of string elements into a hash - update
by Discipulus (Canon) on Feb 09, 2017 at 12:43 UTC
    hello thanos1983,

    From my point of view what you are trying to achieve is simply wrong: hash are built of key - value pairs (where pair imply evenly) so why have something to over come something that must be true by definition?

    To populate an hash you must provide even elements and if there are possibilities that elements are odd you must workaround and end with an even list, as you are doing in the second example.

    PS: note also that the operation is always risky: the odd element must be the last one or the hash will be messed up.

    PPS if you want to ignore odd element you can do somthing like this:

    perl -MData::Dumper -we "while(my $k=shift @ARGV and $v = shift @ARGV +){$h{$k}=$v};print Dumper \ %h" 1 a 2 b ODD $VAR1 = { '1' => 'a', '2' => 'b' };

    OR play a trick with hash keys and scope to include a key with an undef value:

    perl -MData::Dumper -we "my $k; while($k=shift @ARGV and $v = shift @A +RGV ){$h{$k}=$v};$h{$k}= undef unless exists $h{$k}; print Dumper \%h + " 1 a 2 b ODD $VAR1 = { '1' => 'a', 'ODD' => undef, '2' => 'b' };

    the above was wrong, this no:

    perl -MData::Dumper -e "while($k=shift @ARGV and $v = shift @ARGV ){$h +{$k}=$v};if($k and !$v){$h{$k}=undef}print Dumper \%h " 1 a 2 b ODD $VAR1 = { '1' => 'a', 'ODD' => undef, '2' => 'b' };

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

      Hello Discipulus

      Thanks again for the time and effort. I agree with you, in theory it is not correct to have odd number of keys and values in a hash, but some times you have to bend the rules. :D

      I just want to be sure that I am capturing all the possibilities in my script that all elements even odd ones will be captured and displayed in my hash. But yes I agree with you I should not "normally" have odd elements or at least process them with hash. But for my point of view it just makes it such easier to play around with them when all data are stored in a hash. :D

      Thanks for the sample of code that you provided me with.

      Seeking for Perl wisdom...on the process of learning...not there...yet!
Re: How to split a non even number of string elements into a hash
by hippo (Archbishop) on Feb 09, 2017 at 12:49 UTC

    If you don't want the warning, just silence it:

    #!/usr/bin/perl use strict; use warnings; no warnings 'misc'; use Data::Dumper; my $str = "one 1 two 2 three 3 odd_element"; my %hash = split / /, $str; print Dumper \%hash;

    You can (and should), limit the scope of such silencing further.

    This isn't to say that your initial situation doesn't smack of some deeper problem which maybe ought to be addressed.

      Hello hippo

      This is not a bad idea, but at the same time for my point of view not a good one. :D

      Do not take me wrong, but I do not really want to silence warnings. There are there for some reason, even if you silence them temporarily and you enable them after.

      But on the other hand in some rare cases it could be the only solution. In this case since the are work around solutions I would prefer to apply alternative solutions.

      Thank you for your time and effort reading ans replying to my question though.

      Seeking for Perl wisdom...on the process of learning...not there...yet!
        ... I do not really want to silence warnings. There are there for some reason, even if you silence them temporarily and you enable them after.

        And, of course, the reason in this case is to warn you about an unpaired key in a set of key/value pairs. But you want to accept an unpaired key, so if the oddball can only be at the end of the input string and if its default value is undef:

        c:\@Work\Perl\monks>perl -le "use strict; use warnings; use Data::Dumper; ;; my %hash; ;; my $str = 'one 1 two 2 three 3 odd_element'; { no warnings 'misc'; %hash = split / /, $str; } print Dumper \%hash; " $VAR1 = { 'odd_element' => undef, 'three' => '3', 'one' => '1', 'two' => '2' };
        and you're done. KISS.

        Update: Actually, the following is even KISSier and less messy:

        my %hash = do { no warnings 'misc'; split / /, $str; };
        I don't know why I didn't use it to begin with.


        Give a man a fish:  <%-{-{-{-<

Re: How to split a non even number of string elements into a hash [RESOLVED]
by haukex (Archbishop) on Feb 09, 2017 at 16:06 UTC

    Hi thanos1983,

    In the spirit of TIMTOWTDI: just yesterday I published some code that uses m/\G.../gc to incrementally parse a string into a hash. The goal of that code is to parse a string like "RH= 23.8 %RH T= 20.4 'C Td= -1.0 'C Tdf=-0.9 'C a=  4.2 g/m3   x=   3.5 g/kg  Tw= 10.2 'C ppm=  5635 pw=   5.68 hPa pws=  23.90 hPa h=  29.5 kJ/kg" (a line of data from a sensor I have to parse) into a data structure like { RH => { val=>23.8, unit=>"%RH" }, ... }. Although this is probably overkill for your case, note how it does allow for more powerful parsing of the input - in the aforementioned example, note how "ppm=  5635" doesn't have units, and how a simple split on whitespace won't work for "Tdf=-0.9".

    If I apply the same principle to your code (slightly simplified):

    use warnings; use strict; my $input = "one 1 two 2 three 3 odd_element"; my $REGEX = qr{ \s* \b (\w+) \s+ (\w+) \b \s* }msx; my %output; pos($input)=undef; while ($input=~/\G$REGEX/gc) { $output{$1} = $2; } my $rest = substr $input, pos $input; if (length $rest) { # leftovers $output{$rest} = 'turkeysandwich'; } use Data::Dumper; print Dumper(\%output); __END__ $VAR1 = { 'one' => '1', 'two' => '2', 'three' => '3', 'odd_element' => 'turkeysandwich' };

    (Note: Assigning to pos is not needed in the example above, but it may become necessary if the code is embedded into a larger script that performs other regexes on the input that may modify pos.)

    Hope this helps,
    -- Hauke D

Re: How to split a non even number of string elements into a hash [RESOLVED]
by kcott (Archbishop) on Feb 10, 2017 at 07:33 UTC

    G'day thanos1983,

    Instead of using intermediary variables, you could do this:

    my %hash = ((split / /, $str), $str =~ y/ / / % 2 ? () : undef);

    I tested this on the command line. Sorry about the wrapping.

    $ perl -wMstrict -MData::Dump -e 'my $s = "a 1 b 2 c 3 Odd"; my %h = ( +(split / /, $s), $s =~ y/ / / % 2 ? () : undef); dd \%h' { a => 1, b => 2, c => 3, Odd => undef } $ perl -wMstrict -MData::Dump -e 'my $s = "a 1 b 2 c 3 Even 4"; my %h += ((split / /, $s), $s =~ y/ / / % 2 ? () : undef); dd \%h' { a => 1, b => 2, c => 3, Even => 4 }

    See "perlop: Quote-Like Operators" if you're unfamiliar with using y/// for counting.

    — Ken

      Hello kcott,

      Not a bad idea, I never thought about combining some many functions together also.

      I will keep that in mind thank you for your time and effort.

      Seeking for Perl wisdom...on the process of learning...not there...yet!
Re: How to split a non even number of string elements into a hash [RESOLVED]
by Anonymous Monk on Feb 09, 2017 at 19:04 UTC
    TMTOWTDI, although certainly not best way. :P
    my $str = "one 1 two 2 three 3 odd_element"; my $cnt = () = $str =~ /(\s)/g; # count spaces my %hash = split /\s/, $str, $cnt + 1; # split on spaces + 1

      Hello Anonymous Monk,

      Some times even the worst solutions can be proven great if they work.

      Maybe not the best but another way to resolve it. :D Thanks for your time and effort.

      Seeking for Perl wisdom...on the process of learning...not there...yet!