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

Hi there, I am new to this forum, and to Perl as a whole. I've been practicing programming in Perl today, using exercises from practicepython.org (yes, I know Python is not Perl, but I like the broad nature of the exercises on there and I had no idea where to look for up to date Perl-specific exercises :-P).

I have just finished doing this exercise: http://www.practicepython.org/exercise/2014/03/05/05-list-overlap.html. The exercise is basically finding the values that two randomly generated lists have in common.

I figured out how to do this using regexps (which are awesome btw) but I haven't been able to reduce the code necessary for matching the values to one line, which is one of the optional objectives of the exercise. Would it be possible to reduce the amount of code necessary to find the common values between the two arrays to only one line of Perl? Or is this as short as it gets? I would also like to know if using regexps for this is less or more efficient (performance-wise) than just using a for loop. This is the code I have as of now:

#!/usr/bin/env perl # This script should check what elements are common between two lists use strict; use warnings; # Lists of random numbers my @input=map {int(rand($_))} (1..100); my @valuesToMatch=map {int(rand($_))} (1..100); print "-----list 1 is:-----\n@input\n-----and list 2 is:-----\n@values +ToMatch\n"; # Match the common values my $matchString = sprintf "\\b%s", join("\\b\|\\b",@valuesToMatch); my @result = join(" ", @input) =~ /$matchString/g; # Return result print "---------------The matching numbers are:---------------\n@resul +t\n" ;

Replies are listed 'Best First'.
Re: one line to check for common list values
by haukex (Archbishop) on Feb 25, 2017 at 19:24 UTC

    Welcome to Perl and the Monastery, dollar_sign! Just a small tip on posting: Thank you for using <code> tags for the code, but please don't add line numbers.

    Perl has excellent documentation, see perldoc.perl.org, and the command line tool perldoc. For example, typing perldoc -q intersection at the command line brings up this FAQ: How do I compute the difference of two arrays? How do I compute the intersection of two arrays?

    One of the pieces of Perl philosophy is TIMTOWTDI, There Is More Than One Way To Do It, so if you want to use a regex to find matches, that's ok, but see the performance comparison below. Note that if your input wasn't limited to integers, your regex has one potential problem, see what happens when you set my @input = (1.23) and my @valuesToMatch = (23). I wrote about the generation of regexes here (still a draft at the moment), and I showed what I would consider a "safer" version of your code below.

    I would also like to know if using regexps for this is less or more efficient (performance-wise) than just using a for loop.

    The go-to module for this is Benchmark. Here is a comparison of the performance of using regular expressions, hashes, and grep. Note how the hash approach clearly wins!

    Would it be possible to reduce the amount of code necessary to find the common values between the two arrays to only one line of Perl?

    Sure, it's possible to jam it all into one line, the AM showed one way to do it for the "regex" approach (Update: tybalt89 showed a few more, but benchmark them and you'll see all three of them are less efficient than all of the above). It's also possible in the above "grep" solution using two nested greps, but for the "hash" solution, you'll need multiple statements, even if you jam them all into one line :-)

    I had no idea where to look for up to date Perl-specific exercises

    One might be HackerRank, or perhaps Rosetta Code.

      HackerRank is fun, but many of the problems are rigged to favor C solutions. Also check out Project Euler (projecteuler.net).
Re: one line to check for common list values
by 1nickt (Canon) on Feb 25, 2017 at 21:21 UTC

    Hi dollar_sign, and welcome to the Monastery and to Perl, the One True Religion.

    Some of the brethren have shown you some valuable tips regarding your posted code, especially regarding regexps and hashes. I offer a slightly different answer.

    write a program that returns a list that contains only the elements that are common between the lists (without duplicates). Make sure your program works on two lists of different sizes.

    perl -Mstrict -MArray::Utils=:all -WE 'my @x=(1,1,2,3,4,3); my @y=(0,3 +,4,5,6,3,7,8); say for unique intersect @x, @y'
    Returns:
    3 4

    I understand that you are teaching yourself to program by solving simple problems, and the use of intersect() and unique() from a library (Array::Utils) might seems like it won't teach you anything about list reduction. But I submit that a key part of programming is knowing how to find and use libraries for common tasks -- precisely so that you can move on to learning how to solve more complex problems or accomplish more complex tasks.

    One of the greatest benefits of using Perl is the CPAN, where you can find tools from bone saws to X-ray-guided arthroscopic laser scalpels. Learning how to use those tools to perform an operation is also learning to program.

    Hope this helps!


    The way forward always starts with a minimal test.
