Dies ist ein Template für den Skripte-Unterbereich RaetseL20070220.

Rätsel der Woche 2007/4

Aufgabe

Die Aufgabe:
~~~~~~~~~~~~
         Gib zu einer gegebenen Zahl möglichst wenige Quadratzahlen aus,
         deren Summe diese Zahl ergibt.

Beispiel:
~~~~~~~~~
         Eingabe:
         7
         Ausgabe:
         4+1+1+1
         Eingabe:
         23
         Ausgabe:
         9+9+4+1
         Eingabe:
         179
         Ausgabe:
         121+49+9

Forumsdiskussion

http://board.perl-community.de/cgi-bin/ikonboard/ikonboard.cgi?act=ST;f=6;st=0;t=3817

Teilnehmer (alphabetisch nach Nickname)

  1. betterworld
  2. der_Martin (in java)
  3. docsnyder
  4. Ishka (regulär&golf)
  5. topeg
  6. Vayu

Übersicht über die Lösungen

Allgemeine Eigenschaften
Teilnehmer Lösungen minimal (im Bereich 1..500) ausgegebene Lösungen rekursiv
betterworld ja eine ja
der_Martin fast (bis auf Quadratzahlen ja, die aber meist nein) eine ja
docsnyder ja alle ja
Ishka ja eine nein
topeg nein (1-3 gibt Fehler) (erstes Gegenbeispiel: 43) eine nein
Vayu nein (erstes Gegenbeispiel: 43) eine nein
Ishka (golf) ja eine nein

Beispielzerlegungen
Teilnehmer 0 2 7 23 43 1235 1876 2151
betterworld 0 1+1 4+1+1+1 9+9+4+1 25+9+9 1225+9+1 1296+576+4 2116+25+9+1
der_Martin (1) [] [1, 1] [1, 1, 1, 4] [1, 4, 9, 9] [9, 9, 25] [1, 9, 1225] [4, 576, 1296] [1, 9, 25, 2116]
docsnyder   1+1 4+1+1+1 9+9+4+1 25+9+9 1225+9+1 (2) 1296+324+256 (2) 2116+25+9+1 (2)
Ishka 0 1+1 4+1+1+1 9+4+1+9 9+9+25 9+1+1225 576+4+1296 2116+9+1+25
topeg 25+16+1 fehler 4+1+1+1 9+9+4+1 25+16+1+1 (6) 1225+9+1 1296+576+4 961+961+225+4
Vayu   1+1 1+4+1+1 9+9+4+1 16+25+1+1 (6) 1225+9+1 1849+25+1+1 (6) 2116+25+9+1
Ishka (golf) 0 1+1 4+1+1+1 9+9+4+1 25+9+9 1225+9+1 k.a. (3) k.a. (3)

Programmlaufzeiten (in Sekunden auf meinem Rechner, bis auf Lösung von der_Martin, siehe auch (4) )
Teilnehmer 23 1235 1876 2151 612731 4252345647 34875347853945 78432567897012631231
betterworld 0,09 0,10 0,09 0,11 0,11 21070 k.a. (3) (5)
der_Martin (4) 0,048 0,640 4,980 27,370 k.a. (3) (5) (5) (5)
docsnyder 0,03 2,62 8,55 212 k.a. (3) (5) (5) (5)
Ishka 0,09 0,26 0,88 0,09 6,46 0,10 k.a. (3) 25,73
topeg 0,02 0,02 0,02 0,03 1,16 k.a. (3) k.a. (3) (5)
Vayu 0,02 0,02 0,01 (6) 0,02 0,03 0,81 44,92 (6) k.a. (3)
Ishka_golf 0,23 42,87 k.a. (3) k.a. (3) k.a. (3) k.a. (3) k.a. (3) k.a. (3)

Erklärung zu den Anmerkungen: 1: ,,Die Zerlegung lautet: '' ist bei der Ausgabentabelle gespart 2: nur die erste Ausgabe angegeben 3: wegen zu langer Laufzeit abgebrochen 4: Auf einem anderen, etwa doppelt so schnellen Rechner getestet wie die anderen Programme, zudem nicht wirklich vergleichbar, da in Java geschrieben 5: Eingabe hat den Zahlenbereich verlassen, für den dieses Programm ausgelegt ist 6: Nicht kürzestmögliche Zerlegung gefunden

