I'm working on a Perl script that requires some basic menu functionality. Ultimately I would like each menu to have a few options and then the option to either return to the previous menu or exit.
example:
This is a menu:
Choice 1
Choice 2
Return to previous menu
Exit
Select an option:
I currently have a menu subroutine making the menus, but there is no functionality allowing it to go back to the previous menu.
sub menu
{
for (;;) {
print "--------------------\n";
print "$_[0]\n";
print "--------------------\n";
for (my $i = 0; $i < scalar(#{ $_[1]}); $i++) {
print $i + 1, "\.\t ${ $_[1] }[$i]\n";
}
print "\n?: ";
my $i = <STDIN>; chomp $i;
if ($i && $i =~ m/[0-9]+/ && $i <= scalar(#{ $_[1]})) {
return ${ $_[1] }[$i - 1];
} else {
print "\nInvalid input.\n\n";
}
}
}
# Using the menu
my $choice1 = menu('Menu1 header', \#list_of_choices1);
# I would like this menu to give the option to go back to
# the first menu to change $choice1
my $choice2 = menu('Menu2 header', \#list_of_choices2);
I don't want to hard code all of the menus and use if/elsif statements for all of the processing so I turned the menu into a function.
My menus currently look like this...
Menu Header:
Choice1
Choice2
Choice3
?: (Enter input here)
This solution still doesn't allow the user to go back to the previous menu or exit though. I was considering making a menu class to handle the menus, but I am still not very good with object oriented Perl. This is a small program with only a few menus so using a complex menu building module may be overkill. I would like to keep my code as light as possible.
EDIT:
Thanks for the quick responses! However there is still an issue. When I select an option from "Menu1" and it progresses to "Menu2" I would like the save the choice from "Menu1" for later use:
Menu1:
Choice1 <-- store value if selected and go to next menu
Choice2 <-- ...
Exit <-- quit
Menu2:
Choice1 <-- store value if selected and go to next menu
Choice2 <-- ...
Back <-- go back to previous menu to reselect value
Exit <-- quit
Selecting either Choice1 or Choice2 should store a value in a variable for later use and progress to the next menu. Then if you choose to go back to the first menu from Menu2, it will give you the option to reselect your choice and redefine the variable. I'm trying to avoid using global variables which makes this quite difficult.
After progressing through all of the menus and setting the values of all of these variables, I want to run a subroutine to process all of the choices and print a final output.
sub main () {
# DO MENU STUFF HERE
# PROCESS RESULTS FROM MENU CHOICES
my $output = process($menu1_choice, $menu2_choice, $menu3_choice, ... );
}
Also if anyone has an object oriented approach to this using classes or some other data structure, although it may be overkill, I would still love to see it and try to wrap my head around the idea!
You could use a module such as Term::Choose:
use Term::Choose qw( choose );
my $submenus = {
menu1 => [ qw( s_1 s_2 s_3 ) ],
menu2 => [ qw( s_4 s_5 s_6 s_7) ],
menu3 => [ qw( s_8 s_9 ) ],
};
my #menus = ( qw( menu1 menu2 menu3 ) );
my $mm = 0;
MAIN: while ( 1 ) {
my $i = choose(
[ undef, #menus ],
{ layout => 3, undef => 'quit', index => 1, default => $mm }
);
last if ! $i;
if ( $mm == $i ) {
$mm = 0;
next MAIN;
}
else {
$mm = $i;
}
$i--;
SUB: while ( 1 ) {
my $choice = choose(
[ undef, #{$submenus->{$menus[$i]}} ],
{ layout => 3, undef => 'back' }
);
last SUB if ! defined $choice;
say "choice: $choice";
}
}
If you don't want to go full OO with this, a simple way that you can make this a lot more flexible is to allow each menu choice to control how it is executed. Let's say each menu has an array of hashes that contain the menu text and a coderef that implements what the menu does.
use strict;
use warnings;
sub menu {
my #items = #_;
my $count = 0;
foreach my $item( #items ) {
printf "%d: %s\n", ++$count, $item->{text};
}
print "\n?: ";
while( my $line = <STDIN> ) {
chomp $line;
if ( $line =~ m/\d+/ && $line <= #items ) {
return $items[ $line - 1 ]{code}->();
}
print "\nInvalid input\n\n?: ";
}
}
my #menu_choices;
my #other_menu_choices;
#menu_choices = (
{ text => 'do something',
code => sub { print "I did something!\n" } },
{ text => 'do something else',
code => sub { print "foobar!\n" } },
{ text => 'go to other menu',
code => sub { menu( #other_menu_choices ) } }
);
#other_menu_choices = (
{ text => 'go back',
code => sub { menu( #menu_choices ) } }
);
menu( #menu_choices );
The menu subroutine takes an array of options, and each option "knows" how to perform its own action. If you want to switch between menus, the menu option just calls menu again with a different list of options, as in the "go back" example from #other_menu_choices. This make linking between menus very easy and it's also easy to add exit options and such.
To keep this code clean and readable, for anything other than trivial menu actions, use a named reference to a subroutine instead of an anonymous subroutine reference. For example:
#another_menu_options = (
{ text => 'complicated action'
code => \&do_complicated_action
}
);
sub do_complicated_action {
...
}
After a few more months of programming with Perl I learned much more about how to deal with objects and wrote a simple object oriented menu building module based off of friedo's answer.
# Menu.pm
#!/usr/bin/perl
package Menu;
use strict;
use warnings;
# Menu constructor
sub new {
# Unpack input arguments
my $class = shift;
my (%args) = #_;
my $title = $args{title};
my $choices_ref = $args{choices};
my $noexit = $args{noexit};
# Bless the menu object
my $self = bless {
title => $title,
choices => $choices_ref,
noexit => $noexit,
}, $class;
return $self;
}
# Print the menu
sub print {
# Unpack input arguments
my $self = shift;
my $title = $self->{title };
my #choices = #{$self->{choices}};
my $noexit = $self->{noexit };
# Print menu
for (;;) {
# Clear the screen
system 'cls';
# Print menu title
print "========================================\n";
print " $title\n";
print "========================================\n";
# Print menu options
my $counter = 0;
for my $choice(#choices) {
printf "%2d. %s\n", ++$counter, $choice->{text};
}
printf "%2d. %s\n", '0', 'Exit' unless $noexit;
print "\n?: ";
# Get user input
chomp (my $input = <STDIN>);
print "\n";
# Process input
if ($input =~ m/\d+/ && $input >= 1 && $input <= $counter) {
return $choices[$input - 1]{code}->();
} elsif ($input =~ m/\d+/ && !$input && !$noexit) {
print "Exiting . . .\n";
exit 0;
} else {
print "Invalid input.\n\n";
system 'pause';
}
}
}
1;
Using this module you can build menus and link them together relatively easy. See example of usage below:
# test.pl
#!/usr/bin/perl
use strict;
use warnings;
use Menu;
my $menu1;
my $menu2;
# define menu1 choices
my #menu1_choices = (
{ text => 'Choice1',
code => sub { print "I did something!\n"; }},
{ text => 'Choice2',
code => sub { print "I did something else!\n"; }},
{ text => 'Go to Menu2',
code => sub { $menu2->print(); }},
);
# define menu2 choices
my #menu2_choices = (
{ text => 'Choice1',
code => sub { print "I did something in menu 2!\n"; }},
{ text => 'Choice2',
code => sub { print "I did something else in menu 2!\n"; }},
{ text => 'Go to Menu1',
code => sub { $menu1->print(); }},
);
# Build menu1
$menu1 = Menu->new(
title => 'Menu1',
choices => \#menu1_choices,
);
# Build menu2
$menu2 = Menu->new(
title => 'Menu2',
choices => \#menu2_choices,
noexit => 1,
);
# Print menu1
$menu1->print();
This code will create a simple menu with a submenu. Once in the submenu you can easily go back to the previous menu.
Thanks for all of the great answers! They really helped me figure this out and I don't think i would have ended up with such a good solution without all the help!
A BETTER SOLUTION:
Say goodbye to those ugly arrays of hashes!
Some of the code internal to the Menu.pm and Item.pm modules may look slightly confusing, but this new design makes the interface of building the menus themselves much cleaner and more efficient.
After some careful code reworking and making the individual menu items into their own objects I was able to create a much cleaner interface for creating menus. Here is my new code:
This is a test script showing an example of how to use the modules to build menus.
# test.pl
#!/usr/bin/perl
# Always use these
use strict;
use warnings;
# Other use statements
use Menu;
# Create a menu object
my $menu = Menu->new();
# Add a menu item
$menu->add(
'Test' => sub { print "This is a test\n"; system 'pause'; },
'Test2' => sub { print "This is a test2\n"; system 'pause'; },
'Test3' => sub { print "This is a test3\n"; system 'pause'; },
);
# Allow the user to exit directly from the menu
$menu->exit(1);
# Disable a menu item
$menu->disable('Test2');
$menu->print();
# Do not allow the user to exit directly from the menu
$menu->exit(0);
# Enable a menu item
$menu->enable('Test2');
$menu->print();
The Menu.pm module is used to build menu objects. These menu objects can contain multiple Menu::Item objects. The objects are stored in an array so their order is preserved.
# Menu.pm
#!/usr/bin/perl
package Menu;
# Always use these
use strict;
use warnings;
# Other use statements
use Carp;
use Menu::Item;
# Menu constructor
sub new {
# Unpack input arguments
my ($class, $title) = #_;
# Define a default title
if (!defined $title) {
$title = 'MENU';
}
# Bless the Menu object
my $self = bless {
_title => $title,
_items => [],
_exit => 0,
}, $class;
return $self;
}
# Title accessor method
sub title {
my ($self, $title) = #_;
$self->{_title} = $title if defined $title;
return $self->{_title};
}
# Items accessor method
sub items {
my ($self, $items) = #_;
$self->{_items} = $items if defined $items;
return $self->{_items};
}
# Exit accessor method
sub exit {
my ($self, $exit) = #_;
$self->{_exit} = $exit if defined $exit;
return $self->{_exit};
}
# Add item(s) to the menu
sub add {
# Unpack input arguments
my ($self, #add) = #_;
croak 'add() requires name-action pairs' unless #add % 2 == 0;
# Add new items
while (#add) {
my ($name, $action) = splice #add, 0, 2;
# If the item already exists, remove it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
splice #{$self->{_items}}, $index, 1;
}
}
# Add the item to the end of the menu
my $item = Menu::Item->new($name, $action);
push #{$self->{_items}}, $item;
}
return 0;
}
# Remove item(s) from the menu
sub remove {
# Unpack input arguments
my ($self, #remove) = #_;
# Remove items
for my $name(#remove) {
# If the item exists, remove it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
splice #{$self->{_items}}, $index, 1;
}
}
}
return 0;
}
# Disable item(s)
sub disable {
# Unpack input arguments
my ($self, #disable) = #_;
# Disable items
for my $name(#disable) {
# If the item exists, disable it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
$self->{_items}->[$index]->active(0);
}
}
}
return 0;
}
# Enable item(s)
sub enable {
# Unpack input arguments
my ($self, #enable) = #_;
# Disable items
for my $name(#enable) {
# If the item exists, enable it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
$self->{_items}->[$index]->active(1);
}
}
}
}
# Print the menu
sub print {
# Unpack input arguments
my ($self) = #_;
# Print the menu
for (;;) {
system 'cls';
# Print the title
print "========================================\n";
print " $self->{_title}\n";
print "========================================\n";
# Print menu items
for my $index(0 .. $#{$self->{_items}}) {
my $name = $self->{_items}->[$index]->name();
my $active = $self->{_items}->[$index]->active();
if ($active) {
printf "%2d. %s\n", $index + 1, $name;
} else {
print "\n";
}
}
printf "%2d. %s\n", 0, 'Exit' if $self->{_exit};
# Get user input
print "\n?: ";
chomp (my $input = <STDIN>);
# Process user input
if ($input =~ m/^\d+$/ && $input > 0 && $input <= scalar #{$self->{_items}}) {
my $action = $self->{_items}->[$input - 1]->action();
my $active = $self->{_items}->[$input - 1]->active();
if ($active) {
print "\n";
return $action->();
}
} elsif ($input =~ m/^\d+$/ && $input == 0 && $self->{_exit}) {
exit 0;
}
# Deal with invalid input
print "\nInvalid input.\n\n";
system 'pause';
}
}
1;
The Item.pm Module must be stored in a subfolder called "Menu" In order for it to be referenced properly. This module lets you create Menu::Item objects that contain a name and a subroutine reference. These objects will be what the user selects from in the menu.
# Item.pm
#!/usr/bin/perl
package Menu::Item;
# Always use these
use strict;
use warnings;
# Menu::Item constructor
sub new {
# Unpack input arguments
my ($class, $name, $action) = #_;
# Bless the Menu::Item object
my $self = bless {
_name => $name,
_action => $action,
_active => 1,
}, $class;
return $self;
}
# Name accessor method
sub name {
my ($self, $name) = #_;
$self->{_name} = $name if defined $name;
return $self->{_name};
}
# Action accessor method
sub action {
my ($self, $action) = #_;
$self->{_action} = $action if defined $action;
return $self->{_action};
}
# Active accessor method
sub active {
my ($self, $active) = #_;
$self->{_active} = $active if defined $active;
return $self->{_active};
}
1;
This design is a vast improvement over my previous design and makes creating menus much easier and cleaner.
Let me know what you think.
Any comments, thoughts, or improvement ideas?
Following is one approach. Each choice has an associated subroutine. When the choice is made, the corresponding subroutine is called. Here I am using anonymous subroutines but you can also use references to named subroutines.
use warnings; use strict;
sub menu {
my $args = shift;
my $title = $args->{title};
my $choices = $args->{choices};
while (1) {
print "--------------------\n";
print "$title\n";
print "--------------------\n";
for (my $i = 1; $i <= scalar(#$choices); $i++) {
my $itemHeading = $choices->[$i-1][0];
print "$i.\t $itemHeading\n";
}
print "\n?: ";
my $i = <STDIN>; chomp $i;
if ($i && $i =~ m/[0-9]+/ && $i <= scalar(#$choices)) {
&{$choices->[$i-1][1]}();
} else {
print "\nInvalid input.\n\n";
}
}
}
my $menus = {};
$menus = {
"1" => {
"title" => "Menu 1 header",
"choices" => [
[ "Choice 1" , sub { print "Choice 1 selected"; }],
[ "Choice 2" , sub { print "Choice 2 selected"; }],
[ "Menu 2" , sub { menu($menus->{2}); }],
[ "Exit" , sub { exit; }],
],
},
"2" => {
"title" => "Menu 2 header",
"choices" => [
[ "Choice 3" , sub { print "Choice 3 selected"; }],
[ "Choice 4" , sub { print "Choice 4 selected"; }],
[ "Menu 1" , sub { menu($menus->{1}); }],
[ "Exit" , sub { exit; }],
],
},
};
menu($menus->{1});
Thanks everyone for the responses! All three of the responses were helpful in finally coming up with my solution. I decided to go with the Term::Choose module, (Thanks sid_com for the idea). My menu structure was different than you had originally suggested and it took quite a while of scratching my head to figure out how to make it do exactly what I wanted. Hopefully this solution will help someone else out who encounters a similar problem.
I constructed the menu as shown below:
(I have replaced my variables with more general names so it is easier to follow)
#!/usr/bin/perl
use strict;
use warnings;
use Term::Choose qw(choose);
my #CHOICES1 = ('A','B','C');
my #CHOICES2 = ('1','2','3');
my #CHOICES3 = ('BLUE','YELLOW','GREEN');
# function to use the choices
sub some_function {
print "THIS IS SOME FUNCTION!\n";
print "Choice 1 is $_[0]\n";
print "Choice 2 is $_[1]\n";
print "Choice 3 is $_[2]\n";
print "Have a nice day! :)\n";
}
sub main() {
# clear the screen
# (for some reason the build in screen clear
# for the module was not working for me)
system ('cls');
# create menu object
my $menu = new Term::Choose();
# menu 1
for (;;) {
my $choice1 = $menu->choose(
[#CHOICES1, undef],
{
prompt => 'Select a choice1:',
undef => 'Exit',
layout => 3,
}
);
last if ! $choice1;
# submenu 1
for (;;) {
my $choice2 = $menu->choose(
[#CHOICES2, undef],
{
prompt => 'Select a choice2:',
undef => 'Back',
layout => 3,
}
);
last if ! $choice2;
# submenu2
for (;;) {
my $choice3 = $menu->choose(
[#CHOICES3, undef],
{
prompt => 'Select a choice3:',
undef => 'Back',
layout => 3,
}
);
last if ! $choice3;
# function operating on all choices
some_function($choice1, $choice2, $choice3);
return;
}
}
}
}
main();
I'm still very new to object oriented Perl so this took a very long time to figure out and it might not be perfect, but it gets the job done. Let me know if you have any ideas or improvements!
I have found this old module without any perldoc in my perl modules...
Please, give it a try...
#!/usr/bin/perl
BEGIN { $Curses::OldCurses = 1; }
use Curses;
use perlmenu;
&menu_init(0,"Select an Animal"); # Init menu
&menu_item("Collie","dog"); # Add item
&menu_item("Shetland","pony"); # Add item
&menu_item("Persian","cat"); # Add last item
$sel = &menu_display("Which animal?"); # Get user selection
if ($sel eq "dog") {print "Its Lassie!\n";}
Related
I was given the assignment to implement a linked list in perl without using the built-in push,pop,shift and unshift. This is my first time learning perl, coming from c++ and java, this is what I came up with:
#!/usr/bin/perl
sub node {
my (#value) = #_;
sub get {
$next;
}
sub push {
#my $next = \#_;
if(defined($next))
{
$next->push(#_);
}
else
{
my $next = \#_;
}
}
sub size {
if(defined($next))
{
$next->size($_[0]);
}
else
{
$_[0]+1;
}
}
sub myprint {
print "$_[0]: ";
foreach (#value) {
print "$_, ";
}
print "\n";
if(defined($next)) {
$next->print($_[0]+1);
}
}
}
while(!defined($done))
{
print "what do you want to do?\n";
print "1 -- push\n";
print "2 -- print nodes\n";
print "3 -- pop\n";
print "4 -- quit\n";
my $val = <STDIN>;
if ($val == 1)
{
print "Type something: ";
$input = <STDIN>;
if(defined($top))
{
$top->push(node($input));
}
else
{
$top = node($input);
}
}
elsif ($val == 2)
{
if(defined($top))
{
$top->myprint(1);
}
}
elsif ($val == 3)
{
if(defined($top))
{
if(defined($top->next))
{
$top=$top->next;
}
}
}
elsif ($val == 4)
{
$done=true;
}
else
{
print "Invalid option\n";
}
}
output:
what do you want to do?
1 -- push
2 -- print nodes
3 -- pop
4 -- quit
1
Type something: q
what do you want to do?
1 -- push
2 -- print nodes
3 -- pop
4 -- quit
1
Type something: w
Can't call method "push" without a package or object reference at ./linkedlistattempt1.pl line 76, <STDIN> line 4.
I'm guessing the "->" operator can only be used with a module or package.
I haven't gotten around to testing the other methods, I'm still working on push. I feel like the best way of doing this is to simply have a holder sub like `sub holder { $value = \#_; } but I don't understand how I would (could?) add more variables, like the next node, hence the sub within a sub design. So without the help of perl's built in functions, how would I do this?
Its important to mention I'm interested in methods that can run on the older versions, going down to 5.10. Most if not all of the tutorials are showing stuff for 5.16 or 5.18
A typical Perl implementation using classic Perl OO would look something like this. Read the man pages perlootut and perlobj to learn how it works.
#!/usr/bin/perl
use strict;
use warnings;
package LinkedList::Node;
# Constructor.
sub new {
my ($class, $item) = #_;
my $self = { item => $item };
return bless($self, $class);
}
# Read-only accessor.
sub item {
my $self = shift;
return $self->{item};
}
# Read-write accessor.
sub next {
my $self = shift;
my $next = $self->{next};
if (#_ > 0) {
$self->{next} = shift;
}
return $next;
}
package LinkedList;
# Constructor. Creates an empty linked list.
sub new {
my $class = shift;
return bless({}, $class);
}
# Read-only accessor.
sub head {
my $self = shift;
return $self->{head};
}
# Insert an item at the beginning.
sub push {
my ($self, $item) = #_;
my $node = LinkedList::Node->new($item);
my $head = $self->{head};
if ($head) {
$node->next($head);
}
$self->{head} = $node;
}
package main;
my $list = LinkedList->new;
$list->push(2);
$list->push(5);
$list->push(9);
for (my $node = $list->head; $node; $node = $node->next) {
print($node->item, "\n");
}
I wrote the following Perl Class:
package Menu;
use strict;
my #MENU_ITEMS;
my $HEADER = "Pick one of the options below\n";
my $INPUT_REQUEST = "Type your selection: ";
sub new {
my $self = {};
$self->{ITEM} = undef;
$self->{HEADER} = undef;
$self->{INPUT_REQUEST} = undef;
bless($self);
return $self;
}
sub setHeader {
my $self = shift;
if(#_) { $self->{HEADER} = shift }
$HEADER = $self->{HEADER}."\n";
}
sub setInputRequest {
my $self = shift;
if(#_) { $self->{INPUT_REQUEST} = shift }
$INPUT_REQUEST = $self->{INPUT_REQUEST}." ";
}
sub addItem {
my $self = shift;
if(#_) { $self->{ITEM} = shift }
push(#MENU_ITEMS, $self->{ITEM});
}
sub getMenu {
my $formatted_menu .= $HEADER;
my $it=1;
foreach(#MENU_ITEMS) {
$formatted_menu.=$it.". ".$_."\n";
$it++
}
$formatted_menu.=$INPUT_REQUEST;
return $formatted_menu;
}
1;
If I call the following perl script:
#!/usr/bin/perl
use strict;
use Menu;
my $menu = Menu->new();
$menu->addItem("First Option");
$menu->addItem("Second Option");
print $menu->getMenu;
I'll get the following output:
Pick one of the options below
1. First Option
2. Second Option
Type your selection:
I'd like to modify given class in a way that I can pass a second argument to the method addItem()
something like:
$menu->addItem("First Option", &firstOptionFunction());
and if and only if First Option is selected, then $firstOptionFunction is executed.
Is there any way to achieve such behavior in Perl?
Thanks!
You would want to pass a reference to the subroutine.
$menu->addItem("First Option", \&firstOptionFunction);
And your addItem method might look like this:
sub addItem { ## your logic may vary
my ( $self, $option, $code ) = #_;
if ( $option eq 'First Option' ) {
$code->();
}
$self->{ITEM} = $option;
push #MENU_ITEMS, $option;
return;
}
As you mentioned in the comments, you might want to not pass the subroutine as a reference, but rather store it somewhere else. Something like this might work:
sub new {
my $class = shift;
my $self = bless {}, $class;
$self->{f_o_code} = \&firstOptionFunction; ## use a better name than f_o_code
return $self;
}
## add your other methods
sub addItem { ## your logic may vary
my ( $self, $option ) = #_;
if ( $option eq 'First Option' ) {
$self->{f_o_code}->();
}
$self->{ITEM} = $option;
push #MENU_ITEMS, $option;
return;
} ## call like $menu->addItem( 'First Option' );
I want to build a bunch of Perl subrotines that all have the same template if elsif elsif else that takes a decision based on a factor variable. Here's an example of subroutine template:
sub get_age{
my $factor=shift;
if ($factor == 1 ){ print "do something" }
elsif ($factor == 2 ){ print "do somthing2" }
elsif ($factor == 3 ){ print "do somthing3" }
elsif ($factor == 4 ){ print "do somthing4" }
else { print "error" }
}
I am wondering if there some design pattern on Perl to replace the if else condition with more elegant solution which easy to maintain in the future specifically if I need to change some of the conditions or delete some of it?
A couple of people have mentioned a dispatch table. There are two things and it's nice to keep them apart sometimes. There's the list of possible things that could happen, and the thing that makes them happen. If you couple the two, you're stuck with your solution. If you keep them separate, you have more flexibility later.
The dispatch table specifies the behavior as data instead of program structure. Here's two different ways to do it. With your example you have integers and something like that might use an array to store things. The hash example is the same idea but looks up the behavior slightly differently.
Also notice that I factor out the print. When you have repeated code like that, try to move the repeated stuff up a level.
use v5.10;
foreach my $factor ( map { int rand 5 } 0 .. 9 ) {
say get_age_array( $factor );
}
my #animals = qw( cat dog bird frog );
foreach my $factor ( map { $animals[ rand #animals ] } 0 .. 9 ) {
say get_age_hash( $factor );
}
sub get_age_array {
my $factor = shift;
state $dispatch = [
sub { 'Nothing!' }, # index 0
sub { "Calling 1" },
sub { 1 + 1 },
sub { "Called 3" },
sub { time },
];
return unless int $factor <= $#$dispatch;
$dispatch->[$factor]->();
}
sub get_age_hash {
my $factor = shift;
state $dispatch = {
'cat' => sub { "Called cat" },
'dog' => sub { "Calling 1" },
'bird' => sub { "Calling 2, with extra" },
};
return unless exists $dispatch->{$factor};
$dispatch->{$factor}->();
}
Update: Make sure you read brian's comment below; basically, it's better to use for instead of given, due to various issues he comments on in his link. I've updated my advice to incorporate his improvements, which he outlines in Use for() instead of given():
If you're on perl 5.10 or newer, given/when is the magic pair you are looking for, but you really should use for/when instead.. Here's an example:
use strict;
use warnings;
use feature qw(switch say);
print 'Enter your grade: ';
chomp( my $grade = <> );
for ($grade) {
when ('A') { say 'Well done!' }
when ('B') { say 'Try harder!' }
when ('C') { say 'You need help!!!' }
default { say 'You are just making it up!' }
}
just making things shorter:
sub get_age1 {
my $age = shift;
$age == 1 ? print "do something" :
$age == 2 ? print "do somthing2" :
$age == 3 ? print "do somthing3" :
$age == 4 ? print "do somthing4" :
print "error"
}
this one makes more sense if the condition can be best expressed as a regex:
sub get_age2 {
for (shift) {
if (/^ 1 $/x) {print "do something"}
elsif (/^ 2 $/x) {print "do somthing2"}
elsif (/^ 3 $/x) {print "do somthing3"}
elsif (/^ 4 $/x) {print "do somthing4"}
else {print "error" }
}
}
here are a few dispatch tables:
the simple one (with a bug):
{
my %age = ( # defined at runtime
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
);
# unsafe to call get_age3() before sub definition
sub get_age3 {
($age{$_[0]} or sub {print "error"})->()
}
}
a better one:
{
my %age;
BEGIN {
%age = ( # defined at compile time
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
)
}
# safe to call get_age4() before sub definition
sub get_age4 {
($age{$_[0]} or sub {print "error"})->()
}
}
another way to write it:
BEGIN {
my %age = ( # defined at compile time
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
);
# safe to call get_age5() before sub definition
sub get_age5 {
($age{$_[0]} or sub {print "error"})->()
}
}
another good way to write it:
{
my $age;
# safe to call get_age6() before sub definition
sub get_age6 {
$age ||= { # defined once when first called
1 => sub {print "do something"},
2 => sub {print "do somthing2"},
3 => sub {print "do somthing3"},
4 => sub {print "do somthing4"},
};
($$age{$_[0]} or sub {print "error"})->()
}
}
Dispatch tables are a perfect fit for this type of design pattern. I've used this idiom many times. Something like this:
sub get_age {
my $facter = shift;
my %lookup_map = (
1 => sub {.....},
2 => sub {.....},
3 => \&some_other_sub,
default => \&some_default_sub,
);
my $code_ref = $lookup_map{$facter} || $lookup_map{default};
my $return_value = $code_ref->();
return $return_value;
}
This works when the argument you are using to determine which case gets executed is going to exist as a key in your hash table. If it is possible that it won't be an exact match then you may need to use regular expressions or some other way to match your input to which bit of code to execute. You can use regexes as hash keys like this:
my %patterns = (
qr{^/this/one}i => sub {....},
qr{^/that/one}is => sub {....},
qr{some-other-match/\d+}i => \&some_other_match,
)
my $code_ref;
for my $regex (keys %patterns) {
if ($facter =~ $regex) {
$code_ref = $patterns{$regex};
last;
}
}
$code_ref ||= \&default_code_ref;
$code_ref->();
See examples/references/dispatch_table.pl
https://code-maven.com/slides/perl/dispatch-table
#!/usr/bin/perl
use strict;
use warnings;
# Use subroutine references in a hash to define what to do for each case
my %dispatch_table = (
'+' => \&add,
'*' => \&multiply,
'3' => \&do_something_3,
'4' => \&do_something_4,
);
foreach my $operation ('+', 'blabla', 'foobar', '*'){
$dispatch_table{$operation}->(
var1 => 5,
var2 => 7,
var3 => 9,
) if ( exists $dispatch_table{$operation} );
}
sub add {
my %args = (#_);
my $var1 = $args{var1};
my $var2 = $args{var2};
my $sum = $var1 + $var2;
print "sum = $sum \n";
return;
}
sub multiply {
my %args = (#_);
my $var1 = $args{var1};
my $var3 = $args{var3};
my $mult = $var1 * $var3;
print "mult = $mult \n";
return;
}
Output:
sum = 12
mult = 45
This may be a place for something like a dispatch table. I haven't done it myself but this page might be a start: http://www.perlmonks.org/?node_id=456530
use Switch;
Read Dispatch Tables in Higher Order Perl.
I almost feel like saying "it's me again!".
Anyway, here we go.
I like using while $object->next() style constructs. They appeal to me and seem "neat".
Now, when the thing I'm iterating over is an array, it's straightforward ("shift #ary or return undef")
sub next {
my ( $self, $args ) = #_;
my $next = shift #{ $self->{list_of_things} } or return undef;
my ( $car, $engine_size, $color )
= split( /\Q$opts->{fieldsep}/, $next );
$self->car = $host;
$self->engine_size = $engine_size;
$self->color = $color;
}
In this example I use AUTOLOAD to create the getters and setters and then have those instance variables available in my object during the while loop.
I'd like to do something similar but with the "list_of_things" being a %hash.
Here's a non-OO example that doesn't make it into the first iteration. Any ideas why?
(The total "list_of_things" is not that big - maybe 100 entries - so to do a keys(%{$hash}) every time doesn't seem too wasteful to me).
use strict;
use warnings;
use Data::Dumper;
my $list_of_things = {
volvo => {
color => "red",
engine_size => 2000,
},
bmw => {
color => "black",
engine_size => 2500,
},
mini => {
color => "british racing green",
engine_size => 1200,
}
};
sub next {
my $args = $_;
my #list = keys( %{$list_of_things} );
return undef if scalar #list == "0";
my $next = $list_of_things->{ $list[0] };
delete $list_of_things->{ $list[0] };
return $next;
}
while ( next()) {
print Dumper $_;
print scalar keys %{ $list_of_things }
}
Is there a better way of doing this? Am I doing something crazy?
EDIT:
I tried Ikegami's suggestion. Of course, Ikegami's example works flawlessly. When I try and abstract a little, so that all that is exposed to the object is a next->() method, I get the same "perl-going-to-100%-cpu" problem as in my original example.
Here's a non-OO example:
use Data::Dumper qw( Dumper );
sub make_list_iter {
my #list = #_;
return sub { #list ? shift(#list) : () };
}
sub next {
make_list_iter( keys %$hash );
}
my $hash = { ... };
while ( my ($k) = next->() ) {
print Dumper $hash->{$k};
}
It does not seem to get past the first step of the while() loop.
I am obviously missing something here...
If you don't want to rely on the hash's builtin iterator (used by each, keys and values), there's nothing stopping you from making your own.
use Data::Dumper qw( Dumper );
sub make_list_iter {
my #list = #_;
return sub { #list ? shift(#list) : () };
}
my $list_of_things = { ... };
my $i = make_list_iter(keys %$list_of_things);
while (my ($k) = $i->()) {
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
say "$k: " . Dumper($list_of_things->{$k});
}
The each operator is a builtin that iterates over hashes. It returns undef when it runs out of elements to return. So you could so something like
package SomeObject;
# creates new object instance
sub new {
my $class = shift;
return bless { hash_of_things => { #_ } }, $class
}
sub next {
my $self = shift;
my ($key,$value) = each %{ $self->{hash_of_things} };
return $key; # or return $value
}
Calling keys on the hash will reset the each iterator. It's good to know this so you can reset it on purpose:
sub reset {
my $self = shift;
keys %{ $self->{hash_of_things} }
}
and so you can avoid resetting it on accident.
The section on tie'ing hashes in perltie also has an example like this.
Here's how List::Gen could be used to create an iterator from a list:
use strict;
use warnings;
use List::Gen 'makegen';
my #list_of_things = ( # This structure is more suitable IMO
{
make => 'volvo',
color => 'red',
engine_size => 2000,
},
{
make => 'bmw',
color => 'black',
engine_size => 2500,
},
{
make => 'mini',
color => 'british racing green',
engine_size => 1200,
}
);
my $cars = makegen #list_of_things;
print $_->{make}, "\n" while $cars->next;
Well, if you don't need $list_of_things for later, you can always do something like
while(keys %$list_of_things)
{
my $temp=(sort keys %$list_of_things)[0];
print "key: $temp, value array: " . join(",",#{$list_of_things->{$temp}}) . "\n";
delete $list_of_things->{$temp};
}
And if you do need it, you can always assign it to a temporary hash reference and perform the same while loop on it.
I'd written a Perl script to generate a table of contents from HTML pages which is working fine (and generating valid HTML) except for that the Perl output is removing closing tags for some elements like p. This is not validating against DocType of strict.
Please scroll down the post to see the Perl code.
What should I do to correct it?
#!/usr/bin/perl -w
#Copyright anurag gupta ; free to use under GNU GPL License
use strict;
use feature "switch";
use Common;
use HTML::Element;
use HTML::TreeBuilder;
#"F:/anurag/work/indiacustomercare/airtel/recharge.html";
my $filename="F:/tmp/t9.html";
my $index=0;
my $labelprefix="anu555ltg-";
my $tocIndex=100001;
my $toc;
my #stack;
my $prevHtag="h2";
sub hTagEncountered($)
{
my $hTag=shift;
my $currLevel=(split //, $hTag)[1];
given($hTag)
{
when(/h1/)
{
break;
}
default{
my $countCurr= (split /h/,$hTag)[1];
my $countPrev= (split /h/,$prevHtag)[1];
if($countCurr>$countPrev)
{
push #stack,($currLevel);
$toc.="<ul>";
}
elsif($countCurr<$countPrev)
{
# Now check in the stack
while ( #stack and $currLevel < $stack[$#stack])
{
pop #stack;
$toc.="</ul>";
}
}
}
}
$prevHtag=$hTag;
}
sub getLabel
{
my $name=$labelprefix.++$tocIndex;
}
sub traversehtml
{
my $node=$_[0];
# $node->dump();
# print "-----------------\n";
# print $node->tag()."\n";
# print ref($node),"->\n";
if((ref(\$node) ne "SCALAR" )and ($node->tag() =~m/^h[2-7]$/i)) #it's an H Element!
{
my #h = $node->content_list();
if(#h==1 and ref(\$h[0]) eq "SCALAR") #H1 contains simple string and nothing else
{
hTagEncountered($node->tag());
my $label=getLabel();
my $a = HTML::Element->new('a', name => $label);
my $text=$node->as_trimmed_text();
$a->push_content($text);
$node->delete_content();
$text=HTML::Entities::encode_entities($text);
$node->push_content($a);
$toc.=<<EOF;
<li>$text
EOF
}
elsif ( #h==1 and ($h[0]->tag() eq "a")) # <h1>ttt</h1> case
{
#See if any previous label already exists
my $prevlabel = $h[0]->attr("name");
$h[0]->attr("name",undef) if(defined($prevlabel) and $prevlabel=~m/$labelprefix/); #delete previous name tag if any
#set the new label
my $label=getLabel();
$h[0]->attr("name",$label);
hTagEncountered($node->tag());
my $text=HTML::Entities::encode_entities($node->as_trimmed_text());
$toc.=<<EOF;
<li>$text
EOF
}
elsif (#h>1) #<h1>some text herettt</h1> case
{
die "h1 must not contain any html elements";
}
}
my #h = $node->content_list();
foreach my $item (#h)
{
if(ref(\$item) ne "SCALAR") {traversehtml($item); } #skip scalar items
}
}
die "File $filename not found" if !-r $filename;
my $tree = HTML::TreeBuilder->new();
$tree->parse_file($filename);
my #h = $tree->content_list();
traversehtml($h[1]);
while(pop #stack)
{
$toc.="</ul>";
}
$toc="<ul>$toc</ul>";
print qq{<div id="icctoc"><h2>TOC</h2>$toc</div>};
my #list1=$tree->content_list();
my #list2=$list1[1]->content_list();
for(my $i=0;$i<#list2;++$i){
if(ref(\$list2[$i]) eq "SCALAR")
{
print $list2[$i]
}
else{
print $list2[$i]->as_HTML();
}
}
# Finally:
Try passing {} for the \%optional_end_tags argument to as_HTML. See the documentation for details.