I am writing a script to solve very basic systems of equations. I convert the equations into binary expression trees, isolate the variable that I want the value of, and then do substitutions.
This is where I have a problem, I have a function "substitution" that walks the binary expression tree of the left side of the equation I want substituted. And when I found the variable to be substituted, I replace the node with the expression tree of another equation.
But when I try to return the new tree, my susbstitution is not there.
It is obviously a pass-by-reference / pass-by-value problem but I cannot find the way to solve it.
Here's a side script that shows the part which doesn't work:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
sub inorder {
my $expression = shift;
my $node = $expression;
if ($node->{type} eq "operator") {
print "(";
inorder($node->{left});
print $node->{value};
inorder($node->{right});
print ")";
}
else {
print $node->{value};
}
}
sub substitution {
my ($inserted_equation, $master_equation) = #_;
my $inserted_expression = $inserted_equation->{right_side};
my $insertion_point = $inserted_equation->{left_side}->{value};
my $master_expression = $master_equation->{right_side};
my #stack_tree_walk;
my $node = $master_expression;
push #stack_tree_walk, {$node->%*, left_visited => 0, side=> "left"};
while(#stack_tree_walk) {
if ($node->{type} eq "variable" and $node->{value} eq $insertion_point) {
foreach (#stack_tree_walk) {
}
# print $node->{value};
# print Dumper $inserted_expression;
$node = $inserted_expression; # WORKS
# print Dumper $node; # WORKS
# print Dumper $master_expression; # DOES NOT WORK
pop #stack_tree_walk;
$node = $stack_tree_walk[-1];
}
elsif ($node->{type} eq "operator") {
if (not $stack_tree_walk[-1]->{left_visited}) {
$stack_tree_walk[-1]->{left_visited} = 1;
$node = $node->{left};
push #stack_tree_walk, {$node->%*, left_visited => 0, side=> "left"};
}
elsif ($node->{side} eq "left") {
$node = $node->{right};
$stack_tree_walk[-1]->{side} = "right";
push #stack_tree_walk, {$node->%*, left_visited => 0, side=> "left"};
}
else {
pop #stack_tree_walk;
$node = $stack_tree_walk[-1];
}
}
else {
pop #stack_tree_walk;
$node = $stack_tree_walk[-1];
}
}
return {right_side=>$master_expression, left_side=>$master_equation->{left_side}};
}
my $equation = {left_side => { type=> "variable",
value=> "y"},
right_side=> { type=> "operator",
value=> "*",
left=> {type=> "variable", value=> "a"},
right=> {type=> "variable", value=> "b"} }
};
my $insertion = {left_side => { type=> "variable" ,
value=> "a" },
right_side=> { type=> "operator",
value=> "+",
left=> {type=> "variable", value=> "x"},
right=> {type=> "variable", value=> "y"} }
};
$,="";
$\="";
print "equations before substitution\n";
inorder($equation->{left_side});
print "=";
inorder($equation->{right_side});
print "\n";
inorder($insertion->{left_side});
print "=";
inorder($insertion->{right_side});
print "\n";
print "------------------\n";
$,="\n";
$\="\n\n";
my $final = substitution($insertion, $equation);
$,="";
$\="";
print "------------------\n";
print "equation substituted\n";
inorder($final->{left_side});
print "=";
inorder($final->{right_side});
print "\n";
Here is the OUPUT:
equations before substitution
y=(a*b)
a=(x+y)
equation substituted
y=(a*b) <==== this is the ERROR
y=((x+y)*b) <==== this should be the RIGHT result
I hope someone can show me which part is wrong.
Thank you.
$node is a essentially a pointer into the structure. Your code simply sets $node to a different pointer, i.e. $inserted_expression. You don't change the structure this way, you only change a local variable $node to point to different things. Basically you does this:
$struct = { foo => { bar => 1 } };
$node = $struct->{foo}; # points at { bar => 1 } in $struct
$node = { bar => 2 } # points at { bar => 2 } and not longer into $struct
print(Dumper($struct)); # unchanged
If you want to change the value you in the struct you need to take a reference to the value and not just take the value, i.e.
$struct = { foo => { bar => 1 } };
$node = \$struct->{foo}; # reference to value of { foo => ... }, currently { bar => 1 }
$$node = { bar => 2 } # changes value of { foo => ... } to { bar => 2 }
print(Dumper($struct)); # changed
Related
I'm trying to get a workable data structure that I can pull the element values from in a sensible fashion. Just having great difficulty working with the data once its in the structure. This is how the struct is built:
sub hopCompare
{
my %count;
my %master;
my $index = 0;
foreach my $objPath (#latest) #get Path object out of master array
{
my #path = #{$objPath->_getHopList()}; #dereferencing
my $iter = 0;
foreach my $hop (#path)
{
++$count{$hop}->{FREQ};
$count{$hop}->{INDEX} = $index;
$count{$hop}->{NODE} = $hop;
$index++;
}
$index = 0;
}
foreach my $element( keys %count )
{
if (defined($count{$element}->{NODE}))
{
my $curr = $count{$element}->{INDEX};
my $freq = $count{$element}->{FREQ};
if (($freq > 1) || ($count{$element}->{INDEX} =~ /[0-1]/))
{
push #{ $master{$curr} }, {$count{$element}->{NODE}, {FREQ => $count{$element}->{FREQ}}};
}
print "$element = $count{$element}\n";
print "$element Index = $count{$element}->{INDEX}\n";
}
}
print "\n Master contains: \n" . Dumper (%master);
if (%master){return %master;} else {die "NO FINAL HOPS MATCHED";}
}
Producing this structure:
%Master contains:
$VAR1 = '4';
$VAR2 = [
{
'1.1.1.2' => {
'FREQ' => 2
}
}
];
$VAR3 = '1';
$VAR4 = [
{
'1.1.1.9' => {
'FREQ' => 5
}
},
{
'1.1.1.8' => {
'FREQ' => 1
}
}
];
{truncated}
Although ideally the structure should look like this but I had even less joy trying to pull data out at sub identifyNode:
$VAR1 = {
'1' => [
{
'1.1.1.9' => {
'FREQ' => 5
}
},
{
'1.1.5.8' => {
'FREQ' => 1
}
}
],
Then to get back at the data in another method I'm using:
sub identifyNode
{
my %hops = %{$_[0]};
my $i = 0;
foreach my $h ( keys %hops ) #The HOP-INDEX is the key
{
print "\n\$h looks like \n" . Dumper ($hops{$h});
my %host = %{ $hops{$h}[0] }; #Push the first HASH in INDEX to the %host HASH
foreach my $hip (keys %host)
{
my $corelink = `corelinks $hip`;
my ($node) = $corelink =~ /([a-z0-9-]+),[a-z0-9-\/]+,$hip/s;
print "\n\t\t\tHostname is $node\n";
}
$i++;
}
}
This then generates:
$h looks like
$VAR1 = [
{
'1.1.1.2' => {
'FREQ' => 2
}
}
];
Hostname is blabla-bla-a1
$h looks like
$VAR1 = [
{
'1.1.1.9' => {
'FREQ' => 5
}
},
{
'1.1.1.8' => {
'FREQ' => 1
}
}
];
Hostname is somew-some-a1
So for each hash in $h only the topmost host gets evaluated and hostname returned. This is because it is told to do so by the [0] in line:
my %host = %{ $hops{$h}[0] };
I've played around with different data structures and de-referencing the structure a multitude of ways and this is the only halfway house I've found...
(The IPs have been obfuscated so are not consistent in my examples)
Thanks for your advice it got me halfway there. It works now (in still somewhat a convoluted fashion!) :
sub identifyNode
{
my %hops = %{$_[0]};
my $i = 0;
my #fin_nodes;
my $hindex;
foreach my $h ( keys %hops ) #The HOP-INDEX is the key
{
$hindex = $h;
foreach my $e (#{$hops{$h}}) #first part of solution credit Zdim
{
my #host = %{ $e }; #second part of solution
my $hip = $host[0];
my $corelink = `corelinks $hip`;
my ($node) = $corelink =~ /([a-z0-9-]+),[a-z0-9-\/]+,$hip/s;
print "\n\t\t\tHostname is $node\n";
push (#fin_nodes, [$node, $hindex, $e->{$hip}->{FREQ}]);
}
$i++;
}
return (\#fin_nodes);
}
Am I brave enough to add the data as a hash to #fin_nodes.. hmm
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";}
This is my problem:
I have a file-system like data-structure:
%fs = (
"home" => {
"test.file" => {
type => "file",
owner => 1000,
content => "Hello World!",
},
},
"etc" => {
"passwd" => {
type => "file",
owner => 0,
content => "testuser:testusershash",
},
"conf" => {
"test.file" => {
type => "file",
owner => 1000,
content => "Hello World!",
},
},
},
);
Now, to get the content of /etc/conf/test.file I need $fs{"etc"}{"conf"}{"test.file"}{"content"}, but my input is an array and looks like this: ("etc","conf","test.file").
So, because the length of the input is varied, I don't know how to access the values of the hash. Any ideas?
You can use a loop. In each step, you proceed one level deeper into the structure.
my #path = qw/etc conf test.file/;
my %result = %fs;
while (#path) {
%result = %{ $result{shift #path} };
}
print $result{content};
You can also use Data::Diver.
my #a = ("etc","conf","test.file");
my $h = \%fs;
while (my $v = shift #a) {
$h = $h->{$v};
}
print $h->{type};
Same logic as what others given, but uses foreach
#keys = qw(etc conf test.file content);
$r = \%fs ;
$r = $r->{$_} foreach (#keys);
print $r;
$pname = '/etc/conf/test.file';
#names = split '/', $pname;
$fh = \%fs;
for (#names) {
$fh = $fh->{"$_"} if $_;
}
print $fh->{'content'};
Path::Class accepts an array. It also gives you an object with helper methods and handles cross platform slash issues.
https://metacpan.org/module/Path::Class
You can just build the hash element expression and call eval. This is tidier if it is wrapped in a subroutine
my #path = qw/ etc conf test.file /;
print hash_at(\%fs, \#path)->{content}, "\n";
sub hash_at {
my ($hash, $path) = #_;
$path = sprintf q($hash->{'%s'}), join q('}{'), #$path;
return eval $path;
}
I would like to compare the values of two hash references.
The data dumper of my first hash is this:
$VAR1 = {
'42-MG-BA' => [
{
'chromosome' => '19',
'position' => '35770059',
'genotype' => 'TC'
},
{
'chromosome' => '2',
'position' => '68019584',
'genotype' => 'G'
},
{
'chromosome' => '16',
'position' => '9561557',
'genotype' => 'G'
},
And the second hash is similar to this but with more hashes in the array. I would like to compare the genotype of my first and second hash if the position and the choromosome matches.
map {print "$_= $cave_snp_list->{$_}->[0]->{chromosome}\n"}sort keys %$cave_snp_list;
map {print "$_= $geno_seq_list->{$_}->[0]->{chromosome}\n"}sort keys %$geno_seq_list;
I could do that for the first array of the hashes.
Could you help me in how to work for all the arrays?
This is my actual code in full
#!/software/bin/perl
use strict;
use warnings;
use Getopt::Long;
use Benchmark;
use Config::Config qw(Sequenom.ini);
useDatabase::Conn;
use Data::Dumper;
GetOptions("sam=s" => \my $sample);
my $geno_seq_list = getseqgenotypes($sample);
my $cave_snp_list = getcavemansnpfile($sample);
#print Dumper($geno_seq_list);
print scalar %$geno_seq_list, "\n";
foreach my $sam (keys %{$geno_seq_list}) {
my $seq_used = $geno_seq_list->{$sam};
my $cave_used = $cave_snp_list->{$sam};
print scalar(#$geno_seq_list->{$_}) if sort keys %$geno_seq_list, "\n";
print scalar(#$cave_used), "\n";
#foreach my $seq2com (# {$seq_used } ){
# foreach my $cave2com( # {$cave_used} ){
# print $seq2com->{chromosome},":" ,$cave2com->{chromosome},"\n";
# }
#}
map { print "$_= $cave_snp_list->{$_}->[0]->{chromosome}\n" } sort keys %$cave_snp_list;
map { print "$_= $geno_seq_list->{$_}->[0]->{chromosome}\n" } sort keys %$geno_seq_list;
}
sub getseqgenotypes {
my $snpconn;
my $gen_list = {};
$snpconn = Database::Conn->new('live');
$snpconn->addConnection(DBI->connect('dbi:Oracle:pssd.world', 'sn', 'ss', { RaiseError => 1, AutoCommit => 0 }),
'pssd');
#my $conn2 =Database::Conn->new('live');
#$conn2->addConnection(DBI->connect('dbi:Oracle:COSI.world','nst_owner','nst_owner', {RaiseError =>1 , AutoCommit=>0}),'nst');
my $id_ind = $snpconn->execute('snp::Sequenom::getIdIndforExomeSample', $sample);
my $genotype = $snpconn->executeArrRef('snp::Sequenom::getGenotypeCallsPosition', $id_ind);
foreach my $geno (#{$genotype}) {
push #{ $gen_list->{ $geno->[1] } }, {
chromosome => $geno->[2],
position => $geno->[3],
genotype => $geno->[4],
};
}
return ($gen_list);
} #end of sub getseqgenotypes
sub getcavemansnpfile {
my $nstconn;
my $caveman_list = {};
$nstconn = Database::Conn->new('live');
$nstconn->addConnection(
DBI->connect('dbi:Oracle:CANP.world', 'nst_owner', 'NST_OWNER', { RaiseError => 1, AutoCommit => 0 }), 'nst');
my $id_sample = $nstconn->execute('nst::Caveman::getSampleid', $sample);
#print "IDSample: $id_sample\n";
my $file_location = $nstconn->execute('nst::Caveman::getCaveManSNPSFile', $id_sample);
open(SNPFILE, "<$file_location") || die "Error: Cannot open the file $file_location:$!\n";
while (<SNPFILE>) {
chomp;
next if /^>/;
my #data = split;
my ($nor_geno, $tumor_geno) = split /\//, $data[5];
# array of hash
push #{ $caveman_list->{$sample} }, {
chromosome => $data[0],
position => $data[1],
genotype => $nor_geno,
};
} #end of while loop
close(SNPFILE);
return ($caveman_list);
}
The problem that I see is that you're constructing a tree for generic storage of data, when what you want is a graph, specific to the task. While you are constructing the record, you could also be constructing the part that groups data together. Below is just one example.
my %genotype_for;
my $record
= { chromosome => $data[0]
, position => $data[1]
, genotype => $nor_geno
};
push #{ $gen_list->{ $geno->[1] } }, $record;
# $genotype_for{ position }{ chromosome }{ name of array } = genotype code
$genotype_for{ $data[1] }{ $data[0] }{ $sample } = $nor_geno;
...
return ( $caveman_list, \%genotype_for );
In the main line, you receive them like so:
my ( $cave_snp_list, $geno_lookup ) = getcavemansnpfile( $sample );
This approach at least allows you to locate similar position and chromosome values. If you're going to do much with this, I might suggest an OO approach.
Update
Assuming that you wouldn't have to store the label, we could change the lookup to
$genotype_for{ $data[1] }{ $data[0] } = $nor_geno;
And then the comparison could be written:
foreach my $pos ( keys %$small_lookup ) {
next unless _HASH( my $sh = $small_lookup->{ $pos } )
and _HASH( my $lh = $large_lookup->{ $pos } )
;
foreach my $chrom ( keys %$sh ) {
next unless my $sc = $sh->{ $chrom }
and my $lc = $lh->{ $chrom }
;
print "$sc:$sc";
}
}
However, if you had limited use for the larger list, you could construct the specific case
and pass that in as a filter when creating the longer list.
Thus, in whichever loop creates the longer list, you could just go
...
next unless $sample{ $position }{ $chromosome };
my $record
= { chromosome => $chromosome
, position => $position
, genotype => $genotype
};
...
I have some files with below data.
sample File 1:
sitename1,2009-07-19,"A1",11975,17.23
sitename1,2009-07-19,"A2",11,0.02
sitename1,2009-07-20,"A1",2000,17.23
sitename1,2009-07-20,"A2",538,0.02
I want to map the values in column 4 with column 2 and 3 as shown below.
Output required.
Site,Type,2009-07-19,2009-07-20
sitename1,"A1",11975,2000
sitename1,"A2",11,538
Here is what I have tried so far:
#! /usr/bin/perl -w
use strict;
use warnings;
my $column_header=["Site,Type"];
my $position={};
my $last_position=0;
my $current_event=[];
my $events=[];
while (<STDIN>) {
my ($site,$date,$type,$value,$percent) = split /[,\n]/, $_;
my $event_key = $date;
if (not defined $position->{$event_key}) {
$last_position+=1;
$position->{$event_key}=$last_position;
push #$column_header,$event_key;
}
my $pos = $position->{$event_key};
if (defined $current_event->[$pos]) {
dumpEvent();
}
if (not defined $current_event->[0]) {
$current_event->[0]="$site,$type";
}
$current_event->[$pos]=$value;
}
dumpEvent();
my $order = [];
for (my $scan=0; $scan<scalar(#$column_header); $scan++) {
push #$order,$scan;
}
printLine($column_header);
map { printLine($_) } #$events;
sub printLine {
my $record=shift;
my #result=();
foreach my $offset (#$order) {
if (defined $record->[$offset]) {
push #result,$record->[$offset];
} else {
push #result,"";
}
}
print join(",",#result)."\n";
}
sub dumpEvent {
return unless defined $current_event->[0];
push #$events,$current_event;
$current_event=[];
}
The output i am getting is as below.
*Site,Type,2009-07-19,2009-07-20*
sitename1,"A1",11975,
sitename1,"A2",11,
sitename1,"A1",,14620
sitename1,"A2",,538
If I understand you correctly (and I have to admit I'm only guessing), you have several types of things at different dates and a value for each. Thus you need a data structure like this hash for each site:
$foo = {
site => 'sitename1',
type => 'A1',
dates => [
{
date => '2009-07-19',
value => 11975,
},
{
date => '2009-07-20',
value => 538,
},
],
};
Is that even close?
The folowing code produces the expected result and makes "some" sense. I don't know if it makes real sense.
my %dates;
my %SiteType;
while (<DATA>) {
chomp;
my ($site,$date,$type,$value,$percent) = split /,/;
$dates{$date} = '1';
push #{$SiteType{"$site,$type"}}, $value ;
};
print 'Site,Type,', join(',', sort keys %dates), "\n";
foreach ( sort keys %SiteType) {
print $_, ',', join(',', #{$SiteType{$_}}), "\n";
};