Die Lösungen im einzelnen

Hier sind jeweils die Lösungen der teilnehmenden Personen gelistet.

betterworld

#!/usr/bin/perl
use strict;
#use warnings;
use Getopt::Long;
use List::Util qw(reduce);

GetOptions('verbose'=>\my $verbose) or die;

my ($input) = @ARGV;

die 'number expected' unless $input =~ /^\d+\z/;

my $shortest;

sub new_shortest {
    my ($depth) = @_;
    if (!defined $shortest or $shortest > $depth) {
        $shortest = $depth;
        warn "shortest: $shortest\n" if $verbose;
    } else {
        warn "optimization br0ken ($shortest, $depth)\n";
    }
}

sub zerlegen {
    my ($sum, $max, $depth) = @_;
    unless ($sum) {
        new_shortest($depth);
        return [];
    }

    # Nur zur Optimierung
    return () if defined $shortest and $depth+1 >= $shortest;

    my $start = int sqrt($sum);

    # Nur zur Optimierung
    if ($start * $start == $sum) {
        new_shortest($depth+1);
        return [$sum];
    }
    # Nur zur Optimierung
    return () if defined $shortest and $depth+2 >= $shortest;

    my @zerlegungen;
    for my $i (reverse 1..$start) {
        my $quadrat = $i*$i;
        next if $quadrat > $max;
        my $rest = $sum - $quadrat;
        push @zerlegungen, [$i*$i, @$_] for zerlegen($rest, $quadrat, $depth+1);
    }
    return @zerlegungen;
}

my @zerlegungen = $input ? zerlegen($input, $input, 0) : [0];

use Data::Dumper;
print Dumper \@zerlegungen if $verbose;

print join '+', @{scalar reduce {@$a < @$b ? $a : $b} @zerlegungen};
print "\n";

der_Martin

import java.util.*;

/**
 * Lösung fÃr RÃtsel der Woche
 *
 * Suchen der Zerlegung in eine Summe von Quadratzahlen einer natÃrlichen
 * Zahl
 * 
 * Lösung durch Backtracking (nur kleinere Optimierungen)
 **/
class Test {

        //Diese Liste von Quadratzahlen ist nur fÃr eine Schnellere Suche
        List<Integer> quadratZahlen = new ArrayList<Integer>();


        //Sucht die nÃchst kleinere Quadratzahl zu n
        //und gibt den Index fÃr obige Liste zurÃck
        int findLargestSquare(int n)
        {
                //Ist die Zahl schon in der Liste?
                if ((quadratZahlen.size() > 0) && (quadratZahlen.get(quadratZahlen.size()-1) > n))
                        {
                        //Suche (binÃre Suche):
                        if (n == 1)
                                return 0;
                        int low = 0;
                        int high = quadratZahlen.size()-1;
                        int mid = (low + high)/2;

                        while (high - low > 1)
                                {
                                int no = quadratZahlen.get(mid);
                                if (no == n)
                                        return mid;
                                if (no > n)
                                        high = mid;
                                else low = mid;
                                mid = (low + high)/2;
                                }
                        return low;
                        }
                //Nein, Liste verlÃngern:
                for (int i = quadratZahlen.size(); (i+1)*(i +1)< n; i++)
                        quadratZahlen.add((i+1)*(i+1));
                return quadratZahlen.size()-1;
        }

