Zadanie na długie zimowe popołudnie

A taki konkursik zaproponuję na rozgrzewkę ;)

Wiadomo, że

1+2-(3-4-5)*6*7*8-9 = 2010.

Napisać program, który wyznaczy wszystkie poprawne wyrażenia arytmetyczne zawierające po kolei liczby od 1 do 9, których wynik daje pozostałe numery lat XXI wieku, tj 2011 do 2100. Dopuszczalne znaki działań to cztery podstawowe działania arytmetyczne: +-*/ (dzielenie "nie-całkowite") oraz nawiasy (mogą się zagnieżdżać).

Preferowane języki: c/c++, java, python, ruby, php. Najlepsze rozwiązanie wybieramy klikając na kciuki przy odpowiedzi ;)

2 lata, 4 miesiące temu | edytowane przez: paskuda 9517

  • Rozwiązanie w Prologu. Niestety nie udało mi się znaleźć przenośnego modułu do arytmetyki na liczbach wymiernych, więc jest dłuższe niż mogłoby być:

    :- op(500, 'xfx', '--').
    
    expr([A|T]--X, A) :- T == X.
    expr([A|B]--T, E) :- B \== T, expr([A|X]--X, B--T, E).
    
    expr(LHead--LTail, RHead--RTail, E) :- 
        LHead \== LTail, RHead \== RTail,
        expr(LHead--LTail, E1),
        expr(RHead--RTail, E2),
        myop(X), E =.. [X, E1, E2].
    
    expr(LHead--LTail, RHead--RTail, E) :-
        LHead \== LTail, RHead \== RTail,
        RHead = [A|B],
        LTail = [A|X],
        expr(LHead--X, B--RTail, E).
    
    
    myop('+').
    myop('-').
    myop('*').
    myop('/').
    
    eval(X, r(X, 1) ) :- integer(X).
    eval(A + B, r(L, M)) :- 
        eval(A, r(AL, AM)), eval(B, r(BL, BM)),
        L is AL * BM + BL * AM,
        M is AM * BM.
    
    eval(A - B, r(L, M)) :- 
        eval(A, r(AL, AM)), eval(B, r(BL, BM)),
        L is AL * BM - BL * AM,
        M is AM * BM.
    
    eval(A * B, r(L, M)) :- 
        eval(A, r(AL, AM)), eval(B, r(BL, BM)),
        L is AL * BL,
        M is AM * BM.
    
    eval(A / B, r(L, M)) :- 
        eval(A, r(AL, AM)), eval(B, r(BL, BM)), BL \= 0, 
        L is AL * BM,
        M is AM * BL.
    
    year(E, V) :- 
        expr([1,2,3,4,5,6,7,8,9|X]--X, E),
        eval(E, r(L,M)),
        0 is L mod M, % ulamek skracalny do liczby calkowitej
        V is L // M, V >= 2011, V =< 2100.
    
    :- findall(E, (year(E, V), format('~p = ~d~n', [E, V])), _), halt.
    

    Poprawka

    Poprzednia wersja miała trochę zbyt zachłanną metodę nawiasowania. Teraz powinno być już dobrze. Wyszło mi 32386 wyników (trochę sporo). Oczywiście nie uwzględniam takich rzeczy jak łączność dodawania, więc (1+2)+3 jest różnym nawiasowaniem od 1+(2+3).

    Rozwiązanie nie jest zbyt eleganckie, bo wykonanie zajęło przerażające 4.5min :P Jak można przyśpieszyć ? Zamiast liczyć kolejne podproblemy od nowa, należałoby je spamiętywać jako szablony, tzn. nawiasowań 3-ch elementów za pomocą 4-rech operatorów jest 32, nie zależnie od tego jakie to elementy. Żeby wygenerować szablon dla czterech elementów wystarczy odpowiednio połączyć szablony dla 3-ch,2-ch i jednego elemenu. Itd... Oczywiście zużycię pamięci wzrośnie, ale obliczenia będą dużo krótsze.

  • ha, przestrzeń rozwiązań to 4^8 = 65536 (liczba wariacji z powtórzeniami 4 znaków działań na 8 pozycjach) razy liczba Catalana C(8) = 1430 (liczba rozmieszczeń par nawiasów na 9 elementach) - co daje 93.716.480. W sumie pikuś - można przeszukać algorytmem bruteforce wszystkie możliwe ustawienia działań i nawiasów, i wybrać rozwiązania mieszczące się w założonym przedziale :-)

    Ktoś ma czas się pobawić? Ja chyba nie bardzo...

  • Pół dnia mnie w robocie męczyło, ale nie miałem kiedy odpalić edytora. Co prawda zapału wystarczyło mi tylko na wersję bez obsługi nawiasów, co z kolei powoduje, że wyników brak (chyba, że dopuści się wiek XVIII ;-).

    To co było tutaj jest teraz tu: ycalc-1.0



    Znudziła mnie robota więc otwarłem raz jeszcze i dopisałem do mojego głupiego brutala obsługę jednej pary nawiasów (wcześniej też raz dopisałem, ale tamtej wersji nie pokażę ;-).

    
    #! /usr/bin/perl
    
    # http://9fingers.pl/questions/723/Zadanie-na-dlugie-zimowe-popoludnie
    # brute forcem go :D
    
    use strict;
    use warnings;
    
    my %wynik; # hasz z wynikami do wypisania jak już się skończy ta nierówna walka
    my %stat; # licznik sprawdzanych „równań”
    
    
    for my $n ( 0 .. 65535 )
    {
        $_ = sprintf( "%016b", $n );
        s/(\d{2})/ ( $1 == '00' ) ? "+" : 
                   ( $1 == '01' ) ? "-" : 
                   ( $1 == '10' ) ? "*" : "\/"/ge; 
    
        my $i = 1;
        s/(.)/$i++.$1/eg;
        s/$/$i/e;
    
        &check;
    
        # 1 para nawiasów
        my $mem = $_;
    
        for my $w ( 2 .. 8 ) # szerokość pary nawiasów
        {
            for my $i ( 1 .. 10 - $w )
            {
                my $j = $i + $w - 1;
                s/($i.*$j)/\($1\)/;
                &check;
                $_ = $mem;
            }
        }
    
    }
    
    # ------------------------------------------------------------------------------
    
    # Wypluj wyniki
    print "Sprawdzono $stat{tested} możliwości, prawidłowych: $stat{good}\n";
    foreach my $rok ( sort ( keys %wynik ) )
    {
        print "* $rok $wynik{$rok}\n";
    }
    
    # ------------------------------------------------------------------------------
    
    sub check
    {
        $stat{tested}++;
        my $wynik = ( eval ) ? eval : 0;
    
    #    print STDERR "Sprawdzam: $_ = $wynik\n";
    
        $wynik{ $wynik } .= sprintf " = %s", $_, $stat{good}++
            if ( ( $wynik == int( $wynik ) ) && 
                 ( $wynik >= 2011 )          &&
                 ( $wynik <= 2100 ) 
               );
    }
    

    Wyniki działania tego cudactwa jest tu: ycalc-1.3-rezultat ( ideone oddało kredki :D )

    Na moje to koniec brute force w ten sposób, choć nie koniec brutalnych metod podejścia do tematu zapewne, ale nie sądzę, żeby mi się jeszcze chciało. Inna równie brutalna metoda która przychodzi mi do głowy to wygenerowanie stringów w odwrotnej notacji polskiej, najpierw ich policzenie, a później konwersja na zapis dla ludzi. Ale nawet na kartce nie próbowałem jeszcze czy to w ogóle miałoby sens. Reasumując: jeśli celem jest wygenerowanie wszystkich możliwych wyrażeń to ja się poddaję :D.

  • czuję się zawiedziony, że pascal nie jest preferowanym językiem ;-(

    zrobiłem w Pascalu program robiący cztery podstawowe działania, ale bez nawiasów:

        procedure doSth(gByte: Byte; gSum: Real; gSth: Byte; gText: String);
        begin
            Inc(gByte);
            case gSth of
                0 : begin gSum := gSum + gByte; gText := gText + '+' + IntToStr(gByte); end;
                1 : begin gSum := gSum - gByte; gText := gText + '-' + IntToStr(gByte); end;
                2 : begin gSum := gSum * gByte; gText := gText + '*' + IntToStr(gByte); end;
                3 : begin gSum := gSum / gByte; gText := gText + '/' + IntToStr(gByte); end;
            end;
            if gByte < 9 then
            begin
                doSth(gByte, gSum, 0, gText);
                doSth(gByte, gSum, 1, gText);
                doSth(gByte, gSum, 2, gText);
                doSth(gByte, gSum, 3, gText);
            end
            else
            begin
                if (gSum >= 2011) and (gSum <= 2100) and (gSum = Trunc(gSum)) then
                    Output(gText + '=' + IntToStr(Trunc(gSum)));            
            end;
        end;
    
    begin
        doSth(1, 0, 0, '1'); // addition
        doSth(1, 0, 1, '1'); // subtraction
        doSth(1, 0, 2, '1'); // multiply
        doSth(1, 0, 3, '1'); // division
    end;
    

    program wyprodukował:

    1+2+3+4*5*6-7*8-9=2095
    1+2+3-4+5*6-7*8*9=2088
    1+2+3-4+5*6*7*8+9=2025
    1+2+3*4+5+6*7+8*9=2025
    1+2+3*4+5/6*7*8*9=2100
    1+2+3*4-5+6+7*8*9=2016
    1+2+3/4+5*6*7*8-9=2091
    1+2+3/4*5*6*7*8-9=2091
    1+2-3+4-5+6*7*8*9=2016
    1+2-3+4*5+6+7*8*9=2016
    1+2*3+4+5+6+7*8*9=2016
    1+2*3+4*5*6*7+8-9=2099
    1+2*3+4*5*6*7-8-9=2083
    1+2*3-4*5-6*7*8*9=2016
    1+2*3*4+5+6-7*8*9=2016
    1+2/3/4+5*6*7+8*9=2025
    1-2+3+4+5-6*7*8*9=2016
    1-2+3+4*5+6*7+8*9=2025
    1-2+3+4*5/6*7*8*9=2100
    1-2*3-4/5+6*7*8*9=2016
    1-2*3/4+5*6+7*8*9=2016
    1-2/3+4/5*6*7*8*9=2016
    1*2+3+4*5*6+7+8*9=2025
    1*2-3+4+5*6-7*8*9=2088
    1*2-3+4+5*6*7*8+9=2025
    1*2-3-4+5+6*7*8*9=2016
    1/2+3+4*5*6+7+8*9=2025
    1/2-3+4+5*6-7*8*9=2088
    1/2-3+4+5*6*7*8+9=2025
    1/2-3-4+5+6*7*8*9=2016
    

  • Witam, jak się dzisiaj czujesz? Mam nadzieję, że wszystko jest dobrze. Nazywam się na stałym poziomie. W poszukiwaniu człowieka, który rozumie znaczenie miłości, zaufania i wiary w siebie, a nie ten, kto widzi miłość jako jedyny sposób zabawy, ale dojrzały człowiek z Nicei wizję tego, co na świecie jest wszystko o, a po przeczytaniu profilu tutaj (matchperfect) I wziął interes w ciebie, więc zarzuty odpowiedź mi w tym e-mail (constantduke10@hotmail.com. Będę bardzo szczęśliwy, aby przeczytać odpowiedzi tak, że ja wysłać moje zdjęcie dla Ciebie możemy wówczas zacząć wiedzieć więcej o sobie nawzajem. Dziękuję za przeczytanie mojego maila i być Bless.

    stałej (constantduke10@hotmail.com))

    Hello, how are you doing today? i hope all is well. My name is constant., In search of a man who understand the meaning of love as Trust and faith in each other rather than one who sees love as the only way of fun, but a matured Man with Nice Vision of what the world is all about, and after reading your profile here in(matchperfect ) I took Interest in you, so pleas reply me with this Email (constantduke10@hotmail.com. i will be very happy to read your reply so that i will send my picture to you then we can start know more about each other. Thanks for reading my mail and be Bless.

    constant (constantduke10@hotmail.com))

Zaloguj się, aby dodać swoją odpowiedź