Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Code Works But Not Sure How

by RichHamilton (Novice)
on Feb 11, 2018 at 01:14 UTC ( #1208937=perlquestion: print w/replies, xml ) Need Help??
RichHamilton has asked for the wisdom of the Perl Monks concerning the following question:

I could use some guidance on some code that appears to work, but I'm not sure exactly how it works. I know I need to read of on references and some on arrays for starters. What the code is supposed to do is take an array and just add single quotes around each element. I can print out the data via Data::Dumper and it appears to be fine. However, I'm not exactly sure how I got the make_menu_items subroutine to work. So here's the code:

package MyClass; use strict; use warnings; sub new { my $class = shift; my $self = { menu_items => shift, }; make_menu_items(); bless $self,$class; return $self; } sub make_menu_items { my $self = shift; my $items = $#{$self->{menu_items}}; for (my $item = 0; $item < $items; $item++) { $self->{menu_items}[$item]="'$self->{menu_items}[$item]'"; } } 1;

So basically, the class is initialized with an array reference like this:

use MyClass; use strict; use warnings; my @array = ("1","Option 1","2","Option 2","3","Option 3","4","Option +4"); my $obj = MyClass->new(\@array);

So the output of data dumper looks like:

$VAR1 = bless( { 'menu_items' => [ '1', 'Option 1', '2', 'Option 2', '3', 'Option 3', '4', 'Option 4' ] }, 'MyClass' );

What I thought I was doing was getting the size of the of the array that is the value of menuitems and then loop through the array. But  $items = $#{$self->{menu_items}} returns a -1. I had thought this would have returned the number of elements in the array. I have no clue how it (or if it even) looped through the array that is the value of menuitems.

I know I could have done defined the array using something like @array=qq('1' 'Option 1' '2' 'Option 2' '3' 'Option 3' '4' 'Option 4'); and that have worked fine. But I thought I could just loop through menuitems and just use the array as coded.

I'd appreciate any guidance on the code any more experienced Monks can provide. Thanks in advance for your guidance.

Replies are listed 'Best First'.
Re: Code Works But Not Sure How
by 1nickt (Monsignor) on Feb 11, 2018 at 01:50 UTC

    Hi, it looks like you might be trying to make a drop-down menu. If so, consider using a templating system (eg Template) instead. You should be able to pass your data to the rendering engine without worrying about quoting the values.

    Note that Data::Dumper quotes the values it prints by default, so the quotes you are seeing are not the ones you hope to be introducing. That's why your program seems to be working when it's not. See what I mean about letting the presentation layer worry about quoting?

    The main issue is that your array is not passed to the subroutine, since you give no arguments in the call. It's not yet blessed so calling one of its subs won't cause the object to be the first argument. You can see this if you Dumper $self in the subroutine. You would need to pass $self explicitly, or just pass the arrayref.

    The reason you get a value of -1 for the last index of the array is that it's an empty list. From perldata:

    The following are equivalent:

    @whatever = ();
    $#whatever = -1;.

    That said, if you want to quote the values in your array, use map:

    my @array = ("1","Option 1","2","Option 2","3","Option 3","4","Option +4"); my @quoted = map { "'$_'" } @array;

    Finally, your loop in the sub is over-complicated. I showed a loop above, nicely packaged in a map call. If you really wanted to do it by hand, it would be more like this:

    sub make_menu_items { my $items = shift; my @quoted; for my $item ( @{ $items } ) { push @quoted, "'$item'"; } return \@quoted; }
    Update: fixed typos with map and for, thanks jwkrahn and AnomalousMonk and johngg for the correction.

    Hope this helps!


    The way forward always starts with a minimal test.
      The main issue is that your array is not passed to the subroutine, since you give no arguments in the call.

      RichHamilton: I just wanted to highlight this since it is the first thing that jumped out at me when I skimmed your code.

      Inside new, you call make_menu_items();, without arguments and not as a method call, so Perl won't pass $self as the first argument. One way to change this is by blessing the object first, i.e. bless $self, $class; $self->make_menu_items();.

      So in the code you showed, in the call to sub make_menu_items, $self starts out as undef. The reason the code doesn't fail is autovivification (here's one thread of several on that). As 1nickt said, if you dump $self, you'll see that before the line "my $items = $#{$self->{menu_items}};" it is undef, but after that line it magically becomes { menu_items => [] }, because you first accessed it as a hashref with $self->{...}, and then accessed the hash key menu_items as an array reference with $#{...}.

        Sounds like I need to go though Chapter 4 of Conway's book again. :) He talks about autovivification there. And I'll check out the thread too. Thanks!
      1Nickt You're right I did go back at the data dumper and I see what you're talking about. What it should show is:
      $VAR1 = bless( { 'menu_items' => [ '\'1\'', '\'Option 1\'', '\'2\'', '\'Option 2\'', '\'3\'', '\'Option 3\'', '\'4\'', '\'Option 4\'' ], 'number_of_items' => 8 }, 'MyClass' );
      I like the idea of using map. I did figure out how I can access the values using:
      package MyClass; use strict; use warnings; sub new { my $class = shift; my $self = { number_of_items => shift, menu_items => shift, }; make_menu_items(); bless $self,$class; return $self; } sub make_menu_items { my $items = shift; for (my $i=0; $i < $items->{number_of_items}; $i++) { $items->{menu_items}[$i] = "'$items->{menu_items}[$i]'"; } } 1;
      However, I wind up doing this to get single quotes in, but then I have to pass the array size.
      my @array = ("1","Option 1","2","Option 2","3","Option 3","4","Option +4"); my $array_size = @array; my $obj = MyClass->new($array_size,\@array);
      This works more or less. Although I get Use of uninitialized value in numeric lt (<) at MyClass.pm line 20. But It seems like I should be able to get the number of elements in the array in loop through them. What's making it hard for me is making the changes to menu_items instead of @array. Mapping it would be ideal. I could do after declaring the array, but it would sure be nice to do it as part of the calls. Using map would defiantly be cleaner. I'll keep chugging away on it and see what I can come up with. You're close on the purpose of the code. I'm working on some code to work with dialog on Linux boxes. I had written some code a while back, but didn't implement a menubox. Now I have a need for it so I'm trying to finish it. I could probably just use UI::Dialog. But I figure since I'm learning I might as well finish this. Even though probably coded as well as UI::Diaglog.

        The code you showed still does not work because it suffers from the problem 1nickt and I explained: With "make_menu_items();", you're not giving it any arguments, so inside make_menu_items, $items is still undef, this is the reason you get the "Use of uninitialized value" warning. Again, use Data::Dumper or Data::Dump to actually look at the values you are working with, see the Basic debugging checklist.

        but then I have to pass the array size.

        While necessary in languages like C, it's not necessary in Perl because you can easily get the size of an array via scalar(@array), or the index of its last element via $#array. For an array reference like the one you're passing to new, you can get its size via scalar(@{$arrayref}), or the index of its last element via $#{$arrayref}. There are also shorter ways to express the size of an array, but the aforementioned should work in just about any situation.

        Update: Plus, another central point is that Perl provides the foreach loop, where you don't even need to know the size of the array you are iterating over, and that kind of loop is almost always better. Plus, it "aliases" the iterator variable to the actual array item, so you can modify array items through it. The following prints "("Laziness is cool", "Impatience is cool", "Hubris is cool")":

        use warnings; use strict; use Data::Dump; my @array = ('Laziness', 'Impatience', 'Hubris'); for my $item (@array) { $item = "$item is cool"; } dd @array;

        The items have their own associated array index so I don't see you really need to store sequential numbers as well

        #!/usr/bin/perl package MyClass; use strict; use warnings; sub new { my $class = shift; my $self = { menu_items => _quote_items(shift), }; bless $self,$class; } sub _quote_items { my $ar = shift; $_ = "'$_'" for @$ar; return $ar; } package main; use strict; use warnings; use Data::Dumper; my @array = ('Option 0','Option 1','Option 2','Option 3','Option 4'); my $obj = MyClass->new(\@array); print Dumper $obj;
        poj
      1nickt, I had to reread your post after reading haukex's. I focused on the suggested function call in your post. So that last reply what I came up with is still using autovivification. :) Now things are starting to make sense.
Re: Code Works But Not Sure How
by johngg (Canon) on Feb 11, 2018 at 11:44 UTC

    If you are numbering your menu items upwards from '1' this might be a simpler way to initialise your options.

    use strict; use warnings; use feature qw{ say }; my @options = ( q{Option 1}, q{Option 2}, q{Option 3}, q{Option 4}, ); my $ct; @options = map { ++ $ct; qq{'$ct'}, qq{'$_'} } @options; say for @options;

    The output.

    '1' 'Option 1' '2' 'Option 2' '3' 'Option 3' '4' 'Option 4'

    I hope this is of interest.

    Cheers,

    JohnGG

      Yes, I'm interested in this. At my skill level map examples are very helpful. Thanks!
Re: Code Works But Not Sure How
by jwkrahn (Monsignor) on Feb 11, 2018 at 05:27 UTC

    1nickt: my @quoted = map { '$_' } @array; should be my @quoted = map { "'$_'" } @array; or my @quoted = map "'$_'", @array;.

    RichHamilton, you have an off-by-one error here:

    my $items = $#{$self->{menu_items}}; for (my $item = 0; $item < $items; $item++) ^^^

    $#{$self->{menu_items}} is the index of the last element of the array @{$self->{menu_items}}, and you are traversing the array from zero to the element just before the last element, so the last element of the array will not be modified.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (1)
As of 2018-08-19 19:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Asked to put a square peg in a round hole, I would:









    Results (187 votes). Check out past polls.

    Notices?