#!/usr/bin/perl

use integer;

# Utworzenie pewnych prywatnych, trwaych zmiennych.
my @liczby_pierwsze     = ( 2, 3, 5 );
my @wielokrotnosci      = ( 4, 9, 25 );
my $ostatnia_liczba     = 5;

# @sterta jest sterta indeksow dla tablic @liczby_pierwsze i @wielokrotnosci.
# Zindeksowana wartosc @wielokrotnosci jest uzywana do sortowania sterty.
# W czasie kolejnych testow zawsze pomijane sa liczby parzyste i wielokrotnosci 3,
# przez co tablica @sterta rozpoczyna sie od 2  indeksu dla liczby pierwszej 5.
my @sterta = ( 2 );

# $ostatni_przyrost pozwala na pominiecie liczb parzystych i wielokrotnosci 3.
# Ta zmienna jest zawsze zwiekszana o 2 lub 4. Poniewaz pierwsza wartosc 
# $ostatni_przyrost jest nieparzysta(5), to i kolejne wartosci sa nieparzyste.
# Nigdy nie  sa uzywane takze wielokrotnosci 3 w dowolnej grupie 6.  Pierwsze
# wartosci to:  5+2 -> 7, 7+4 -> 11
my $ostatni_przyrost = 4;

sub goldbach {
    use integer;
    my ($n) = shift;
    my ($dolna, $gorna, $liczby_pierwsze);
    ($liczby_pierwsze, $gorna) = liczby_pierwsze($n); # Procedura z wczesniejszej czesci rozdzialu.
    $dolna = 0;
#   return 1 unless $n > 2e10;     # Liczba juz sprawdzona!
#                                  # (Ale funkcja liczby_pierwsze() sprawi problemy,
#                                  # jesli ta granica zostanie znacznie przekroczona.)
    return if $n % 2;              # Powrot jesli liczba jest nieparzysta.
    while( $dolna <= $gorna ) {
        my $suma = $liczby_pierwsze->[$dolna] + $liczby_pierwsze->[$gorna];
        if ($suma == $n) {
            return ($liczby_pierwsze->[$dolna], $liczby_pierwsze->[$gorna]);
        } elsif ($suma < $n) {
            ++$dolna;
        } else {
            --$gorna;
        }
    }

    print "HIPOTEZA GOLDBACHA ZOSTALA OBALONA: $n\n";
    print "\a" while 1;
}

print "922 to ", join(' + ', goldbach(992)), "\n";

# Binarne przeszukanie tablicy @liczby_pierwsze w celu odnalezienia indeksu
# najwiekszej liczby pierwszej, ktora jest <= argumentowi.
  sub _bin_pierwsza {
my ($n, $dolna, $gorna) = (shift, 0, $#liczby_pierwsze);
while ( $dolna < $gorna ) {
my $srodkowa = ($dolna+$gorna+1) / 2;
                if ( $liczby_pierwsze[$srodkowa] > $n ) {
                    $gorna = $srodkowa -1
                } else {
                    $dolna = $srodkowa;
                }
         }
         return $dolna;
}

# @sterta jest sterta indeksow dla @liczby_pierwsze i @wielokrotnosci.
# @sterta jest posortowana wedlug zindeksowanej wartosci @wielokrotnosci.
# Pierwszy element zostal wlasnie zwiekszony i moze zajmowac niewlasciwe miejsce.
sub _sterta_dol {
my $is = 0;                     # indeks sterty
my $il = $sterta[0];            # indeks liczby pierwszej
my $wp = $wielokrotnosci[$ip];  # wartosc liczby pierwszej
my $ip;                         # indeks potomka
while ( ($ip = $is*2 + 1) < @sterta ) {
++$ip
             if ($ip < $#liczby_pierwsze)
             && ($wielokrotnosci[$sterta[$ci+1]] < $wielokrotnosci[$sterta[$ci]]);
last unless $wielokrotnosci[$sterta[$ip]] < $wp;
$sterta[$is] = $sterta[$ip];
$is = $ip;
    }
    $sterta[$is] = $il;
}

# Zwrocenie wszystkich liczb pierwszych az do $n
#   oraz indeksu najwiekszej liczby pierwszej, ktora jest <= $n.
sub liczby_pierwsze {
    my $n = shift;

    # Male liczby nie sa liczbami pierwszymi ani zlozonymi.
    return (\@liczby_pierwsze, undef) if $n < 2;

    # Jesli wczesniej wygenerowano wystarczajaco duzo liczb pierwszych,
    # to nalezy zwrocic aktualna tablice i wlasciwy indeks.
    return (\@liczby_pierwsze, _bin_pierwsza($n) ) if $n < $ostatnia_liczba;

    # W przeciwnym wypadku nalezy rozszerzyc liste liczb pierwszych.
    while ( $n > $ostatnia_liczba ) {
        # Wygenerowanie kolejnej liczby pierwszej, przeskok o 2 lub 4 oraz pominiecie
        # wszystkich liczb parzystych oraz nieparzystych wielokrotnosci 3.
        $ostatnia_liczba += ($ostatni_przyrost = 6-$ostatni_przyrost);
        my $sterta;
        while ( $ostatnia_liczba > $wielokrotnosci[$sterta=$sterta[0]] ) {
            # Przemieszczenie wszystkich liczb pierwszych, ktorych kolejna
            # wielokrotnosc zostala przekazana.
            $wielokrotnosci[$sterta] += 2 * $liczby_pierwsze[$sterta];
            _sterta_dol();
        }
        # Pominiecie tego kandydata, jesli jest wielokrotnoscia.
        next if $ostatnia_liczba == $wielokrotnosci[$sterta];
        # $ostatnia_liczba jest kolejna liczba pierwsza.
        push ( @liczby_pierwsze, $ostatnia_liczba );
        # Pierwsza sprawdzana wielokrotnosc to pierwiastek kwadratowy 
        # (wszystkie mniejsze liczby sa rowniez wielokrotnosciami innych mniejszych 
        # liczb pierwszych).
        push ( @wielokrotnosci, $ostatnia_liczba*$ostatnia_liczba );
        # Dodanie liczby do sterty. Ta liczba musi byc wieksza od wszystkich
        # innych elementow sterty, przez co jest potrzebna funkcja 
        # _sterta_gora.

        push ( @sterta, $#liczby_pierwsze );
    }

    # Dalsze wyszukiwanie nie jest potrzebne. Zwracana wartosc moze byc
    # indeksem ostatniego elementu (jesli odpowiada on elementowi docelowemu,
    # ktory jest liczba pierwsza) lub indeksem przedostatniego elementu
    # (jesli ostatni element jest wiekszy niz element docelowy; zdarza sie
    # to w przypadku, gdy element docelowy jest liczba zlozona). 
    # Z tego powodu nalezy ustalic, czy konieczne jest odjecie 1
    # od indeksu ostatniego elementu:
    return (\@liczby_pierwsze, $#liczby_pierwsze - ($n != $ostatnia_liczba));
}