        /**
         * Hiermit wird die Zerlegung durchgefÃhrt:
         * @param n Die zu zerlegende Zahl
         * @param max Die gröÃ
         *      darf, um mehrfach die gleiche Lösung zu vermeiden
         * @return niemals null, eine Liste mit allen benötigten Zahlen (die GröÃ
         * @throws Exception Wenn einer der Parameter kleiner als 0 ist
         **/
        List<Integer> zerlegen(int n, int max) throws Exception
        {
        //Hier ein paar patologische FÃlle abprÃfen
        if (n == 0) //Ergebniss gefunden ...
                return new ArrayList<Integer>();

        //Welcher Depp gibt negative Zahlen an???
        if ((n < 0) || (max < 0))
                throw new Exception("Irgendwas ist schief gegangen ...");

        //Es macht nur Sinn nach Zahlen zu suchen, die auch kleiner
        //als die Zielzahl sind ...
        if (max > n)
                max = n;

        //Wer alles etwas gesprÃchiger möchte:
        //System.out.println("Versuche: "+n+" gröÃ

        //Sonderfall: nur noch die Zahl 1 erlaubt
        if (max == 1)
                {
                ArrayList<Integer> res = new ArrayList<Integer>();
                for (int i = 0; i < n; i++)
                        res.add(1);
                return res;
                }

        //GröÃ
        int idx = findLargestSquare(max);

        List<Integer> result = null;

        //Jetzt diese Zahl, und alle kleineren probieren, 
        //so dass das gewÃnschte Ergebniss rauskommt
        for (int i = idx; i >= 0; i--)
                {
                //Wenn es eine Lösung gibt, die mit weniger Zahlen auskommt, als
                //mit der Zahl i minimal notwenidig wÃren, ist eine 
                //Lösung gefunden
                if ((result != null) && (n/quadratZahlen.get(i) > result.size()))
                        return result;

                //Zerlegen des Rests:
                List<Integer> res = zerlegen(n - quadratZahlen.get(i), quadratZahlen.get(i));

                //Wenn Besser als vorherige Versuche, diese Variante verwenden:
                if ((result == null) || (res.size() + 1 < result.size()))
                        {
                        res.add(quadratZahlen.get(i));
                        result = res;
                        }
                }
        return result;
        }

        /**
         * Das Programm braucht als einigen Parameter, die zu zerlegende Zahl, und gibt eine 
         * Meldung aus, die die Zerlegung darstellt
         **/
        public static void main(String [] args) throws Exception
        {
        //Wenn kein Parameter, dann Hinweis ausgeben
        if (args.length < 1)
        {
        System.out.println("Um eine Zahl zu zerlegen, bitte dieses Programm mit dieser Zahl als Parameter aufrufen");
        return;
        }

        //Zahl lesen:
        int no = Integer.parseInt(args[0]);

        //Zahl zerlegen:
        List <Integer> result = new Test().zerlegen(no,no);

        //Und ausgeben:
        System.out.println("Die Zerlegung lautet: "+result);
        }
}

docsnyder

#!/usr/bin/perl

use warnings;
use strict;

my($min) = my($n) = shift;
my(@vals, @arr, @res, %sums);

map { my($rt)=sqrt($_); push(@vals, $_) if ( $rt == int($rt) ) } (reverse(1..$n) );

sub partition {
  if ( ($_[0] >= 0) && (@arr <= $min) ) {
    if ( ! $_[0] ) {
      push(@{$res[$min=@arr]}, join('+', sort { $b <=> $a } @arr) . "\n");
    }
    else { map { push(@arr, $_); partition($_[0]-$_); pop(@arr) } @vals }
  }
}

partition($n);

map { print if ( ! $sums{$_}++ ) } @{$res[$min]};

Ishka

#!/usr/bin/perl

use strict;
use warnings;
use Math::BigInt lib => 'GMP';

my $zahl = $ARGV[0];

die "Kann nur eine positive Ganzzahl als Summe von Quadraten darstellen.\n"
  unless $zahl =~ m#^\d+$#;

print quad4($zahl), "\n";
exit;

sub quad4
{    # stelle eine gegebene Zahl als Summe von 4 oder weniger Quadratzahlen dar
    my $i = new Math::BigInt( $_[0] );
    return "0" if 0 == $i;
    if ( zahlbraucht4($i) ) {
        my $weg = wurzel($i);
        while ( zahlbraucht4( $i - $weg * $weg ) ) { $weg-- }
        $weg *= $weg;
        return "$weg+" . quad3( $i - $weg, 1 );
    }
    else { return quad3($i) }
}

