Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

How to call a function on each item after a split?

by MrSnrub (Beadle)
on Oct 08, 2012 at 14:40 UTC ( [id://997827]=perlquestion: print w/replies, xml ) Need Help??

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

Suppose I have the following code:
#!/usr/bin/perl use strict; use warnings; my $str = "item1 | item2| item3 |item4| "; my ($item1, $item2, $item3, $item4, $item5) = split(/\|/, $str); print "After split:\nitem1: '$item1'\nitem2: '$item2'\nitem3: '$item3' +\nitem4: '$item4'\nitem5: '$item5'\n"; clean($item1); clean($item2); clean($item3); clean($item4); clean($item5); print "After clean:\nitem1: '$item1'\nitem2: '$item2'\nitem3: '$item3' +\nitem4: '$item4'\nitem5: '$item5'\n"; exit(0); sub clean { chomp($_[0]); $_[0] =~ s/^\s+//; $_[0] =~ s/\s+$//; }
I don't want to have to call the clean() subroutine on every item returned in the split. Suppose instead of five items I had 20. Do I have to call clean($item1);, clean($item2);, ... , clean($item19);, clean($item20);? My code would look quite long. Can't I just use a for loop somehow instead?

Replies are listed 'Best First'.
Re: How to call a function on each item after a split?
by aitap (Curate) on Oct 08, 2012 at 14:56 UTC
    Use map: my @cleaned = map { clean($_) } split( /\|/, $str );
    Sorry if my advice was wrong.

      This solution doesn't work since map assigns the return value of clean() to the array. clean() will return the result of the last line which is either 1 or '' for this string. You can make it work by adding $_ in the map block. Not sure if the op wanted the empty final value but they provided a variable for it.

      #!/usr/bin/perl use strict; use warnings; my $str = "item1 | item2| item3 |item4| "; my @cleaned = map { clean($_) } split( /\|/, $str ); print ">$_<\n" foreach @cleaned; print "*"x75,"\n"; my @cleaned2 = map { clean($_);$_ } split( /\|/, $str ); print ">$_<\n" foreach @cleaned2; sub clean { chomp($_[0]); $_[0] =~ s/^\s+//; $_[0] =~ s/\s+$//; } >1< >< >1< >< >< ********************************************************************** +***** >item1< >item2< >item3< >item4< ><

        Thanks for that, I surely should have thought of it.

        Sorry if my advice was wrong.
      Yeah. More elegant and Perlish than mine.

      I'm too lazy to be proud of being impatient.
        Why is it better to use the map?
Re: How to call a function on each item after a split?
by nemesdani (Friar) on Oct 08, 2012 at 14:46 UTC
    Use an array for the items:
    my @items = split(/\|/, $str); foreach my $item(@items){ ...#whatever }


    I'm too lazy to be proud of being impatient.
Re: How to call a function on each item after a split?
by Kenosis (Priest) on Oct 08, 2012 at 15:56 UTC

    Given your data set, another option is to use a regex to grab the items you want out of the string. Consider the following:

    use strict; use warnings; my $str = "item1 | item2| item3 |item4|"; my @items = $str =~ /\w+/g; print "$_\n" for @items;

    Output:

    item1 item2 item3 item4

    Hope this helps!

Re: How to call a function on each item after a split?
by Lotus1 (Vicar) on Oct 08, 2012 at 15:55 UTC

    The split function can get rid of the white space also.

    #!/usr/bin/perl use strict; use warnings; my $str = "item1 | item2| item3 |item4| "; print ">$_<\n" foreach split /\s*\|\s*/, $str;
Re: How to call a function on each item after a split?
by CountZero (Bishop) on Oct 08, 2012 at 15:59 UTC
    Although using an array is usually a better solution, it can be done as follows:
    my ($item1, $item2, $item3, $item4, $item5) = map {clean($_)} split(/\ +|/, $str);

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

    My blog: Imperial Deltronics

      Actually your code assigns the return values from clean() which are either 1 or the empty string. The function uses the side effect of changing $_ but could be modified to return the trimmed string instead.

        Oops, my bad. I am so used to writing subs that return a meaningful result rather than do an in-place action-at-a-distance. MrSnrub has the right solution: just return $_ in the map.

        CountZero

        A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

        My blog: Imperial Deltronics
Re: How to call a function on each item after a split?
by 2teez (Vicar) on Oct 08, 2012 at 21:40 UTC
    Hi,

    Please, before one shutdown the wonderful solution first given by aitap above on this issue,
    Or wonder why "the" lovely map function doesn't do the "magic" as expected, Please, check the OP subroutine clean().

    sub clean { chomp($_[0]); $_[0] =~ s/^\s+//g; $_[0] =~ s/\s+$//g; }
    Why use chomp and then sepeartely, remove spaces? At the beginning and endling of each value?
    I thought, \s+ also covers \n that chomp is suppose to remove?
    I modified the OP clean subroutine and both map and grep gave the happy endling.
    So, there is really no need for the extra $_ as perviously stated, if the modified subroutine is considered.
    check below:
    #!/usr/bin/perl use strict; use warnings; my $str = "item1 | item2| item3 |item4| "; my @cleaned1 = map { clean_modify($_) } split( /\|/, $str ); my @cleaned2 = grep { clean_modify($_) } split( /\|/, $str ); print join "\n", @cleaned1; print join "\n", @cleaned2; sub clean_modify { $_[0] =~ s/^\s+?|\s+?$//g; return $_[0]; } sub clean { ## don't use chomp( $_[0] ); $_[0] =~ s/^\s+//g; $_[0] =~ s/\s+$//g; }
    OUTPUT
    item1 item2 item3 item4 item1 item2 item3 item4

    If you tell me, I'll forget.
    If you show me, I'll remember.
    if you involve me, I'll understand.
    --- Author unknown to me

      Your demonstration of using grep like that only works if the function modifies $_. Your example makes it seem that map and grep are equivalent which they aren't. grep looks at the return value in the block to determine whether or not to pass along $_ while map passes along the return value in the block. Big difference. If one of the values in the string happens to be '0' then grep doesn't pass that value through. Also your solution doesn't pass along the final empty string like the one with the map{ clean($_);$_ } solution did.

      #!/usr/bin/perl use strict; use warnings; my $str = "item1 | 0 | | item2| item3 |item4 | "; my @cleaned1 = map { clean_no_side_effects($_) } split( /\|/, $str ); my @cleaned2 = grep { clean_no_side_effects($_) } split( /\|/, $str ); print "*"x25, "\n"; print join "\n", @cleaned1; print "*"x25, "\n"; print join "\n", @cleaned2; print "*"x25, "\n"; sub clean_no_side_effects { my $string = shift; $string =~ s/^\s+|\s+$//g; return $string; } sub clean_modify { ## don't use, still using side effects for grep t +o work $_[0] =~ s/^\s+?|\s+?$//g; # '?' isn't needed here since \s neve +r matches '|' return $_[0]; } sub clean { ## don't use chomp( $_[0] ); $_[0] =~ s/^\s+//g; $_[0] =~ s/\s+$//g; } __END__ ************************* item1 0 item2 item3 item4 ************************* item1 item2 item3 item4 *************************
Re: How to call a function on each item after a split?
by MrSnrub (Beadle) on Oct 08, 2012 at 20:40 UTC
    So, this line:

    my ($item1, $item2, $item3, $item4, $item5) = map { clean($_); } split(/\|/, $str);

    makes each item either "1" or "" based on (I guess) whether there's empty space between the "item" string and the pipe symbol.

    However, this line:

    my ($item1, $item2, $item3, $item4, $item5) = map { clean($_); $_; } split(/\|/, $str);

    ...does EXACTLY what I want in only one line, no matter how many items I have. Many thanks! Question, though: I don't exactly see what is going on with the added $_;. It's not returning a value, since this is not a subroutine. It's not changing the value of $_;. I guess what it does is it tells map to return $_ as opposed to the return value of the previous statement. Is that accurate?

      map aliases $_ to each item in turn in the list it is dealing with. If you edit $_ in the map the original list items are edited. This can be an unwanted and nasty side effect. Note that aliasing in exactly the same way happens in Perl for loops.

      Your clean sub doesn't return the cleaned string. It edits the passed in string in place (Perl essentially passes aliases of parameters into subs). Because the doesn't return the cleaned string you need to "return" the edited string from map, hence the '; $_;' bit in the map.

      In this case a cleaner solution is to trim the white space in the split:

      my $str = "item1 | item2| item3 |item4| "; print ">$_<\n" for split /\s*\|\s*/, $str, -1;

      Prints:

      >item1< >item2< >item3< >item4< ><
      True laziness is hard work
Re: How to call a function on each item after a split?
by aaron_baugher (Curate) on Oct 09, 2012 at 03:41 UTC

    You could change your clean sub so it takes an array and returns an array. Then you can 'pipe' your data through it:

    #!/usr/bin/env perl use Modern::Perl; sub clean { for (@_){ s/^\s+//; s/\s+$//; # this includes chomp } return @_; } my $str = 'The | quick | brown | fox | jumped| over | the | lazy | do +g. '; say for clean split /\|/, $str;

    Aaron B.
    Available for small or large Perl jobs; see my home node.

Re: How to call a function on each item after a split?
by kcott (Archbishop) on Oct 09, 2012 at 09:02 UTC

    G'day MrSnrub,

    I see you have a lot of responses already. Here's my take on this.

    You can trim the data by capturing everything that isn't leading or trailing whitespace with this regular expression:

    /\A\s*(.*?)\s*\z/

    You can use that directly inside a map like this:

    say for map { (/\A\s*(.*?)\s*\z/) } split /\|/, $str;

    You can use it inside a subroutine called from map like this:

    say for map { clean($_) } split /\|/, $str; sub clean { ($_[0] =~ /\A\s*(.*?)\s*\z/)[0] }

    It's good that you've tried a variety of cases with whitespace in different positions in your test input $str. What you don't have is leading whitespace at the start of $str or trailing whitespace (following non-whitespace) at the end of $str.

    Here's my tests using both of the options I presented with some additional test data.

    $ perl -Mstrict -Mwarnings -E ' my $str = " \t item0 with leading whitespace |item1 | item2| item3 | extra item with embedded whitespace \t \n \n\t |item4| | itemN with trailing whitespace \t "; say "---- WITHOUT SUBROUTINE ---"; say ">$_<" for map { (/\A\s*(.*?)\s*\z/) } split /\|/, $str; say "---- USING SUBROUTINE ---"; say "<$_>" for map { clean($_) } split /\|/, $str; sub clean { ($_[0] =~ /\A\s*(.*?)\s*\z/)[0] } ' ---- WITHOUT SUBROUTINE --- >item0 with leading whitespace< >item1< >item2< >item3< >extra item with embedded whitespace< >item4< >< >itemN with trailing whitespace< ---- USING SUBROUTINE --- <item0 with leading whitespace> <item1> <item2> <item3> <extra item with embedded whitespace> <item4> <> <itemN with trailing whitespace>

    -- Ken

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (6)
As of 2024-03-29 11:57 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found