Re: one line to check for common list values
by tybalt89 (Monsignor) on Feb 25, 2017 at 19:46 UTC
    #!/usr/bin/perl use strict; use warnings; # This script should check what elements are common between two lists # Lists of random numbers my @input = map { int rand $_ } 1..100; my @valuesToMatch = map { int rand $_ } 1..100; print "-----list 1 is:-----\n@input\n-----and list 2 is:-----\n@values +ToMatch\n"; # Match the common values (three ways) my @result = "@input\n@valuesToMatch" =~ /\b(\d+)\b(?=.*\n.*\b\1\b)/g; print "---------------The matching numbers are:---------------\n@resul +t\n" ; @result = grep +{map { $_, 1 } @valuesToMatch}->{$_}, @input; print "---------------The matching numbers are:---------------\n@resul +t\n" ; @result = grep "@valuesToMatch" =~ /\b$_\b/, @input; print "---------------The matching numbers are:---------------\n@resul +t\n" ;
Re: Re: one line to check for common list values
by dollar_sign (Novice) on Feb 25, 2017 at 21:10 UTC

    First of all, thank you all very much for your replies and for the warm welcome, I have a feeling that I will be returning here a lot in the foreseeable future :).

    Your explanations are a huge help, I have been reading the perldoc a lot the past couple of days, but seeing code in a context I have been working in just clears up so much more for me! I will study all of your answers thoroughly until I understand every last bit of code in them!

    I'm sorry about the line numbering, I am using a version of VIM that has no clipboard support so I have to copy my whole command line screen, including the line numbers (still need to look for some plugin/other version of VIM, in the meantime I guess I'll just open my code in another editor to paste it here :).

      Turn on/off line numbering in vim:

      :set nu :set nonu


      The way forward always starts with a minimal test.
Re: one line to check for common list values
by Anonymous Monk on Feb 25, 2017 at 18:12 UTC

    There's a trick for jamming an expression into a string or regex:

    my @result = "@input" =~  /@{[join "|", map "\\b$_\\b", @valuesToMatch]}/g;

    But you're probably better off using a hash for this. It'll be much faster if the arrays are large.

    my %match; $match{$_} = 1 foreach @valuesToMatch; my @result = grep $match{$_}, @input;
Re: one line to check for common list values
by Lotus1 (Vicar) on Feb 27, 2017 at 00:23 UTC

    I would like to point out that the numbers aren't as random as they could be. int(rand(1)) will always produce 0 as the output. int(rand(2)) will produce 0 or 1, int(rand(3)) will produce 0, 1 or 2 and so on. If you would like to produce multiple random numbers that are between 1 0 and 99 then try this.

    use strict; use warnings; my @a; foreach my $upper (1..10) { @a = map { int rand $upper } 1..10; print "@a\n"; } print '*'x79, "\n"; foreach my $upper (1..10) { @a = map { int rand 100 } 1..10; printf "%2s ", $_ foreach @a; print "\n"; } __END__ 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1 1 1 1 2 0 0 2 1 1 1 0 2 1 1 0 0 0 3 0 1 0 0 2 2 2 1 1 4 1 3 4 2 0 4 3 3 2 0 4 5 1 2 3 0 0 5 5 2 6 5 1 1 6 1 4 7 5 2 0 2 3 6 7 7 4 0 4 4 8 2 2 1 1 8 9 2 9 7 6 0 7 1 4 ********************************************************************** +********* 15 38 19 5 43 30 71 4 55 44 12 62 64 68 38 3 76 68 61 41 11 60 88 46 80 89 20 45 78 99 91 57 58 16 93 75 69 98 90 81 87 61 42 72 47 16 19 19 97 16 51 99 89 12 86 53 58 71 52 99 31 1 36 27 85 56 90 88 69 92 82 6 86 59 88 66 88 50 42 58 23 54 96 52 61 39 82 65 62 6 7 92 77 22 78 91 6 63 67 22

      Lotus1 Thank you for pointing that out!

      1nickt Thank you for your advice, I will surely be taking advantage of CPAN when I start doing bigger projects. At the moment my priority is with leaning all the builtins because in addition to using Perl for building applications, I want to be able to write portable scripts. (I might want to do some sysadmin work in the future and I was told that portability is key in that line of work).

Re: one line to check for common list values
by Cow1337killr (Monk) on Mar 04, 2017 at 15:18 UTC
      Cow1337killr hahaha well I guess I had that question coming ;) I actually ordered a copy of Learning Perl (7th edition) from a a web store just 4 hours ago! (Don't tell anyone but I got excited and found a pdf of it on the internet, so I could start reading while waiting for the physical book)