#!/usr/bin/env perl -w use strict; #for big programs, strict is always a good idea use Data::Dumper; my %students; # my data stucture looks like this: # %students = ( # 660066466 => { # FN=>'Paul', # LN=>'Lalli', # RCS=>'lallip', # HW=>[100, 95, 102, 94], # Ex=>[84, 90], # meth1=>94.525, # meth2=>93.45 # }, # 123456789 => { # FN=>'John', # LN=>'Smith', # RCS=>'smithj', # HW=>[64, 89, 100, 93], # Ex=>[87, 105], # meth1=>89.35, # meth2=>90.3 # } # ); my $choice; sub ParseFile($); sub PrintMenu; sub StudentInfo($); sub PrintAll; sub AddStudent; sub RemoveStudent($); sub AddGrade($); sub ChangeGrade($); sub SaveExit; sub GetStudent; sub ComputeNew($); if (@ARGV > 1){ die "Usage: $0 [DataFile]\n"; } #If a file exists, parse it if (@ARGV == 1){ ParseFile($ARGV[0]); } #main loop while (1){ #The main loop prints the menu, takes in the user's choice, and #calls the appropriate function PrintMenu; print "Enter an option:\n"; chomp($choice = ); if ($choice !~ /^(?:d|e|[0-6])$/){ print "Invalid choice!\n"; next; } if ($choice eq 'd'){ #for debugging purposes, call the dumper function open TEMP, ">temp.txt"; $Data::Dumper::Purity = 1; print TEMP Dumper(\%students); close TEMP; print Dumper(\%students); print "\n\n"; next; } if ($choice == 1){ StudentInfo(GetStudent); } elsif($choice == 2){ PrintAll; } elsif($choice == 3){ AddStudent; } elsif($choice == 4){ RemoveStudent(GetStudent); } elsif($choice == 5){ AddGrade(GetStudent); } elsif($choice == 6){ ChangeGrade(GetStudent); } else { SaveExit; } print "\n"; } sub ParseFile($){ my ($FN, $LN, $RCS, $RIN, $HW, $Ex, $meth1, $meth2); my $file = shift; if (!open FILE, $file){ print "$file could not be opened. Proceeding with no data\n"; #@ARGV will be checked later when we save, so we need to empty it shift @ARGV; return; } chomp (my @lines = ); foreach my $line (@lines){ ($RIN, $LN, $FN, $RCS, $HW, $Ex, $meth1, $meth2) = split /\|/, $line; my @hw = split /,/, $HW; my @ex = split /,/, $Ex; #This line uses a hash slice. The syntax is a litle weird. #basically, just as I would use $ to signify one member of a hash, #I use @ to signify multiple members of the hash @{$students{$RIN}}{'FN', 'LN', 'RCS', 'HW', 'Ex', 'meth1', 'meth2'} = ($FN, $LN, $RCS, \@hw, \@ex, $meth1, $meth2); } } sub PrintMenu{ #The funky << syntax here is a here-doc. We'll cover them in the MoreCGI #lecture. It's just a shortcut for printing multiple lines of text print <); if ($student =~ /^\d{9}$/){ if (exists $students{$student}){ return $student; } else { print "No such student with that RIN found\n"; return undef; } }elsif ($student =~ /^[A-Za-z]+\d*$/){ foreach my $RIN (keys %students){ if ($students{$RIN}{RCS} eq $student){ return $RIN; } } print "No such student with that RCS Id\n"; return undef; } else { print "Sorry, that's not a valid choice\n"; return undef; } } #This function gets the student and prints all information from the hash sub StudentInfo($){ my $student = shift; if (!defined ($student)){ return; } my %student = %{$students{$student}}; print "\n"; print "$student{FN} $student{LN}: $student{RCS} - $student\n"; $" = ", "; print "Homeworks: @{$student{HW}}\n"; print "Exams: @{$student{Ex}}\n"; print "Final Grade by 70/30: $student{meth1}\n"; print "Final Grade by 60/40: $student{meth2}\n"; } #A simple ASCIIbetical sort of the students' last names sub by_LastName{ return ($students{$a}{LN} cmp $students{$b}{LN}); } #A not-quite simple numerical sort of the final grade. #First we have to find which method is higher for each student, #then sort by the higher grade for each. sub by_FinalGrade{ my ($maxa, $maxb); if ($students{$a}{meth1} >= $students{$a}{meth2}){ $maxa = 'meth1'; } else { $maxa = 'meth2'; } if ($students{$b}{meth1} >= $students{$b}{meth2}){ $maxb = 'meth1'; } else { $maxb = 'meth2'; } return ($students{$b}{$maxb} <=> $students{$a}{$maxa}); } #Ask the user to sort by last name or grade, sort the students appropriately, #and print them all out. sub PrintAll{ my @students; print "\n"; print "A) Sort by Last Name\nB) Sort by Final Grade\n"; chomp (my $choice = ); if ($choice =~ /^A$/i){ @students = sort by_LastName keys %students; } elsif ($choice =~ /^B$/i){ @students = sort by_FinalGrade keys %students; } else { print "That's not a valid choice\n"; return; } print "\n"; foreach my $RIN (@students){ print "$students{$RIN}{FN} $students{$RIN}{LN} "; #Here we decide on the fly which method is the higher grade and print that out. print $students{$RIN}{meth1} >= $students{$RIN}{meth2} ? "$students{$RIN}{meth1}\n" : "$students{$RIN}{meth2}\n"; } } #Take in all the information about a student, and set the new information sub AddStudent{ print "\n"; print "Enter the new student's last name:\n"; chomp (my $last = ); print "Enter the new student's first name:\n"; chomp (my $first = ); print "Enter $first $last\'s RCS Id:\n"; chomp (my $RCS = ); if ($RCS !~ /^[A-Za-z]+\d*$/){ print "That is not a valid RCS Id!\n"; return; } print "Enter $first $last\'s RIN:\n"; chomp (my $RIN = ); if ($RIN !~ /^\d{9}$/){ print "That is not a valid RIN!\n"; return; } if (exists $students{$RIN}){ print "A student with that RIN already exists in the system!\n"; return; } #we're again using that funky hash-slice syntax @{$students{$RIN}}{'LN', 'FN', 'RCS', 'HW', 'Ex', 'meth1', 'meth2'} = ($last, $first, $RCS, [], [], 0, 0); } #Using hashes in perl makes this amazingly simple. #Get the student's RIN, and delete that position in the hash. sub RemoveStudent($){ my $RIN = shift; if (exists $students{$RIN}){ delete $students{$RIN}; print "$RIN successfully removed from the system.\n"; } else { print "No student with RIN $RIN was found\n"; } } #A tiny subfunction to ask the user for Homework or Exam grades #it's used by AddGrade and ChangeGrade sub HorE{ print "\n"; print "H)omework Grade\nE)xam Grade\n"; chomp(my $choice = ); if ($choice !~ /^(?:h|e)$/i){ print "That is not a valid choice!\n"; return undef; } else { return lc($choice); } } #Get the student, get h or e, get the new grade, #Add the new grarde to the student sub AddGrade($){ my $RIN = shift; return if !defined $RIN; my $choice = HorE; return if !defined $choice; print "\n"; print "Enter the new grade:\n"; chomp (my $newgrade = ); if ($newgrade !~ /^\d+$/){ print "That is not a valid grade\n"; return; } #Setting the position of the array one past the end to the new grade #I could have just as easily (if not moreso) used the push function #(I'm not quite sure why I didn't) if ($choice =~ /e/i){ $students{$RIN}{Ex}[@{$students{$RIN}{Ex}}] = $newgrade; } else { $students{$RIN}{HW}[@{$students{$RIN}{HW}}] = $newgrade; } #need to compute the new final grade here ComputeNew($RIN); } #Get the student, print some of his/her information, #take in h or e, print appropriate grades, Take in the new grade, #and set it sub ChangeGrade($){ my $RIN = shift; return if !defined $RIN; my ($first, $last, $hw, $ex) = @{$students{$RIN}}{'FN','LN','HW','Ex'}; print "\n"; print "$first $last\n"; local $" = ", "; print "Homework: @$hw\n"; print "Exam: @$ex\n"; my $choice = HorE; return if !defined $choice; if (($choice eq 'h' and !@$hw) || ($choice eq 'e' and !@$ex)){ print "Sorry, there are no ", ($choice eq 'h' ? "homework" : "exam"), "grades to change.\n"; return; } #change $choice to be used directly in %students $choice = $choice eq 'h' ? 'HW' : 'Ex'; print "Change which grade?\n"; for (my $i = 0; $i < @{$students{$RIN}{$choice}}; $i++){ print $i+1, ") ", $students{$RIN}{$choice}[$i], "\n"; } chomp (my $num = ); if ($num !~ /^\d+$/ or $num < 1 or $num > @{$students{$RIN}{$choice}}){ print "That is not a valid choice!\n"; return; } print "Please enter the new grade:\n"; chomp (my $newgrade = ); if ($newgrade !~ /^\d+$/){ print "That is not a valid choice!\n"; return; } $students{$RIN}{$choice}[$num-1] = $newgrade; #We need to recompute the final grade ComputeNew($RIN); } #Either retrieve or request the filename, open it for writing, #and print out the data to that file sub SaveExit{ my $filename; if (!@ARGV){ print "\n"; print "Please enter a filename in which to store the data:\n"; chomp ($filename = ); } else { $filename = $ARGV[0]; } if (!open OFILE, ">$filename"){ die "$filename could not be opened for writing. Data will not be saved.\n"; } select OFILE; local $"; foreach my $RIN (keys %students){ $" = "|"; print "$RIN|@{$students{$RIN}}{'LN','FN','RCS'}|"; $" = ","; print "@{$students{$RIN}{HW}}|"; print "@{$students{$RIN}{Ex}}|"; $" = "|"; print "@{$students{$RIN}}{'meth1','meth2'}\n"; } select STDOUT; close OFILE; print "Changes saved to $filename\n"; exit; } #Get the students HW and Exam grades, and compute the two methods. sub ComputeNew($){ my $RIN = shift; my @hws = @{$students{$RIN}{HW}}; my @exs = @{$students{$RIN}{Ex}}; my ($hwavg, $exavg); if (@hws){ foreach (@hws){ $hwavg += $_; } $hwavg /= @hws; } if (@exs){ foreach (@exs){ $exavg += $_; } $exavg /= @exs; } if (!@exs && !@hws){ @{$students{$RIN}}{'meth1', 'meth2'} = (0, 0); } elsif (!@exs){ @{$students{$RIN}}{'meth1', 'meth2'} = ($hwavg) x 2; } elsif (!@hws){ @{$students{$RIN}}{'meth1', 'meth2'} = ($exavg) x 2; } else { $students{$RIN}{meth1} = .7 * $hwavg + .3 * $exavg; $students{$RIN}{meth2} = .6 * $hwavg + .4 * $exavg; } }