sub quad3
{    # versuche die Zahl in 3 oder weniger Quadratzahlen darzustellen
    my $i   = new Math::BigInt( $_[0] );
    my $qua = wurzel($i);
    unless ( $_[1] ) {
        return $i if $qua * $qua == $i;    # Fall: 1 Quadratzahl
        my $j = quad2($i);                 # Fall: 2 Quadratzahlen
        return $j if $j;
    }
    my $r  = undef;
    my $rq = undef;
    my $t  = undef;
    while (1) {
        $r = $i - $qua * $qua;
        $t = quad2($r);
        if ($t) { $qua *= $qua; return "$t+$qua" }
        $qua--;
    }
}

sub quad2
{    # versuche die Zahl als genau 2 Quadratzahlen darzustellen
    my $i   = new Math::BigInt( $_[0] );
    my $qua = wurzel($i);
    my $r   = undef;
    my $rq  = undef;
    while ( $qua > 0 ) {
        $r  = $i - $qua * $qua;
        $rq = wurzel($r);
        if ( $rq * $rq == $r ) { $qua *= $qua; return "$qua+$r" }
        $qua--;
    }
    return undef;
}

sub zahlbraucht4
{   # kann die Zahl nur durch vier Quadrate dargestellt werden?
    my $was = new Math::BigInt( $_[0] );

    my $ge = 0;
    while ( not( $was % 2 ) ) {
        $ge++;
        $was /= 2;
    }

    if   ( not( $ge % 2 ) && 7 == $was % 8 ) { return 1 }
    else                                     { return 0 }
}

sub wurzel
{    # Die Wurzelimplementation von Math::BigInt ist auf manchen Systemen kaputt
    my $von     = $_[0];
    my $schritt = 1;
    until ( $schritt * $schritt <= $von && ( $schritt + 1 ) * ( $schritt + 1 ) > $von) {
        $schritt = ( $schritt + $von / $schritt ) / 2;
    }
    return $schritt;
}

Golflösung

perl -le '$*=pop;$*[$u=0]++,$h=join"+",map{$*[$u]=$*[$u+1]++if$_>$*;$u++;$_*$_}@*while$*-eval$h;print$h||0'

topeg

#!/usr/bin/perl

use strict;
use warnings;


sub quadsum($)
{
 my $zahl=shift(@_);
 my @o;
 for my $c (2..int($zahl**.5))
 {
  my @l;
  my $z=$zahl;
  for (grep{$_**=2}reverse(1..$c))
  {
   if($z>=$_)
   {
    $z-=$_;
    push(@l,$_);
    redo;
   }
   last if($z<=0);
  }
  push(@o,\@l);
 }
 return join('+',@{(sort{@$a<=>@$b}@o)[0]});
}


print quadsum(shift(@ARGV) or 42)."\n";

Vayu

#!/usr/bin/perl
# written by Ben Herfurth

use strict;
use warnings;

my $zahl = $ARGV[0];
my $zahl2 = $zahl;
my @zahlen = computeSquares();

$zahl = $ARGV[0];
if(odd($zahl/2)) {
   $zahl2 = ($zahl/2)-0.5; 
} else {
   $zahl2 = $zahl/2;
}
my @zahlen1 = computeSquares();

if ($#zahlen < $#zahlen1) {
   printArray(@zahlen);
} else {
   printArray(@zahlen1);
}

sub odd {
   my $odd = shift;
   if($odd =~ /\./) {
      return 1;
   }
}

sub printArray {
   foreach my $i (0..$#_) {
      print "+" if $i > 0;
      print $_[$i];
   }
}

sub computeSquares {
   my @ar;
   while($zahl) {
      if(sqrt($zahl2) !~ /\./) {
         $zahl -= $zahl2;
         push @ar, $zahl2;
         $zahl2 = $zahl;
         next;
      }
      $zahl2--;
   }
   return @ar;
}

Kommentare werden am besten in folgender Form vorgenommen, damit sie im Inhaltsverzeichnis angezeigt werden (natürlich ohne das <verbatim>):
---### Main.??? - 14 Jul 2003 - Betreff

UtilPerlSkripteSubForm edit

Titel Rätsel der Woche 2007/4
Autor IshKa
Bereich RaetselDerWoche
Topic revision: r4 - 2007-03-01 - 11:32:52 - IshKa
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um WebBottomBarExample">Rückmeldung.