Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

map a list

by evil_otto (Novice)
on Mar 12, 2007 at 18:49 UTC ( [id://604413]=perlquestion: print w/replies, xml ) Need Help??

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

How can I map a list (a,b) into (a,fn(b)) in the context of a ternary operator?

Here's my full problem: I have a string like "a,b=c,e=#f" - comma separated terms, where each is of the form "x", "x=y", or "x=#y". I need to parse this string into a list (really, a hash), where "x" becomes x=>fn(x), "x=y" becomes x=>y, and x=#y becomes x=>fn(y). The silly part is that I'd like to do it as a ternary operator, so I can assign it without needing to put an empty declaration of the variable outside an if block.

The first two parts, x=>fn(x) and x=>y are easy:

@result=map { /=/ ? split(/=/,$_,2) : ($_,fn($_)) } split(/,/, $string);
Adding in the x=>fn(y) case is where I'm having trouble; my first attempt:
@result=map { /=#/? (@l=split(/=#/,$_,2) && ($l[0],fn($l[1])) : /=/ ? split(/=/,$_,2) : ($_,fn($_)) } split(/,/, $string);

didn't quite work, and in the course of trying to debug the first branch of the ternary I got segfaults from perl:

$ perl -e ' (@n=(1,2) && ($n[1],$n[0])); (@n=(1,2) && ($n[1],$n[0])); (@n=(1,2) && ($n[1],$n[0])); ' Segmentation fault

Is this just perl trying to tell me that this is a stupid way to go about this?

Replies are listed 'Best First'.
Re: map a list
by almut (Canon) on Mar 12, 2007 at 19:27 UTC

    If you really want to write it that way, I think you'd need to use do {...}, e.g. something like this

    my $s = "a,b=c,e=#f"; sub fn { "fn(@_)" } # dummy function my %result = map { /=#/ ? do { my @l = split(/=#/,$_,2); ( $l[0], fn($l[1]) ) } : /=/ ? split(/=/,$_,2) : ($_,fn($_)) } split(/,/, $s); use Data::Dumper; print "$s\n"; print Dumper \%result;

    Output:

    a,b=c,e=#f $VAR1 = { 'e' => 'fn(f)', 'a' => 'fn(a)', 'b' => 'c' };
      This is exactly what my brain was trying to do the first time around, I just couldn't think of using a "do" block (I was thinking of the c comma operator, which obviously does something quite different in perl list context)
Re: map a list
by dragonchild (Archbishop) on Mar 12, 2007 at 19:08 UTC
    my @accumulator; foreach my $item ( split ',', $string ) { my @elems = split '=', $item; if ( @elems == 1 ) { push @accumulator, "fn($item)"; } else { if ( $elems[1].substr(0,1) eq '#' ) { push @accumulator, $elems[0] . '=>fn(' . $elems[1] . ')'; } else { push @accumulator, "$elems[0]=>$elems[1]"; } } }

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: map a list - segfault
by imp (Priest) on Mar 12, 2007 at 20:00 UTC
    The block that caused the segfault doesn't work the way you think it does, because of precedence:
    perl -MO=Deparse -e ' (@n=(1,2) && ($n[1],$n[0])); (@n=(1,2) && ($n[1],$n[0])); (@n=(1,2) && ($n[1],$n[0])); ' @n = ('???', 2) && ($n[1], $n[0]); @n = ('???', 2) && ($n[1], $n[0]); @n = ('???', 2) && ($n[1], $n[0]);
    Using 'and' instead of && would work, or an extra set of parens.
    perl -MO=Deparse -e ' (@n=(1,2) && ($n[1],$n[0])); ((@n=(1,2)) && ($n[1],$n[0])); (@n=(1,2) and ($n[1],$n[0])); ' @n = ('???', 2) && ($n[1], $n[0]); $n[1], $n[0] if @n = (1, 2); $n[1], $n[0] if @n = (1, 2);

    As for the segfault itself, I don't grok why it breaks but Devel::Peek gives a hint:

    perl -MDevel::Peek -le ' (@n=(1,2) && ($n[1],$n[0])); print Dump \@n; (@n=(1,2) && ($n[1],$n[0])); print Dump \@n;' SV = RV(0x3c02b018) at 0x3c0062dc REFCNT = 1 FLAGS = (TEMP,ROK) RV = 0x3c011718 SV = PVAV(0x3c0084dc) at 0x3c011718 REFCNT = 2 FLAGS = () IV = 0 NV = 0 ARRAY = 0x3c02a4f0 FILL = 1 MAX = 3 ARYLEN = 0x0 FLAGS = (REAL) Elt No. 0 SV = NULL(0x0) at 0x3c006168 REFCNT = 1 FLAGS = () Elt No. 1 SV = NULL(0x0) at 0x3c006264 REFCNT = 1 FLAGS = () SV = RV(0x3c02b018) at 0x3c0062dc REFCNT = 1 FLAGS = (TEMP,ROK) RV = 0x3c011718 SV = PVAV(0x3c0084dc) at 0x3c011718 REFCNT = 2 FLAGS = () IV = 0 NV = 0 ARRAY = 0x3c02a4f0 FILL = 1 MAX = 3 ARYLEN = 0x0 FLAGS = (REAL) Elt No. 0 SV = UNKNOWN(0xff) (0x0) at 0x3c006168 REFCNT = 1 FLAGS = () Elt No. 1 SV = UNKNOWN(0xff) (0x0) at 0x3c006264 REFCNT = 1 FLAGS = ()
    Note that the array entries changed from :
    SV = NULL(0x0) at 0x3c006168
    To:
    SV = UNKNOWN(0xff) (0x0) at 0x3c006168
Re: map a list
by GrandFather (Saint) on Mar 12, 2007 at 19:19 UTC
    use strict; use warnings; my $str = "a,b=c,e=#f"; my @result = map { /(\w+)(=(#?)(\w+))?/; defined $3 && $3 eq '#' ? "$1=>fn($4)" : defined $2 ? "$1=>$4" : "$1=>fn($1)" } split ',', $str; print "@result";

    Prints:

    a=>fn(a) b=>c e=>fn(f)

    DWIM is Perl's answer to Gödel

        Argh, I did know better, I just forgot. However that's not a style I'd recommend for practical code in any case - it would be a dog to maintain and is somewhat obscure. Inserting die if ! in front of the regex might suit the OP's purpose.


        DWIM is Perl's answer to Gödel
      Ah! While this answer is not ideal (it doesn't preserve the case : action style of the original, which makes it more straightforward to add in another syntax when that becomes necessary), it did provide the insight for achieving wisdom: since I'm already matching the expression with a RE, I can simply use the work already done and avoid re-splitting. So:
      my @result=map { /(\w+)=#(.+)/ ? ($1,fn($2)) : /(\w+)=(.+)/ ? ($1, $2) : ($_, fn($_)) } split(/,/, $str);
      Thanks.

      Still curious about that segfault tho...

        Depending on what the larger problem is, you may be better looking at something like Parse::RecDescent. Even if you stick to manual parsing, using a more explicit coding structure as shown in dragonchild's example would pay off in terms of maintenance.


        DWIM is Perl's answer to Gödel
Re: map a list
by davidrw (Prior) on Mar 12, 2007 at 20:09 UTC
    I'm a fan of the ternary operator, but i think an approach like this would be clearer:
    # LHS = Left-hand side # RHS = Right-hand side my $s = 'a,b=c,e=#f'; my %h = map { $_ .= "=fn($_)" unless /=/; # add a RHS s/#(\w+)/fn($1)/; # change the RHS '#' notation to the ' +fn' notation split( /=/, $_, 2 ); # return the LHS=>RHS mapping (delim'd + by a '=') } split /,/, $s; # split on commas to get the strings for the pai +rs
Re: map a list
by BrowserUk (Patriarch) on Mar 12, 2007 at 22:13 UTC

    I think that I'd transform the string before splitting it:

    #! >perl -slw use strict; use Data::Dumper; $_ = 'a,b=c,e=#f'; s[(\w+)=#(\w+)][$1=>fn($2)]g; s[(\w+)=(\w+)][$1=>$2]g; s[(?<!=>)(\w+),][$1=>fn($1),]g; my %results = split ',|=>'; print Dumper \%results; __END__ C:\test>junk8 $VAR1 = { 'e' => 'fn(f)', 'a' => 'fn(a)', 'b' => 'c' };

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: map a list
by ikegami (Patriarch) on Mar 12, 2007 at 21:23 UTC
    The conditional operator (?:) usually makes code less readable, especially when nested.
    my %results = map { my ($x, $y) = split(/=/, $_, 2); if (defined($y) && $y =~ /^#(.*)/) { $y = fn($1); } elsif (!defined($y)) { $y = fn($x); } ($x, $y) } split(/,/, $string);

    If you wanted terse:

    my %results = map { /^([^=]*)(?:(=)(#)?(.*))?/s; ($1, ($3 ? fn($4) : ($2 ? $4 : fn($1)))) } split(/,/, $string);

    Update: Added terse snippet.

Re: map a list
by johngg (Canon) on Mar 12, 2007 at 21:58 UTC
    I don't think this is the best way to solve the problem but, instead of ternaries, I thought I'd have a go with regex conditionals. It also uses an on-the-fly subroutine method suggested by almut a couple of months ago here.

    use strict; use warnings; my $str = q{a,b=c,e=#f,ghi}; my @result = map { my $res; m {(?x) ^ (?(?=([a-z])$) (?{(sub {($res) = @_})->(qq{$1=>fn($1)})}) | (?(?=([a-z])=([a-z])$) (?{(sub {($res) = @_})->(qq{$2=>$3})}) | (?(?=([a-z])=\#([a-z])$) (?{(sub {($res) = @_})->(qq{$4=>fn($5)})}) | (?{(sub {($res) = @_})->(q{???})}) ) ) ) }; $res } split m{,}, $str; print qq{@result\n};

    It prints

    a=>fn(a) b=>c e=>fn(f) ???

    I hope this is of interest.

    Cheers,

    JohnGG

      You can simplify your code a lot by making $res a package variable.
      my @result = map { local our $res; m {(?x) ^ (?(?=([a-z])$) (?{ $res = qq{$1=>fn($1)}; }) | (?(?=([a-z])=([a-z])$) (?{ $res = qq{$2=>$3}; }) | (?(?=([a-z])=\#([a-z])$) (?{ $res = qq{$4=>fn($5)}; }) | (?{ $res = q{???}; }) ) ) ) }; $res } split m{,}, $str;

      Of course, the above is just an obfuscated way of doing

      my @result = map { (/([a-z])$/ ? qq{$1=>fn($2)} : (/([a-z])=([a-z])$/ ? qq{$1=>$2} : (/([a-z])=\#([a-z])$/ ? qq{$1=>fn($2)} : q{???} ) ) ) } split m{,}, $str;

      By no means do I consider the second snippet acceptable either.

      By the way, why did you introduce limits on what the values can be? Nowhere did the OP imply the values would be single lowercase letters. If you remove that arbitrary limit, you can get rid of the "???" case.

      Why did you also changed the output, returning a string where a pair of values should have been returned?

Re: map a list
by Anno (Deacon) on Mar 12, 2007 at 20:14 UTC
    I'm not entirely sure if I understand your Problem. It is hard to tell when you are talking in terms of text replacement and when you mean actual Perl code. I believe that dragonchild has throroughly misunderstood the problem for the same reason.

    Here is one way to do what I think you want to do:

    @result = map { my ( $x, $y) = split /=#?/; /=#/ ? ( $x, fn( $y)) : /=/ ? ( $x, $y) : ( $x, fn( $x)); } split /,/, $string;
    See how the code makes the map block a comfortable place, with named lexicals for the parts we work with. The nested ?: can then be written strightforward.

    I think an approach more along the lines of dragonchild's (though he solved different problem) would be more readable and maintainable.

    Anno

    Update: Corrected misattribution to imp

      Perhaps it was a bit unclear, in that I didn't specify that "fn()" is a perl sub defined elsewhere. Otherwise there is pretty much nothing to do with any text substitution - I just figured that much was clear from the working reduced case (and the non-working full case).

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://604413]
Approved by marto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (6)
As of 2024-04-19 09:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found