http://www.perlmonks.org?node_id=11104608

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

This is a homework assignment, and it works. If I use strict, I don't get an error, but if I use warnings, it says "Use of uninitialized value in string eq at line 7", it prints this error twice (I assume its for the two times that the match is found).
Here is the code I am turning in for my assignment, but I still want to know why its giving me those errors, and if I should do something better. So I'm really just hoping for someone to help me understand what is happening to cause this error.
my @colors = qw(red green blue yellow pink purple brown); my $count = @colors; my @drop = qw(pink brown); my $num = 0; foreach $num (1..$count){ $num--; if ($colors[$num] eq $drop[0] or $colors[$num] eq $drop[1]){ splice (@colors, $num, 1); } } print "@colors \n";

Replies are listed 'Best First'.
Re: The error says the value is uninitialized, but it works anyway
by haukex (Archbishop) on Aug 17, 2019 at 09:48 UTC

    Hello and Welcome to Perl and the Monastery, mizducky!

    You are storing the length of the @colors array in the variable $count, but then modifying the length of the array by using splice to remove elements. So, because you are reducing the length of the array from 7 down to 5, and you are still trying to access elements from the original length of the array, which now no longer exist, you're getting the warning "Use of uninitialized value in string eq".

    By the way, the more Perlish way to iterate over the indicies of an array is for my $num (0..$#colors), where $#colors returns the index of the last element of the array.

    Note that your code suffers from an issue: If the two colors to be removed are immediately after one another in @colors, only one will be removed. Can you see why that is? If you're not sure, see the Basic debugging checklist: something as simple as printing the values of the variables as your loop runs will already give you a lot of information.

    Personally, when I have to modify an array I am iterating over, I like to use a classic for (my $i=0; $i<@array; $i++) loop, because if I am inserting or removing elements, that form gives me the most control over $i, which I may have to modify depending on where I'm inserting/removing elements. If that gets too complicated, note that in Perl, There Is More Than One Way To Do It. Unless your homework assignment explicitly states that you should iterate over the looparray this way, here are some ideas:

    • You could iterate over the elements of the @colors array, pushing the ones you want onto a new array.
    • You could use grep to filter the @colors array.

    Another limitation of your code is that it only works for the case of @drop having two elements - this could also be solved in a few different ways:

    • You could loop over the @drop array as well, but that will become inefficient as the arrays grow.
    • You could store the values of the @drop elements as the keys of a hash, this will be much more efficient since hash lookups are fairly cheap.
    • A more advanced technique, which is more applicable if the things you're looking for aren't fixed strings, would be to build a regular expression from the elements of @drop, as described in the tutorial Building Regex Alternations Dynamically.
      The homework question just defines @colors and @drop and says to remove the things in drop from colors, that's it.
      The code I posted does print @colors with out the items from @drop, but since I'm just learning this for the first time that's the only way I could think of to do it.
      I'll keep playing with the things you suggested. Thank you for your help!
        The homework question just defines @colors and @drop and says to remove the things in drop from colors, that's it.

        In that case, the "easiest" way to go about it would probably be to build a hash from the @drop array, and to use grep to filter the @colors array. I could write these two lines of code for you, but since this is homework (thanks for letting us know, by the way), it's probably best if you see if you can figure it out :-) But feel free to ask any questions about it here, or ask if you get stuck!

        Hint: See "Data: Arrays" in perlfaq4 (just ignore the stuff about ~~, that's still experimental).

Re: The error says the value is uninitialized, but it works anyway
by BillKSmith (Monsignor) on Aug 17, 2019 at 13:14 UTC
    haukex has explained your indexing problem. You can fix it by processing the array backwards. No other change to your code is required. (Of course you should restore the strict and warnings).
    foreach $num (reverse 1..$count){

    I recommend against using a previously defined lexical variable as a loop variable. It does not make any difference in this script, but it is a bad practice. In the future, you may be tempted to use that variable after the loop has finished and it almost certainly will not contain what you expect. Use the syntax haukex showed.

    Bill
      ... using a previously defined lexical variable as a loop variable ... is a bad practice. ... you may be tempted to use that variable after the loop has finished and it almost certainly will not contain what you expect.

      I agree this syntax is Bad Practice and should be avoided, but the variable after the loop has finished will always contain exactly what you expect if you expect it to be absolutely unchanged:

      c:\@Work\Perl\monks>perl -wMstrict -le "my $x = 'foo'; print $x; ;; for $x (7 .. 9) { print qq{ $x}; } ;; print $x; " foo 7 8 9 foo
      This is because the loop variable, whatever it may be ($x in this case), is "topicalized", i.e., localized upon entry to the loop and restored upon loop termination. This surprising bit of Perl-style for-loop behavior (it's completely absent in the C-style for-loop) is why this syntax [Note 1] should be avoided IMHO. Otherwise, it makes no difference whatsoever AFAIK.

      Notes:
      1. ... this syntax ...   By which I mean
            my $x = ...;
            ...
            for $x (...) { ...;  do_something_with($x);  ...; }
        Otherwise, the Perl-style
            for my $x (...) { ...;  do_something_with($x);  ...; }
        is the greatest thing since sliced bread!

      Update: Added Note 1.


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

Re: The error says the value is uninitialized, but it works anyway
by jcb (Parson) on Aug 17, 2019 at 23:22 UTC

    Other monks have explained where the undef values are coming from, but have not explained why your program still works despite the warnings. Since you were honest about this being a homework assignment, I will take some time to explain.

    Your program works anyway because neither of the elements in @drop are undef, and while undef eq undef (with two warnings) in Perl, undef ne $string if $string is a defined true value.

    Here is the contents of the @colors array on each iteration of the loop, both before and after an element is possibly removed. The element that $num refers to is underlined.

    0: before: red green blue yellow pink purple brown
    0:  after: red green blue yellow pink purple brown
    --
    1: before: red green blue yellow pink purple brown
    1:  after: red green blue yellow pink purple brown
    --
    2: before: red green blue yellow pink purple brown
    2:  after: red green blue yellow pink purple brown
    --
    3: before: red green blue yellow pink purple brown
    3:  after: red green blue yellow pink purple brown
    --
    4: before: red green blue yellow pink purple brown
    4:  after: red green blue yellow purple brown
    --
    5: before: red green blue yellow purple brown
    5:  after: red green blue yellow purple
    --
    6: before: red green blue yellow purple
    6:  after: red green blue yellow purple
    --
    

    I modified the code slightly to print that. While I cannot show you exactly what I used without giving away an answer that you should find, this code shows what the "before" and "after" parts mean:

    my @colors = qw(red green blue yellow pink purple brown); my $count = @colors; my @drop = qw(pink brown); my $num = 0; foreach $num (1..$count){ $num--; print $num, ": before: @colors\n"; if ($colors[$num] eq $drop[0] or $colors[$num] eq $drop[1]){ splice (@colors, $num, 1); } print $num, ": after: @colors\n--\n"; } print "@colors \n";

    This technique of inserting additional output to show a program's intermediate states is commonly known as "printf debugging", after the common output function in the C programming language. Perl also has printf, but print is far more commonly used.

    The bug in your code that haukex mentioned should be easy to see now, and I will give a few hints towards the "two lines of code" that haukex mentioned:

    1. How do you construct a hash from a list in Perl?
    2. How can you use map to construct such a list?
    3. How do you test for the existence of a hash key?
    4. What does grep do in Perl?

    And another issue:

    1. How can you avoid the wasted seventh iteration of the loop? (Hint: What are the loop control operators in Perl?)

    Good luck on your adventures in learning Perl.

    Edited 2019-08-18 by jcb: Fix logical error pointed out by haukex: undef is not special under eq and stringifies to an empty string.

      I want to thank all of you for your help in understanding this problem. I went to bed thinking about it and I realized the point about how the removal of the element "pink" changes the first array, and how that impacts the rest of the code.
      As a student I don't mind if I lose a few points for it not being exactly right, I will still turn in my first script even though I've learned more from you guys. It's my work and I'm ok with taking the grade I've earned.
      So I didn't know that "printf debugging" was a term, but I had already tried that a few times. In the end I had just determined to keep the script as simple as possible as long as it did the thing.
      Here is what I had done to see what was happening.
      use warnings; my @colors = qw(red green blue yellow pink purple brown); my $count = @colors; my @drop = qw(pink brown); my $num = 0; foreach $num (1..$count){ $num--; print "$count \n"; print "$num \n"; print "$colors[$num] \n"; if ($colors[$num] eq $drop[0] or $colors[$num] eq $drop[1]){ splice (@colors, $num, 1); } else { print "$colors[$num] was not dropped from the array \n"; } } print "@colors \n";
      Of course this left me with a lot of questions originally. Here is what prints for me.
      7 0 red red was not dropped from the array 7 1 green green was not dropped from the array 7 2 blue blue was not dropped from the array 7 3 yellow yellow was not dropped from the array 7 4 pink 7 5 brown 7 6 Use of uninitialized value within @colors in concatenation (.) or stri +ng at C:\Users\..ch7q4.pl line 10. Use of uninitialized value in string eq at C:\Users\..ch7q4.pl line 11 +. Use of uninitialized value in string eq at C:\Users\..ch7q4.pl line 11 +. Use of uninitialized value within @colors in concatenation (.) or stri +ng at C:\Users\..ch7q4.pl line 15. was not dropped from the array red green blue yellow purple
      I understand now, that the element "purple" is never evaluated, its skipped when "pink" is removed and the element numbers change. Then the next element "brown" is removed and then there are basically 2 empty spaces that the loop is trying to evaluate.
      My classmate made two new arrays and set the loop to move the word to another array, shifting from the @colors and pushing to @new (basically).
      For each of you who responded, thank you so much for your help. I will keep using all the resources you have sent so that I can do better in the future.
        Try your experiment with my suggestion for reversing the processing order. (Remember you only edit one line.) Do you see why it runs correctly? The modified program never tries to access an array element which has been moved.
        Bill
      undef ne $string if $string is a defined value.

      Sorry, but no: $string = "" is a defined value, and undef eq "". What's happening is that eq forces string context on its arguments and undef is converted to "", with a warning, which of course is the same thing as the string "". eq is not a test for definedness, only defined does that - undef eq undef not because they are both undef, but because "" eq "".

        Hmmm... You are right. I have edited my earlier post to correct the misinformation.

        It seems that I had confused Perl, SQL, and Lisp while writing that. As I recall, there is only one undef value in perl, and DBI (see also DBI recipes) maps SQL NULL and Perl undef. Here are the tests I ran before writing that post:

        • $ perl -we 'print "T\n" if "foo" eq "foo"'
          T
        • $ perl -we 'print "T\n" if undef eq "foo"'
          Use of uninitialized value in string eq at -e line 1.
        • $ perl -we 'print "T\n" if undef eq undef'
          Use of uninitialized value in string eq at -e line 1.
          Use of uninitialized value in string eq at -e line 1.
          T

        Spotted the missing test yet? Try:

        • $ perl -we 'print "T\n" if undef eq ""'
          Use of uninitialized value in string eq at -e line 1.
          T

        Since the message specifically mentions use of undef in eq, I had assumed that eq actually sees the undefined value, instead of stringifying it like any other value. Wrong!

      We haven't learned "map" or "grep" yet. When I look things up, it feels like I'm diving into the deep end and I can't see the shore.
      We have covered hashes only in the most basic sense so far.

      For instance in a previous assignment, I really wanted to take a user input and force it to be a floating point number not a string (for math equations), but I could only find a way to take user input and make it an integer "int(<STDIN>)", but what if they enter a number with a decimal? Why is it basically impossible to easily force user input to be a number if it's not a whole number?

      Now, I know that you guys who know this stuff well probably have the answer to this readily at hand, but for me, trying to find an Easy answer for something that I think is a simple question, was not easy to understand at all.
      Obviously I don't expect that to be answered now, but just explaining how I'm still learning, and I don't always understand what I see online when I look things up.
        We haven't learned "map" or "grep" yet.

        Is that a royal we? ;-)

        Yes, learning in groups is ok, also the classic school "class and teacher" approach. But that's not all. Don't think that's the only way to learn. There are so many other ways, and some of them make learning so easy that you don't even feel like you are learning.

        Feel free to learn new things outside the group. The group won't always be there when you need to learn something new, so you better learn how to learn on your own.

        To learn a computer language, read other people's code and try to understand what their code does. Especially in the early learning process, you will find new things in almost every line of code, and what you read looks more like line noise or a cat trampling over the keyboard than anything else. But things will soon start to make sense. You will recognize some constructs you've seen before. And very soon after that, you will know the most common constructs. Programs look like buildings made from Lego, and you know the Lego bricks quite well. Then, one day, you will see a car made from those building blocks. A program that uses the same building blocks that you already know, plus a single new building block that you have never seen before. That will happen several times, and you will get used to people building cars, trucks, railway systems, space ships, boats from the Lego bricks. Way before that, you have build your own little buildings, perhaps a little bit wonky, but they did the job. Your first "car" may have five wheels and a spaceship canon, but hey, it moved! And the next one will roll on an even number of wheels.

        One day, you will find an artistic, huge bouquet of flowers, large and tiny, all made from the same Lego blocks you use to build ugly cars. And you will enjoy not only the look, but also the way it was built. That's why Perl has an "artistic" license.

        The great thing about computer languages is that you can do experiments with them without really breaking anything. Take any program you find, make it run on your computer. Then try to change it a little bit. If if did additions of two numbers, make it subtract instead. Extend it to three numbers. You will make errors, Things will break. Your code will look like a mess. That's expected. And if you have messed up the code to the point were nothing works, you can easily revert to the original program simply by renaming your source file to something different and copying in the original file.

        There are tools for exactly that, taking, breaking, and reverting code. But they also need some time to learn. Subversion and Git are two famous ones, and once you have learned how to make your computer manage your files, you won't want to go back. But that's a diffferent story, and for your first steps, renaming and copying files is ok. Once you can write software on a level above hello world and the classic calculator example, take some time to learn Subversion. It comes with a really good book, from which you really need only one or two chapters to get up and running.

        When I look things up, it feels like I'm diving into the deep end and I can't see the shore.

        I know that feeling. But believe me, you've just got your toes wet, and the beach is right behind you. I have that feeling at work with every new product that we develop (embedded systems for medical applications, aerospace, and other industries). Every time, I feel like knowing nothing about the product environment. It takes a few weeks, then I think I've understood what the product will do. Then, our client explains what the product really does, with some of the edge cases, and I see that I'm still near the beach, and the water just reaches the belly button. I'm nowhere near the deep see that I thought I was swimming in. Get used to it. You can't know everything.

        We have covered hashes only in the most basic sense so far.

        Hashes are easy. There is some math involved in the background, and that's the difficult part of hashes, but you don't have to know that to work with hashes. First, hashes are like arrays, with some little differences:

        • Hashes aren't ordered like arrays, they will enumerate their keys and values in a random order that changes with every run of your program.
        • Hashes don't use positive integers to find the location of an entry, but instead, they use any strings you like.
        • Hashes use % instead of @, and {} instead of [].

        Then, there are some functions to help you with hashes: To get a list of keys, use keys. To get a list of values, use values. To get a stream of key-value-pairs, use each in a while loop. To find out if a key exists in a hash, use exists.

        For instance in a previous assignment, I really wanted to take a user input and force it to be a floating point number not a string (for math equations), but I could only find a way to take user input and make it an integer "int(<STDIN>)", but what if they enter a number with a decimal? Why is it basically impossible to easily force user input to be a number if it's not a whole number?

        Good questions. It's all in the documentation, but perl has a lot of documentation, and in some cases, it's a little bit messy. Reading documentation is another thing you have to learn.

        But your feeling is right. Making a number from a string of user input should be easy, and in fact it is easy. Perl's scalars can store strings or numbers, and perl automatically converts them when needed. You don't have to do anything!

        Now, input from STDIN usually has a trailing newline that needs to be removed, see chomp. After that, if the user input looks like a number in a format understood by perl (i.e. not roman or klingon numbers), perl can use it like a number.

        There are a few ways to trigger automatic conversion, like adding 0 to force numeric context or appending an empty string to force string context. You will see that in other peoples code, and with a little bit of luck, you will also find it in the documentation.

        Now, I know that you guys who know this stuff well probably have the answer to this readily at hand, but for me, trying to find an Easy answer for something that I think is a simple question, was not easy to understand at all. Obviously I don't expect that to be answered now, but just explaining how I'm still learning, and I don't always understand what I see online when I look things up.

        It looks like Perl is your first or second computer language, That makes things a little bit hard. To stay with the Lego bricks picture: you start with lego bricks, you have never had wood blocks to play with. That makes it a little bit harder to understand stacking of blocks to build simple houses. But do not fear, everyone but Larry Wall had to learn Perl.

        Learning new computer languages will become easier, because they share many of the same concepts. Their syntaxes may differ wildly, but things like strings, arrays, references/pointers, loops, if/then/else are quite universal. Your building blocks look a little bit different, but building things works using the same prinicples.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
        For instance in a previous assignment, I really wanted to take a user input and force it to be a floating point number not a string (for math equations), but I could only find a way to take user input and make it an integer "int(<STDIN>)", but what if they enter a number with a decimal? Why is it basically impossible to easily force user input to be a number if it's not a whole number?

        A scalar variable in perl has various internal representations: integer, string, number - of which "number" also handles decimals. Conversion from string to numbers and vice versa happens internally according to some rules. You find all pertaining to that in the perldata perl manual page.

        If you input a decimal number, chances are great that you don't enter the binary representation of that number, but rather a string representation, e.g. the decimal number "1.23" consists of 4 chars (or bytes, equivalent in this case); perl will convert that to float internally and store that value in the number slot of the variable, as soon as you treat that value in a numeric context - as in math.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
        I really wanted to take a user input and force it to be a floating point number not a string (for math equations), but I could only find a way to take user input and make it an integer int(<STDIN>), but what if they enter a number with a decimal?

        You've gotten some good answers already, here is a short one :-) Perl automagically converts between strings and numbers, including decimal numbers. If a variable contains a string that looks like a number, and you use it in an operation that normally involves numbers, Perl will do the conversion for you.

        my $input = <STDIN>; chomp($input); # remove newline my $output = $input + 1.23; print "output: $output\n";

        Now for example, if you enter "3.45", the output is "output: 4.68". Perl has automatically converted $input from "3.45" to 3.45 for the addition, and converted $output from 4.68 to "4.68" for the "output: $output\n"!

        When I look things up, it feels like I'm diving into the deep end and I can't see the shore.

        I know the feeling as well - looking things up in the official Perl documentation sometimes has that effect, because you get all the details at once. Fortunately, there are tutorials, for example on this site, as part of the Perl documentation (for example perlintro is a good start), and of course there are books such as Learning Perl that give a slow introduction without overwhelming with all the details.

        Why is it basically impossible to easily force user input to be a number if it's not a whole number?

        mizducky:   Further to the posts of shmem here and afoken here:   It's not (basically impossible, that is). Perl tries hard to obey the Do What I Mean (DWIM) principle. Perl has not, unfortunately, been blessed with powers of telepathy or clairvoyance, but it does what it can within reason. In particular, Perl will try to seamlessly convert back and forth between numbers and strings. Here are some concrete examples. All the examples in this post have warnings and strictures fully enabled.

        If you try to add strings that look like integers, floats, etc., the results should be pretty much as you expect (or I should say, as Perl expects you expect):

        c:\@Work\Perl\monks>perl -wMstrict -MData::Dumper -le "my $x = '1.23' + qq{0.34 \t\n\r} + '5.6e3' + '100'; print Dumper $x; ;; $x = $x + 98.6; print Dumper $x; ;; my $y = 0 + $x; print Dumper $y; " $VAR1 = '5701.57'; $VAR1 = '5800.17'; $VAR1 = '5800.17';
        Note that one of these strings, qq{0.34 \t\n\r}, has a bunch of whitespace padding at the end and still adds correctly. (I use the generic  qq{...} double-quote operator only because Windows command line turns up its nose at common  "..." double-quotes.) In the Dumper output in this example, the single-quotes around the nummbers (e.g., $VAR1 = '5601.57') indicate that Perl still sees these scalars as strings even after an arithmetic operation with a literal number (update: but see haukex's reply), but what care you as long as the numeric | arithmetic results are correct? (Actually, I can't say offhand just how I would force a scalar into strict "numeric" mode. Shall we say this is left as an exercise for the reader?)

        You mention difficulty with a numeric string taken from STDIN. Because it's a newline-terminated string, it should behave just like the highly whitespace-padded string in the foregoing example. (Normally, a program would use chomp to get rid of newline termination on input from STDIN or a file handle, but in this example it's not necessary.)

        c:\@Work\Perl\monks>perl -wMstrict -MData::Dumper -le "my $x = <STDIN>; print Dumper $x; ;; my $y = 56.7 + $x; print Dumper $y; " 2.34 $VAR1 = '2.34 '; $VAR1 = '59.04';
        Question: why does the Dumper output
        $VAR1 = '2.34
        ';
        above look a bit odd? You don't give an example of the code involving STDIN that gave you difficulty; this is always helpful — see Short, Self-Contained, Correct Example.


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

        We haven't learned "map" or "grep" yet.

        Others have linked to map and grep and to discussions of the use of these powerful built-in functions for solving your problem, and have given concrete examples of their use. You've had a chance to look at this material, so I'll just go ahead and give another example. This example is given in the context of a Test::More testing framework, which has also been mentioned already. Such a framework allows you to more easily alter functions and test data and more effectively play with different approaches. (Note that the difference set is assigned back to the original  @colors array.)

        c:\@Work\Perl\monks>perl use strict; use warnings; use Test::More 'no_plan'; use Test::NoWarnings; my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink purple); my %to_be_dropped = map { $_ => 1 } @drop; @colors = grep !$to_be_dropped{$_}, @colors; is_deeply \@colors, [ qw(red green blue yellow brown) ], "dropped: (@drop)"; done_testing; __END__ ok 1 - dropped: (pink purple) 1..1 ok 2 - no warnings 1..2

        Ok, that works, let's go nuts! Let's extend the Test::More framework to add many different test cases. When a software change is made, a large (and growing) number of test cases gathered together in one place/file allows you to quickly confirm that all the stuff that used to work still does, and makes it easy to add just one more test case, perhaps the one that reveals a nasty bug. At the same time, let's introduce:

        • Anonymous array  [ ... ] constructors (see perlref);
        • Array reference de-referencing  @$array_reference (also perlref);
        • More complex Perl data structures (see perldsc);
        • Distinguishing between a reference and a non-reference with ref;
        • ...?
        (This should really have been put into a file. Oh, well...) It's a lot to absorb, so don't worry too much about it. It's really just meant to whet your appetite: just know it's there.


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

        "...We haven't learned "map" or "grep" yet..."

        What does you discourage from reading the friendly manual and learning it by yourself?

        Some sketch written in a hurry for your further inspiration:

        #!/usr/bin/env perl use strict; use warnings; use Test::More; use Data::Dump; my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink brown); my @expected = qw(red green blue yellow purple); my $regex = join "|", @drop; my @result = grep {!/$regex/} @colors; is($regex, q(pink|brown), q(Regex seems to be OK!)); is(@result, @expected, qq(Heureka!)); done_testing(); dd \@drop; dd $regex; dd \@colors; dd \@result; __END__

        It's not carefully tested. And may be there is a better solution. Best regards, Karl

        «The Crux of the Biscuit is the Apostrophe»

        perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

        I don't rate your prof's assignments very well so far.....

        A first Perl class would not normally have any assignment that required using indices of an array, $array[$i] (although list slice which selects a subset of an array would be there, @subset= (@array)[1,5].
        Likewise, splice() is something to be mentioned in class, but not used in any assignment.
        The reason for that is to get you off C or other programming lingo that you've used before. These constructs while possible in Perl are not that common in a common Perl application, processing textual input line by line.

        Dealing with user input is a basic skill that should be taught in the first class. Your prof should have given you a prototype framework of how to do this.... Under "standard" command line input rules, any leading or trailing spaces don't matter.

        Below I show how to prompt the user for a number that must contain a decimal point.
        We ask the user for a number, get the input from stdin (which may contain spaces), we use regex to decide if the user input line meets the input criteria or not? If the criteria are not met, then the user is re-prompted and we go again...
        I like to express this procedure as a single while loop. Note that the parens around the "print" statement are required to get that prompt onto the screen before the rest of the while statement is executed. I also prefer use of the comma operator just from habit and ASM and C background. The comma operator uses the last "sub statement" to determine truth or falsehood. Here an "and" conjunction" would be fine also. Here this difference doesn't really matter at all.

        #!/usr/bin/perl use strict; use warnings; print "loops until 4 floats are entered\n"; for (1..4) { my $float = get_float(); print "$float Ok!!\n" } sub get_float { my $float; while ( (print"enter a float \(decimal required\): "), $float = <STDIN>, $float !~ /^\s*(\-|\+)?\d+\.\d*\s*$/) { print "Input not an float...try again\n"; } $float =~ s/^\s*|\s*$//g; #delete leading/trailing spaces return $float; } __END__ perl getfloat.pl loops until 4 floats are entered enter a float (decimal required): 4.5.3 Input not an float...try again enter a float (decimal required): +4,5 Input not an float...try again enter a float (decimal required): +4.5 +4.5 Ok!! enter a float (decimal required): 3 Input not an float...try again enter a float (decimal required): 3.0 3.0 Ok!! enter a float (decimal required): -2.56 -2.56 Ok!! enter a float (decimal required): +3 Input not an float...try again enter a float (decimal required): 3.12345 3.12345 Ok!!
        You wrote: "force it to be a floating point number not a string (for math equations)".
        Perl is NOT Python!
        In simple terms, every Perl variable starts out as a string. Each variable has kind of dual "type", a string value and a numeric value. Unlike Python, you don't have to worry about converting or worrying much about strings vs numeric types - Perl takes care of that for you.

        In the above code, I wrote:

        $float =~ s/^\s*|\s*$//g; #delete leading/trailing spaces return $float;
        That does indeed do what it says it does. Here Perl is using the string version of $float. I did this so that the calling routine will see the "+" sign, which is what the user perhaps entered. Now consider this:
        $float +=0; #causes creation of numeric value for $float! return $float;
        The plus sign will be missing when printed. Try it!

        In general, you don't have to worry about "string" vs "number", Perl will do the right thing without you having to worry about it because there is a duality of values for each variable.

        Update: Normally, I would allow an integer input without a decimal to be valid for a "float". Change the regex to eliminate that requirement. Also there is not a Perl "type" for an integer or a float, Perl will figure that difference out for you. There are good things about this and perhaps "bad" things. Usually the programmer doesn't have to worry about the fine details "under the covers".

        Oh, just as another comment, your Prof is asking you to do things that haven't been taught in the class yet. A very obvious solution to your assignment is:

        #!/usr/bin/perl use strict; use warnings; #The homework question just defines @colors and @drop #and says to remove the things in drop from colors, that's it. my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink brown); my %drop = map{$_ =>1}@drop; @colors = grep{!$drop{$_}}@colors; print "@colors \n"; #red green blue yellow purple
        Note that both grep and map imply "foreach" loops. They are "hidden", but they are there. A much more verbose version of this will execute in similar time. The assignment is poor because the techniques for the obvious solution for a Perl'er weren't covered in class yet. I also think that it could be that this splice stuff is actually slower!! Splice changes the size of an array and this is a relatively expensive operation. This is not commonly done (except for pop or push) which deal the the beginning or end of an array - not the middle. Making a complete new array with a subset of string values is probably much faster. Underneath Perl is C. An array of strings is to my knowledge an array of pointers to strings. Making a new subset array doesn't involve copying the strings themselves, just their pointers. Changing the size of the array of pointers to strings involves potentially copying a lot of pointers.

        Weird thing: I read somewhere in the Perl docs about a "lazy array delete". The element disappears from @array when used in a list context, but the indices of the unaffected elements of @array doesn't change. That sounds "dangerous" albeit much faster than a "splice".

        This is more "wordy", but about the same as the shorter version in terms of execution time:

        #!/usr/bin/perl use strict; use warnings; #The homework question just defines @colors and @drop #and says to remove the things in drop from colors, that's it. my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink brown); my %drop; foreach my $drop (@drop) { $drop{$drop} = 1; ## WILD! Hash and scalar drop ## have separate name spaces! ## I would name different, but ## just a namespace demo... } my @result; foreach my $color (@colors) { push @result, $color unless $drop{$color}; } @colors = @result; print "@colors \n"; #red green blue yellow purple
        In Perl the various sigils, @,%,$ have their own namespaces. This is not true in many other languages (C,Python). Be aware of that and in general do not use the exact same name for different Perl types. Above I showed how confusing this could be! Just because it is allowed doesn't mean you should do it!

        You wrote: "I could only find a way to take user input and make it an integer" Study the above. If a variable is an valid integer string, you can use it as a numeric value! No conversion is required! A $variable can be pretty much be used interchangeably. as a number (int or float) or string.

        One thing I learned (although in another area of studies): It's not important to know everything, but it's important to know where too look it up or ask.

        As long, as you find a solution, noone will ask from where the solution came. Although it's common and helpful for everyone to point at your sources. The important thing is that you do have a solution.

        Regarding you question about floats as input: I did a lot of programming, also in perl. Although I don't really know, how to do it; I have some starting points.

        printf/scanf comes to my mind - So I'd look this up, firstly.

        After this, I'd most probably lookup the problem in CPAN. User input is always a source of trouble. Not to say, users are the real source of all these troubles.. They always do things, you'd never expect. Instead of entering a number, putting a cat onto the keyboard. Or entering sql-injects. or whatever.

        So, to handle border cases, different localizations, security flaws, ... ..., with some luck there's a package already there.

        If there's a reason to implement the thing myself - maybe, cause it's homework, or cause there isn't a module - google is your friend.

        Just don't do only Copy and Paste, copy only, what you did understand.


        Which is the most important: I really don't want to know, how many things are just copy'd n pasted, without any understanding at all.

        Having studied Philosophy, I had to learn, most people don't know at all, what they are talking about.

        There's a big advantage in programming: There are valid and well defined citerias. Either this thing works, or it doesnt.

        Sadly, most software works - but works only within defined criterias.


        E.g. Most microsoft software works only for a limited time - I never managed to keep (years before) Windows XP running longer than, say, 24 hours.

        Obviously someone did copy and paste the malloc routine into gwbasic ( suspecting Windows has been written in basic), but didn't UNDERSTAND, that he had to copy also the free routine.

        When the error showed up, cause free wasn't defined, the debug team luckily found also the fix: Just define an empty free function, and everything is ok. Especially, since the release was overdue.

        And, as always, me as the user did the unexpected: Why the heck keeps someone his pc running for 24 hours???

        8 hours runtime SHOULD BE ENOUGH FOR EVERYONE!!!

        (Quoting Bill's famous idea, 640kB should be enough for everyone.....)


        Anyways, seems to me you are well on the way.
Re: The error says the value is uninitialized, but it works anyway
by holli (Abbot) on Aug 18, 2019 at 11:32 UTC
    As a general rule, you should avoid changing an array you are iterating over. That's bound to cause all kinds of headaches. Also, it is favourable to name variables by what they mean, not by what they contain. Here, $num is a bad name. $index is better, so are $i, $j, $k, etc.

    Here's my stab at the problem.
    use strict; use warnings; my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink brown); foreach my $drop_color (@drop) { my $index = 0; foreach my $color (@colors) { if ( $color eq $drop_color ) { splice (@colors, $index, 1); last; } $index++; } } print "@colors \n";
    Besides being clearer, this also works with n elements in whatever array, whereas your solution hardcodes two elements in the @drop.


    holli

    You can lead your users to water, but alas, you cannot drown them.

      It is probably worth noting here that $i, $j, $k, $l, and so on are only good loop iteration variables because of the sheer weight of the long-standing convention of using them that way.

      As far as I know, this convention originated with the C programming language, although the fact that I, J, K and some others default as integers in FORTRAN suggests that it is older than C. These variables are probably best used when your iteration variables will have integer values, as that is also a (weaker) long-standing convention. In the example that started this discussion, the iteration was over the positions in an array, which are integers in Perl. If you are iterating over the values of an array, as in the solution presented by holli above, you should use a descriptive iterator variable as holli did.

      The solution holli presented also illustrates another problem: algorithmic complexity. Nested loops are usually the slowest practical way to solve a problem, and when you see such a construction, you should consider if there is a better way, and explicitly checking two elements of @drop is really just an unrolled nested loop. (See also: Loop unrolling) On modern hardware, the issue will be unnoticeable until you are trying to process large amounts of data, and then your program will suddenly be horrendously slow. For more enlightenment on this, analyze the expected running time of the various solutions here and then run actual benchmarks with both the small data set given in the original assignment and some (much) larger generated data sets, with hundreds, thousands, tens of thousands, hundreds of thousands, and millions of items.

        I think usage of i, j, k for indexes is older, FORmulaTRANslator is oriented towards mathematical notation.

        However, in homework tasks for beginners, loop unrolling is less likely in the curriculum, and my guess is that it's all about that bass nested loops. All the other solutions given so far won't work if @drop has more or less that two elements…

Re: The error says the value is uninitialized, but it works anyway
by karlgoethebier (Abbot) on Aug 19, 2019 at 18:44 UTC

    A different solution AKA TMTOWTDI using Set::Scalar:

    Best regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: The error says the value is uninitialized, but it works anyway
by dbuckhal (Chaplain) on Aug 18, 2019 at 05:01 UTC
    Looks like you have been well taken care of regarding your inquiry, but I would like to add a cool use of each I recently discovered. Should fit well with your code assignment. You can use each to collect the indices of an array, or both indices and values. From the docs:
     each HASH
     each ARRAY
                When called on a hash in list context, returns a 2-element list
                consisting of the key and value for the next element of a hash. In
                Perl 5.12 and later only, it will also return the index and value
                for the next element of an array so that you can iterate over it;
                older Perls consider this a syntax error. When called in scalar
                context, returns only the key (not the value) in a hash, or the
                index in an array.
    
    So, using each, your code can go from:
    $ perl -e ' my @colors = qw(red green blue yellow pink purple brown); my $count = @colors; my @drop = qw(pink brown); my $num = 0; foreach $num (1..$count){ $num--; if ($colors[$num] eq $drop[0] or $colors[$num] eq $drop[1]){ splice (@colors, $num, 1); } } print "@colors \n"; '
    to:
    $ perl -e ' my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink brown); while ( my ($num, $val) = each @colors ) { if ($val eq $drop[0] or $val eq $drop[1]) { splice (@colors, $num, 1); } } print "@colors \n"; '
    both produce the following output:
    __output__ red green blue yellow purple

    I hope that was the correct answer! :)

      Unfortunately, it's still altering an array (or it might have been a hash) over which you're iterating and so still incorrect:

      c:\@Work\Perl\monks>perl -wMstrict -le "my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink purple); while ( my ($num, $val) = each @colors ) { if ($val eq $drop[0] or $val eq $drop[1]) { splice (@colors, $num, 1); } } print qq{@colors}; " red green blue yellow purple brown

      Update: each sez:

      If you add or delete a hash's elements while iterating over it, the effect on the iterator is unspecified; for example, entries may be skipped or duplicated--so don't do that.
      The same obviously applies to arrays. The each doc goes on to discuss a specific exception involving delete and hashes, but still no joy WRT arrays.


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

        Ahh, of course... thanks!

        adding "save" array, and "if" to "unless"...

        perl -wMstrict -e ' my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink purple); my @save = (); while ( my ($num, $val) = each @colors ) { unless ($val eq $drop[0] or $val eq $drop[1]) { push @save, $colors[$num]; } } print "@save \n"; ' __output__ red green blue yellow brown
Re: The error says the value is uninitialized, but it works anyway
by karthiknix (Sexton) on Aug 21, 2019 at 09:53 UTC

    The code uses splice to remove the elements from the array but the size of the array is not adjusted in the foreach loop. Below code would traverse the array based on the last index of the array. This works even after using warnings module without any error. Here I am adjusting the for loop count based on your splice.

    use warnings; print "hello world\n"; my @colors = qw(red green blue yellow pink purple brown); my @drop = qw(pink brown); my $num = 0; for (0..$#colors){ if ($colors[$num] eq $drop[0] or $colors[$num] eq $drop[1]){ + splice (@colors, $num, 1); $num--; } $num++; } print "@colors \n";
      if ($colors[$num] eq $drop[0] or $colors[$num] eq $drop[1]){ + splice (@colors, $num, 1); $num--; } $num++;
      Just as a nitpick: I think it's better to
      if ($colors[$num] eq $drop[0] or $colors[$num] eq $drop[1]) { splice (@colors, $num, 1); } else { $num++; }
      because find it's somehow dopey (for lack of a better word) to decrement just to increment immediately afterwards.
      With "else" it's clear that you need to increment only if the condition isn't met.
Re: The error says the value is uninitialized, but it works anyway
by Anonymous Monk on Aug 19, 2019 at 11:24 UTC
    The homework question just defines @colors and @drop and says to remove the things in drop from colors, that's it.

    If perl was math we could say:

    
    @colors - @drop = @colors
    
    
    But perl is perl so we have to say:
    
    @colors = -@drop + @colors
    
    
    Perl can do that, like this:
    
    @colors = (miniature @drop program here) @colors
    
    
    The flow of information goes:
          __<________________________<__
       #4/                              \#3
    @colors = (miniature > program > here) @colors
             #2\__<___________________<___/#1
    
    
    The mini program goes inside map {} like:
    
    @colors = map { my $x = join '|', @drop; /$x/ ? () : $_ } @colors;
    
    
    Unrolled:
    
    @colors =
      map {
        my $x = join '|', @drop; # make a regex
        /$x/ ? () : $_           # simpler than it looks
      }
    @colors;
    
    
      my $x = join '|', @drop; /$x/ ? () : $_

      While this may work for the example in the root node, it's of course pretty inefficient to build the regex on every iteration of the loop. And, the regex will match more than fixed strings, because it doesn't escape metacharacters and isn't anchored - see Building Regex Alternations Dynamically (as has been mentioned several times already). Plus, map { ... ? () : $_ } is perhaps better written as grep { not ... }.

      Unrolled: ...

      <quibble>
      Use of this word suggests "loop unrolling", but there's none of that going on. Instead, the source is being re-formatted and spiced up with some comments.
      </quibble>


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

Re: The error says the value is uninitialized, but it works anyway
by Anonymous Monk on Aug 19, 2019 at 17:30 UTC

    Incidentally, if there is a possibility that a value could be undef, you can use the defined() function to reliably test for this. You can use "short-circuit expression evaluation" to great advantage here:

    if (defined($foo) && ($foo eq $bar)) ...

    If defined() returns False, evaluation of the expression will terminate immediately and the eq will not be performed. This will avoid any ambiguity that might be caused by Perl's string-casting, and it also alerts the subsequent programmers that undef is a possibility.