Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

My 'perldar' tells me there is a 'better' solution for this list operation

by OfficeLinebacker (Chaplain)
on Nov 03, 2006 at 22:28 UTC ( [id://582186]=perlquestion: print w/replies, xml ) Need Help??

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

Greetings, esteemed monks!

I have two lists, one of which is a subset of the other. So for example,

my @l1=("Date","IndexID","Maturity","OnTheRun","CompositePrice","Compo +siteSpread","ModelPrice","ModelSpread","Depth","Heat"); my @l2=("OnTheRun","CompositePrice","CompositeSpread","Depth");
I want to end up with a list that is the same length as @l1, but every value that is not also present in @l2 should be replaced with "null".
What I have so far:
foreach my $f (@l1){ if (join(",",@l2) !~ /\b$f\b/){ $f='null'; } }
I'm thinking there has to be a more elegant and/or efficient and/or more robust way.
perhaps something like
my @l3= map {(join(",",@l2) =~ /\b$f\b/)?$f:"null"} @l1; ?

I guess what I don't like is the implied nested looping (I assume the join has to be re-done for each iteration through @l1?). Plus, the join just seems kind of ugly. Maybe it's unavoidable.

For the record, this is for Text::CSV::Simple and I am trying to make a field map.

_________________________________________________________________________________

I like computer programming because it's like Legos for the mind.

Replies are listed 'Best First'.
Re: My 'perldar' tells me there is a 'better' solution for this list operation
by Sidhekin (Priest) on Nov 03, 2006 at 22:35 UTC

    If you judge whether a value is "present" by string equality (and it would seem you do), you could just use a hash:

    my %map; @map{@l2} = (); my @list = map { exists $map{$_} ? $_ : 'null' } @l1;

    ( Also: "perldar"++ )

    print "Just another Perl ${\(trickster and hacker)},"
    The Sidhekin proves Sidhe did it!

Re: My 'perldar' tells me there is a 'better' solution for this list operation
by Roy Johnson (Monsignor) on Nov 03, 2006 at 22:34 UTC
    my %hash; @hash{@l2} = @l2; my @newlist = @hash{@l1};
    Update: Ah, after two downvotes, I see that the OP wants the string 'null' rather than actual null values. Well, that certainly is worth throwing dung at someone over. Sheesh. One change to the final line:
    my @newlist = map defined($_)?$_:'null', @hash{@l1};

    Caution: Contents may have been coded under pressure.
Re: My 'perldar' tells me there is a 'better' solution for this list operation
by OfficeLinebacker (Chaplain) on Nov 03, 2006 at 22:45 UTC
    I see that two responses use a construct like
    my %h1; @h1{@l2}=();
    I must admit I have seen this before (especially the second line) and I don't understand it. Is it just shorthand for populating a hash's keys collection with the elements of an array? Almost like
    keys(%h1)=@l2; ?
    Thanks,
    T
    P.S. I had no other word for that kind of intuitive feel for coding.

    _________________________________________________________________________________

    I like computer programming because it's like Legos for the mind.

      A couple of quick examples may make it easier to understand what a hash slice is doing. This code:

      @hash{"one", "two", "three"} = 1 .. 3;

      Is equivalent to:

      ($hash{one}, $hash{two}, $hash{three}) = 1 .. 3;

      And likewise, these are equivalent:

      my @list = @hash{"one", "two", "three"};

      and

      my @list = ($hash{one}, $hash{two}, $hash{three});
      It's a hash slice, for populating multiple values of a hash from a list.

      Caution: Contents may have been coded under pressure.
Re: My 'perldar' tells me there is a 'better' solution for this list operation
by mreece (Friar) on Nov 04, 2006 at 05:55 UTC
    because there's more than several ways to do it..
    my @l3 = map { my $f = $_; ((grep { $f eq $_ } @l2),'null')[0] } @l1;
Re: My 'perldar' tells me there is a 'better' solution for this list operation
by DigitalKitty (Parson) on Nov 04, 2006 at 03:34 UTC
    Hi OfficeLinebacker,

    In order to reduce the 'noise factor' of your array definitions, you could also construct each as follows:

    #!/usr/bin/perl use warnings; use strict; my ( @l1, @l2 ); @l1 = qw(Date IndexID Maturity OnTheRun CompositePrice CompositeSpread ModelPrice ModelSpread Depth Heat); @l2 = qw(OnTheRun CompositePrice CompositeSpread Depth); # More code here...


    By declaring your variables, arrays, hashes, etc. at an earlier stage in the program, it reduces, in my experience, a portion of the frustration inherently present in the debugging process.

    Hope this helps,

    ~Katie

      I don't think you gain anything this way. Except that you exercise your fingers more. And I don't think it's good to predeclare your variables. You should keep the scope of all variables as small as posible, declaring them only as you intend to start using them. Sometimes it's even good to introduce a block into the code just so that you could specify the scope of a variable, if you want to use it just in those ten lines and not anywhere else. Everything is IMHO of course.

Re: My 'perldar' tells me there is a 'better' solution for this list operation
by johngg (Canon) on Nov 04, 2006 at 10:35 UTC
    Or how about a solution using a regular expression.

    my @l1 = qw(Date IndexID Maturity OnTheRun CompositePrice CompositeSpread ModelPrice ModelSpread Depth Heat); my @l2 = qw(OnTheRun CompositePrice CompositeSpread Depth); my @l3; { local $" = q{|}; @l3 = map {m{^@l2$} ? $_ : q{null}} @l1; }

    Cheers,

    JohnGG

      To johngg:
      it seems your code does not work at all; even if rectified to work, it is not so efficient when regex engine is involved.

      My suggestion is:
      sort (lexicographically) the lists first, then the search can be done in one whole pass through the two lists.

        It works fine for me. In what way did it not work for you? I did not show the use strict; and use warnings; I always put at the top of my scripts in my first post. Here's the whole script again, it's output and some info on my platform and perl version

        $ cat spw582186 #!/usr/bin/perl # use strict; use warnings; my @l1 = qw(Date IndexID Maturity OnTheRun CompositePrice CompositeSpread ModelPrice ModelSpread Depth Heat); my @l2 = qw(OnTheRun CompositePrice CompositeSpread Depth); my @l3; { local $" = q{|}; @l3 = map {m{^@l2$} ? $_ : q{null}} @l1; } print qq{$_\n} for @l3; $ ./spw582186 null null null OnTheRun CompositePrice CompositeSpread null null Depth null $ uname -a SunOS b4rsk 5.10 Generic_118833-03 sun4u sparc SUNW,Ultra-60 $ perl -v This is perl, v5.8.4 built for sun4-solaris-64int (with 28 registered patches, see perl -V for more detail) Copyright 1987-2004, Larry Wall Perl may be copied only under the terms of either the Artistic License + or the GNU General Public License, which may be found in the Perl 5 source ki +t. Complete documentation for Perl, including FAQ lists, should be found +on this system using `man perl' or `perldoc perl'. If you have access to + the Internet, point your browser at http://www.perl.com/, the Perl Home Pa +ge. $

        You may well be right regarding efficiency, I haven't run any benchmarks. The object of the post was to show an alternative method to those already given.

        Cheers,

        JohnGG

Re: My 'perldar' tells me there is a 'better' solution for this list operation
by OfficeLinebacker (Chaplain) on Nov 04, 2006 at 18:15 UTC

    Hey, thanks for all the helpful responses.

    Thanks in particular to the link to hash slices. The part I found particularly helpful was

    "If you're confused about why you use an '@' there on a hash slice instead of a '%', think of it like this. The type of bracket (square or curly) governs whether it's an array or a hash being looked at. On the other hand, the leading symbol ('$' or '@') on the array or hash indicates whether you are getting back a singular value (a scalar) or a plural one (a list)."

    I think that using "map" as a variable name in an example using the "map" function is a bit confusing.

    As far as when to declare variables, I do try to use the smallest lexical scope possible. I also often have a series of what I call "global" variables at the beginning and top level of the program declared with "my." The main purpose is a) saving typing (if certain strings are used several times in the program I will use a variable with a short name; for example if I know I will be using directory and/or file base names of "temp" I might declare 'my $t = "temp";' or somesuch) b) the other obvious reason of making the code more maintainable (if the directory I want to use for something changes, I only have to change it in one spot near the beginning of the program). Both are fraught with danger though as sometimes I'll see a variable used and have to scroll back up to remind myself what it is. This discussion is probably as old as the concept of scoping; further comments(or nudges in the right direction) are welcome.

    In this particular case I download four reports in CSV format and I define a (global) hash with reportname=>["wantedfield1","wf2",etc] pairs. The full list of available column headers is generated dynamically by reading in the first line of each report. Then, the field map is generated via the process discussed in this thread. This means hopefully the next time they change the reports (which seems to constitute simply adding fields), it won't be a major rewrite.

    johngg, I don't get your method at all.

    One last question: it seems a couple of the responses used qw(Field1 Field2 Field3) as opposed to ("Field1","Field2","Field3"). What's the difference?

    Thanks again and ++ all responses in this thread regardless.

    T.

    _________________________________________________________________________________

    I like computer programming because it's like Legos for the mind.

      it seems a couple of the responses used qw(Field1 Field2 Field3) as opposed to ("Field1","Field2","Field3"). What's the difference?

      Short answer, there is no difference :).

      Long answer, see perlop.

      qw/STRING/
            Evaluates to a list of the words extracted out of
            STRING, using embedded whitespace as the word 
            delimiters.  It can be understood as being roughly
            equivalent to:
      
                split(' ', q/STRING/);
      
            the difference being that it generates a real list
            at compile time.  So this expression:
      
                qw(foo bar baz) 
      
            is semantically equivalent to the list:
      
                'foo', 'bar', 'baz'
      
            Some frequently seen examples:
      
                use POSIX qw( setlocale localeconv )
                @EXPORT = qw( foo bar baz );
      

      -- Hofmator

      Code written by Hofmator and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (4)
As of 2024-04-25 08:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found