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

Hi, I am currently trying to get my head around a problem i am trying to solve. I have a text file that contains the following data..

office 1

120 120

120 120

140 135

155 135

120 120

bedroom 2

100 75

100 75

120 180


This is for a window installation business. The text file contains data for a quote. A location, followed by the window measurements (width, height). What I am trying to do is sort this information to display the following desired results

office 1

width height quantity

120 120 3

140 135 1

155 135 1

bedroom 2

width height quantity

100 75 2

120 180 1


I have been trying to visualize the required data structure to achieve this. I have attempted using pattern matching switches to identify if a line is a word then pushing into array. If someone could please point me in the right direction. So far every solution I have tried has failed.
Thanks

Replies are listed 'Best First'.
Re: Data Structures Help
by Arunbear (Prior) on Oct 29, 2004 at 12:31 UTC
    You can use a hash of hashes like this:
    use strict; my %house; my $room; while (<DATA>) { chomp; next unless $_; # skip blank lines if(/^\D/) { $room = $_ } else { $house{$room}{$_}++ } } foreach my $room (keys %house) { print "$room:\n"; print "width height quantity\n"; map { print "$_ $house{$room}{$_}\n"; } sort { $house{$room}{$b} <=> $house{$room}{$a} } keys %{$house{$room}}; } __DATA__ office 1 120 120 120 120 140 135 155 135 120 120 bedroom 2 100 75 100 75 120 180
    Outout:
    bedroom 2: width height quantity 100 75 2 120 180 1 office 1: width height quantity 120 120 3 140 135 1 155 135 1
Re: Data Structures Help
by tachyon (Chancellor) on Oct 29, 2004 at 12:32 UTC

    Well you probably want to read perlreftut but how about this:

    my ($location, $h); while(<DATA>){ chomp; $location = $_ if m/^[a-zA-Z]/; next unless m/(\d+)\s+(\d+)/; $h->{$location}->{windows}++; $h->{$location}->{frames}->{"$1x$2"}++; $h->{$location}->{total_length}+= $1*2 + $2*2; } use Data::Dumper; print Dumper $h; my $fixed_cost_per_window = 100; my $cost_by_material = 0.5; my $total; for my $location( keys %$h ) { my $fixed = $h->{$location}->{windows} * $fixed_cost_per_window; my $materials = $h->{$location}->{total_length} * $cost_by_materia +l; printf "%-10s\t%d windows \$%7.2f\tmaterials \$%7.2f\ttotal \$%7.2 +f\n", $location, $h->{$location}->{windows}, $fixed, $materials, $fi +xed+$materials; $total += $fixed+$materials; } printf "\nTotal: \$%.2f\n", $total; __DATA__ office 1 120 120 120 120 140 135 155 135 120 120 bedroom 2 100 75 100 75 120 180

    This produces:

    $VAR1 = { 'bedroom 2' => { 'frames' => { '120x180' => '1', '100x75' => '2' }, 'windows' => '3', 'total_length' => '1300' }, 'office 1' => { 'frames' => { '155x135' => '1', '140x135' => '1', '120x120' => '3' }, 'windows' => '5', 'total_length' => '2570' } }; bedroom 2 3 windows $ 300.00 materials $ 650.00 total $ 950. +00 office 1 5 windows $ 500.00 materials $1285.00 total $1785. +00 Total: $2735.00

    cheers

    tachyon

Re: Data Structures Help
by hmerrill (Friar) on Oct 29, 2004 at 12:29 UTC
    Got it.

    Ok, I'm thinking you want a multi-dimensional hash. Here's basically what I'm talking about:

    %windows = ( "office 1" => ("120 120" => 3, "140 135" => 1, "155 135" => 1), "bedroom 2" => ("100 75" => 2, "120 180" => 1), );
    That is a pre-loaded hash just to show you what the structure will look like. Of course you'll need to load that hash from scratch with your script as you read through the text file.

    So in hash %windows, key "office 1" has a value which is a *reference* to an anonymous hash (a *reference* to an anonymous hash is created by the parenthesis), and that reference refers to the anonymous hash which has these key/value's:

    Key Value ------- ----- 120 120 3 140 135 1 155 135 1
    Then to print out the %windows hash you will need two loops - one loop to iterate through the outer hash(with keys "office 1" and "bedroom 2", and an inner loop to iterate through the hash elements within each of those outer loop elements - something like this:
    foreach my($room, $measurements_hashref) (each %windows) { print "$room\n"; print "width\theight\tquantity\n"; foreach my($measurements, $count) (each %$measurements_hashref) +{ print "$measurements $count\n"; } }
    ***Careful - this is completely untested code, but hopefully it's close.

    HTH.

Re: Data Structures Help
by Yendor (Pilgrim) on Oct 29, 2004 at 12:47 UTC

    I'll to preface this with saying that the output is not EXACTLY in the format that you want...But it is readable, and the program does appear to work. I'm just a simple Perl hacker...With a little bit of elbow grease (which I don't necessarily know how to apply), this could become what you're looking for.

    #!/usr/bin/perl -wT use strict; use Data::Dumper; my $roomtype; my $dim; my $count; my %output; while (<DATA>) { chomp; # If the first character of the input is a letter, then we're on a n +ew # room type if (/^[a-zA-Z]+/) { $roomtype = $_; $dim = undef; } else { $dim = join('_', split / /); } if (defined($roomtype) && defined($dim)) { my $room = $roomtype . '_' . $dim; if (defined($output{$room})) { my $cur = $output{$room}; $cur++; $output{$room} = $cur; } else { $output{$room} = 1; } } } my $data = Data::Dumper->new([\%output], [qw/*output/]); print $data->Dump; __DATA__ office 1 120 120 120 120 140 135 155 135 120 120 bedroom 2 100 75 100 75 120 180

    And the output:

    $ ./window.pl %output = ( 'bedroom 2_120_180' => 1, 'office 1_120_120' => 3, 'bedroom 2_100_75' => 2, 'office 1_155_135' => 1, 'office 1_140_135' => 1 );
Re: Data Structures Help
by jdporter (Paladin) on Oct 29, 2004 at 12:32 UTC
    (untested)
    my %windows; my $current_location; while (<>) { # read from stdin chomp; if ( /^\d+\s+\d+/ ) { $current_location or die "bad input"; $windows{$current_location}{$_}++; } else { $current_location = $_; } } for my $location ( sort keys %windows ) { print "\n$location\nwidth height quantity\n"; for my $dimensions { sort keys %{ $windows{$location} } ) { print "$dimensions $windows{$location}{$dimensions}\n"; } }
    But /me smells homework.
Re: Data Structures Help
by TedPride (Priest) on Oct 29, 2004 at 19:08 UTC
    You can't use a straight hash of hashes because he seems to want the original order of office 1, bedroom 2, etc. preserved, and a hash won't do that. You also have to provide for there being empty lines.
    use strict; use warnings; my (@arr, $ref); while (<DATA>) { chomp; if (/^[a-zA-Z]/) { $arr[$#arr+1][0] = $_; } elsif (/^\d/) { $arr[$#arr][1]{$_}++; } } foreach (@arr) { print @$_[0] . "\n\n"; print "width height quantity\n\n"; $ref = @$_[1]; for (sort {$ref->{$b} <=> $ref->{$a} or $a cmp $b} keys %$ref) { print "$_ " . $ref->{$_} . "\n\n"; } } __DATA__ office 1 120 120 120 120 140 135 155 135 120 120 bedroom 2 100 75 100 75 120 180
Re: Data Structures Help
by Anonymous Monk on Oct 29, 2004 at 11:59 UTC
    Thanks for your reply. Sorry. The quantity field is what i am trying to generate from the text field. If a width and a height already exist for a given location, the quantity will increment (eg $count++). Thanks again. Does that make it clearer?
Re: Data Structures Help
by Anonymous Monk on Oct 30, 2004 at 13:26 UTC
    Thankyou all for your great advise and examples. (Wow). I have been trying to modify the examples to allow me to export the results to Microsoft Excel. I am trying to access each of the hash keys to allow me to place them into its own cell. How can I use one of the above examples to allow me to do the following.

    $worksheet->Cells($count,1)->{Value} = "$location";

    $worksheet->Cells($count,1)->{Value} = "$width";

    $worksheet->Cells($count,2)->{Value} = "$height";

    $worksheet->Cells($count,3)->{Value} = "$quantity";

    etc


    In addition, I am also wanting to specify a type of film used with a given location

    I was thinking that I could use tab spacing between the location name and the film type

    For example the text file will look like this


    office 3 \t safetyfilm

    100 120

    100 120

    135 125

    bedroom 1 \t windowtint

    125 125

    125 125


    Then i would also export the film type into Excel

    $worksheet->Cells($count,2)->{Value} = "$film";

    Or should i place the type of film into the text file another way

    Thankyou all again

    Thankyou again for all your wisdom

      Why not just make the film another entry in the subhash? for example:

      Office 1 Film => 'something', '120 100' => 2 etc.


      Then you have one per location, and you can easily separate it from the dimension counts by name.

      --
      Spring: Forces, Coiled Again!
Re: Data Structures Help
by hmerrill (Friar) on Oct 29, 2004 at 11:53 UTC
    Where does the "quantity" column come from in your output? I don't see that in the input text file anywhere? Once I have a full picture of the input and output it will be easier to make a recommendation.
Re: Data Structures Help
by nimdokk (Vicar) on Oct 29, 2004 at 12:34 UTC
    What have you tried so far? Why hasn't it worked? You might look at some sort of Hash of Hashes.