An interesting problem

McGlinchy, Alistair Alistair.McGlinchy at marks-and-spencer.com
Wed Jan 3 20:27:07 GMT 2007


Woops. The code got stripped from my previous email
> I'd be interested in comments:

use strict;
use warnings;
use Memoize;

memoize('sum_power2square');
memoize('compute_power_two_ranges');

my $table;
my $max_x=0;
my $max_y=0;

my $c="A";
my @excel_col = map {$c++} (1..256);

while (<DATA>) {
    chomp;
    s/^\s+//;# Remove initial  whitespace.
    s/\s+$//;# Remove trailing whitespace.
    next if /^\s*$/;
    my @row = split /\s+/;
    push @$table, \@row;
    $max_y = @row;
    $max_x++;
}


my $best;
my @result;
for (my $x1 = 0   ; $x1< $max_x ; $x1++) {
for (my $y1 = 0   ; $y1< $max_y ; $y1++) {
print "New $x1,$y1 for top left considered\n";
for (my $x2 = $x1 ; $x2< $max_x ; $x2++) {
for (my $y2 = $y1 ; $y2< $max_y ; $y2++) {
    my $val = sum_range($x1, $y1, $x2, $y2);
#    print "Range ($x1,$y1) , ($x2,$y2) has val $val\n";
    if (!defined($best) or $val > $best) {
        print "Range ($x1,$y1) , ($x2,$y2) is better with $val
..".&excel_coord($x1, $y1, $x2, $y2)."\n";
        $best = $val;
        @result = ($x1,$y1,$x2,$y2);
        
    }
}}}}

print "Range ($result[0],$result[1]) , ($result[2],$result[3]) has val
$best\n";


sub sum_range {
    my ($x1,$y1,$x2,$y2) =@_;
#    print "Summing range ($x1,$y1) => ($x2,$y2)  = ";
    my $x_ranges= compute_power_two_ranges($x1,$x2);
    my $y_ranges= compute_power_two_ranges($y1,$y2);

    # divide the range into rectangles with power of two length and
widths
    my $sum;
	# WARNING You can't use while... Each here as memoize may
provide the same hash for 
      # both X and Y ranges
    for my $x_start (keys %$x_ranges) {
        my $x_width= $x_ranges->{$x_start};
        for my $y_start (keys %$y_ranges) {
            my $y_width= $y_ranges->{$y_start};
            # We now have a rectange from $x_start,$y_start made up of
$x_width/y_width squares
            if ($x_width < $y_width) {
                # We need to devide this rectangle into multiple squares
$x_width wide
                for ( my $y_bot =$y_start; $y_bot < $y_start + $y_width
; $y_bot +=  $x_width) {
                    $sum += sum_power2square($x_start,$y_bot,$x_width)
                }
            } elsif ($x_width ==  $y_width) {
                # We have a square
                $sum+= sum_power2square($x_start,$y_start,$x_width)
            } else {
                # We need to devide this rectangle into multiple squares
$y_width wide
                for ( my $x_bot = $x_start; $x_bot < $x_start + $x_width
; $x_bot +=  $y_width) {
                    $sum += sum_power2square($x_bot,$y_start,$y_width)
                }
            }
        }
    }
#    print "$sum\n";
    return $sum;
}

# Compute the smallest number of ranges of integers to cover $bot to
$top
# with each range being a power of 2 wide.
# Eg ( 35, 47) => { 34=>2, 36=>39, 40=>47 }

sub compute_power_two_ranges {
    my ($bot,$top)=@_;
#    print "Power Two ranges for $bot,$top\n";
    my $range_bot = $bot;
    my $range_top;
    my $power =1;
    my %ranges;
    
    # Work out way from smallest to largest powers of two.
    while ($range_bot  + $power < $top+1) {
        if ($range_bot % ($power*2)) {
            # Then must add a range of size $power 
            $range_top = $range_bot + $power-1;
#            print "New range ($range_bot .. $range_top) of length
$power\n";
            $ranges{$range_bot}=$power;
            $range_bot+=$power;
        }
        $power *=2;
    }
#   print "Power capped at $power\n"; 
    # Now work the way back down
    while ( $range_bot <= $top) {
        if ($range_bot + $power <= $top+1) {
            # Then must add a range of size $power 
            my $range_top = $range_bot + $power-1 ;
#            print "New range ($range_bot .. $range_top) of length
$power\n";
            $ranges{$range_bot}=$power;
            $range_bot+=$power;
        }
        $power/=2;
    }
    return \%ranges
}
 
# Sum the square range with top left ($x,$y) and width $width a power of
two
sub sum_power2square {
    die "coding error" unless @_ == 3;
    my ($x, $y, $width) =@_;
#   print "SumSQ $x,$y,$width\n";
    my $sum;
    # Single Cell 
    return $table->[$x]->[$y]  if $width == 1;

    # or 4 squares with half the width
    my $new_w= $width/2;  # WIll always be an integer
    return (
        sum_power2square($x       , $y         , $new_w)+ 
        sum_power2square($x       , $y+$new_w  , $new_w)+ 
        sum_power2square($x+$new_w, $y         , $new_w)+ 
        sum_power2square($x+$new_w, $y+$new_w  , $new_w)
    )
}

sub excel_coord {
    my ($x1,$y1,$x2,$y2) =@_;
    return sprintf( "%s%s:%s%s",
        $excel_col[$y1], $x1+1,
        $excel_col[$y2], $x2+1
    )
}

__DATA__
31  36  -47 4   
22  -36 -2  26  
-32 -19 -9  -28 
-21 -7  -20 40  

**********************************************************************
Registered Office:
Marks and Spencer plc
Waterside House
35 North Wharf Road
London
W2 1NW

Registered No. 214436 in England and Wales.

Telephone (020) 7935 4422
Facsimile (020) 7487 2670

<<www.marksandspencer.com>>

Please note that electronic mail may be monitored.

This e-mail is confidential. If you received it by mistake, please let us know and then delete it from your system; you should not copy, disclose, or distribute its contents to anyone nor act in reliance on this e-mail, as this is prohibited and may be unlawful.
2005





More information about the london.pm mailing list