Numeri StickStack


22

StickStack è un linguaggio di programmazione basato su stack molto semplice con solo due istruzioni:

  • | spinge la lunghezza della pila sulla pila
  • -estrae i primi due elementi dallo stack e rimuove la loro differenza ( second topmost - topmost)

Dettagli sulla lingua

  • Lo stack è vuoto all'inizio del programma.
  • Tutte le istruzioni vengono eseguite in sequenza da sinistra a destra.
  • Se ci sono meno di 2 numeri nello stack l' -istruzione è illegale.
  • Alla fine dell'esecuzione lo stack dovrebbe contenere esattamente un numero .

Qualsiasi numero intero può essere generato da un programma StickStack. Per esempio:

|||--||-- generates the number 2 through the following stack states:

[]
[0]
[0, 1]
[0, 1, 2]
[0, -1]
[1]
[1, 1]
[1, 1, 2]
[1, -1]
[2]    

Per valutare il tuo codice StickStack puoi usare questo valutatore online (CJam) . (Grazie per @Martin per il codice.)

L'obiettivo

Dovresti scrivere un programma o una funzione che ha dato un numero intero come output di input o restituisce una stringa che rappresenta un programma StickStack che emette il numero dato.

punteggio

  • Il tuo punteggio principale è la lunghezza totale dei programmi StickStack per i casi di test indicati di seguito. Il punteggio più basso è migliore.
  • Il tuo invio è valido solo se hai eseguito il programma su tutti i casi di test e contato il tuo punteggio.
  • Il tuo punteggio secondario (tiebreaker) è la lunghezza del tuo programma o funzione generatrice.

Casi di test di input

(Ogni numero è un caso di test diverso.)

-8607 -6615 -6439 -4596 -4195 -1285 -72 12 254 1331 3366 3956 5075 5518 5971 7184 7639 8630 9201 9730

Il programma dovrebbe funzionare per qualsiasi numero intero (che può essere gestito dal tipo di dati) e non solo per i casi di test indicati. Le soluzioni per i numeri di test non devono essere codificate nel programma. In caso di dubbi sulla codifica hardware, i numeri di test verranno modificati.


Suggerisco di aggiungere una clausola che dice "E funziona in un periodo di tempo ragionevole" per prevenire la forza bruta.

@Reticità che è implicita in "valido solo se hai eseguito il programma su tutti i casi di test"
edc65

Risposte:


7

Python 2 - 5188

Abbastanza efficiente nel tempo, e sembra essere (probabilmente) la soluzione ottimale. Ho osservato che uno schema come

|||||-|||-|-|-|------ (una soluzione ottimale per 25)

può essere delineato come

 0  |
-1  |                  
+2   |                 -
-3    |               -
+4     | |           -
-5      - |         -
+6         | | | | -
-7          - - - -

dove ciascuno il valore totale alla fine è la somma di (il valore di ogni livello moltiplica il numero di '|' s). Quindi, per esempio sopra, abbiamo -1*1 + 2*1 - 3*1 + 4*2 - 5*1 + 6*4 = 25. Usando questo ho scritto questa soluzione che produce risultati simili ad altre risposte, e in un tempo insignificante.

Credo che questa sia la soluzione ottimale, poiché collaudo tutte le possibili altezze ottimali (in realtà ne collaudo molte più del necessario) e sono abbastanza sicuro che la soluzione coinvolga sempre al massimo uno strato con due "|" oltre l'ultimo (posso garantirlo per numeri positivi ma non sicuro al 100% di negativi).

def solution(num):
    if num == 0:
        return '|'

    neg = num<0
    num = abs(num)
    high = int(num**0.5)

    def sub(high):
        counts = [1]*high
        total = num - (high+neg)/2

        if total%high == 0:
            counts[-1] += total/high
        else:
            counts[-1] += total/high
            if (total%high)%2==1 and not neg:
                counts[-1] += 1
                counts[-(total%high)-1] += 1
            elif (total%high)%2==0 and neg:
                counts[(total%high)-2] += 1
                counts[0] += 1
            else:
                counts[total%high-1] += 1

        string = ""
        for c in counts[::-1]:
            string = '|-'*(c-1)+'|'+string+'-'
        return '|'+string

    return min((sub(h) for h in range(2-neg,2*high+2,2)), key=lambda s: len(s))

Ecco il codice che ho usato per testarlo

string = "-8607 -6615 -6439 -4596 -4195 -1285 -72 12 254 1331 3366 3956 5075 5518 5971 7184 7639 8630 9201 9730"
total = 0

def string_to_binary(string):
    total = 0
    for i,char in enumerate(string[::-1]):
        total += (char=='|')*(2**i)
    return total

def stickstack(bits,length):
    stack = []
    for i in range(length):
        d,bits = divmod(bits,2**(length-i-1))
        if d == 1:
            stack.append(len(stack))
        else:
            stack[-2] -= stack[-1]
            stack = stack[:-1]
    return stack

for num in string.split():
    s = solution(int(num))
    print '%s:' % num
    print s
    result = stickstack(string_to_binary(s),len(s))
    print 'Result: %s' % result
    print 'Length: %s' % len(s)
    total += len(s)
    print

print 'Total length: %s' % total

2
Ottima soluzione! Credo che il punteggio sia ottimale (in base al mio calcolo della forza bruta) e in base alla tua descrizione, penso che il tuo algoritmo fornisca sempre le migliori soluzioni.
randomra,

@randomra Penso che sia probabile che lo sia, ma ero solo bruto forzato a circa +/- 100, quindi non ero pronto a dire che era necessariamente il migliore, ma ora che ci penso non riesco a vedere come potrebbe essere fatto meglio.
KSab

+1 molto bello. Come posso provarlo? Che pyton? (Non sono un pitone, ho solo accidentalmente installato un pitone 3.4 sul mio laptop).
edc65,

@ edc65 Ho aggiunto il codice per testarlo; anche questo sta usando Python 2.7, quindi cose come le istruzioni di stampa non funzioneranno con Python 3
KSab

Per quel che vale, posso confermare che questo risultato è ottimale: ho provato una soluzione di forza bruta (un BFS in effetti), verificando fino a 450 (e ancora in esecuzione). Stessi risultati tuoi.
edc65,

12

Java, 5208 5240 5306 6152

Questa è una funzione ricorsiva che si avvicina al bersaglio, con casi base per quando arriva entro 5 (che spesso è solo un passo).

In sostanza, è possibile ottenere (a*b)+(a/2)per (a+b)*2bastoni con uno schema semplice. Se aè dispari, il risultato sarà negativo, quindi questo porta ad una strana logica.

Questo richiede circa un minuto per 2 31 -1, con una lunghezza di 185.367 come risultato. Funziona quasi istantaneamente per tutti i casi di test, però. Segna 4*(sqrt|n|)in media. Il caso di test individuale più lungo è 9730, il che si traduce in uno stack di lunghezza 397:

|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-|||||||||||||||||||||-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|--------------------------------------------------------------------------------------------------|-

Aggiornare:

Trovato un modo più breve per aggiungere / sottrarre dal modello di base. Di nuovo in testa (per ora)!


Con imbracatura e casi di test:

import static java.lang.Math.*;

public class StickStacker {

    public static void main(String[] args){
        StickStacker stacker = new StickStacker(); 
        int tests[] = {-8607,-6615,-6439,-4596,-4195,-1285,-72,12,254,1331,3366,3956,5075,5518,5971,7184,7639,8630,9201,9730};
        int sum = 0;
        for(int test : tests){
            String sticks = stacker.stickStack3(test);
            sum += sticks.length();
            System.out.println("In: " + test + "\t\tLength: " + sticks.length());
            System.out.println(sticks+"\n");
        }
        System.out.println("\n\nTotal: "+sum);          
    }

    String stickStack3(int n){return"|"+k(n);}
    String k(int n){
        String o="";
        int q=(int)sqrt(abs(n)),a,b,d=1,e=0,c=1<<30,
        z[]={232,170,42,10,2,0,12,210,52,844,212};
        a=n>0?q:-q;
        a-=n>0?a%2<1?0:1:a%2<0?0:-1;

        for(b=0;b<abs(a)+10;b++)
            if(abs(n-(a*b+a/2-(n>0?0:1)))<abs(a)&&abs(a)+b<c){
                    c=abs(a)+b;
                    d=a;e=b;
            }

        for(a=0;a++<e;)o+="-|";
        for(a=0;a++<abs(d);)o="|"+o+"-";

        c=n-(d*e+d/2-(n>0?0:1));
        if(c>0&&c<abs(d)){
            if(c%2==0)
                o=o.substring(0,c)+"-|"+o.substring(c);
            else
                o=o.substring(0,c+1)+"-|"+o.substring(c+1)+"|-";
            c=0;
        }else if(c<0&-c<abs(d)){
            if(c%2!=0)
                o=o.substring(0,-c)+"-|"+o.substring(-c);
            else
                o=o.substring(0,-c-1)+"-|"+o.substring(-c-1)+"|-";  
            c=0;
        }

        return n==0?"":n<6&&n>-6?
                Long.toBinaryString(z[n+5])
                .replaceAll("0","-")
                .replaceAll("1","|"):
                o+k(c);
    }
}

Giocherà a golf (altro) nell'improbabile caso di un pareggio esatto.


Sei sicuro del tuo punteggio per 2 ^ 31? Il mio punteggio per 2 ^ 30 è 131099 e 185369 per 2 ^ 31-1.
edc65,

@ edc65 Devo averlo digitato male. Pensavo che sembrasse un po 'basso ... Comunque, grazie per averlo notato e aver dato un po' di competizione. Ora è il momento di vedere se posso fare di meglio :)
Geobits

4

JavaScript (ES6) 5296 6572

Modifica Come ho detto nella mia spiegazione, non sono bravo a risolvere equazioni intere. La mia ipotesi sul valore b non era così buona, quindi ho ampliato la gamma di valori da provare. E (wow) sto guidando ormai.

Modifica 2 Bug fix, stessi risultati. Ho un'idea, ma non posso inchiodarla.

Byte: ~ 460, abbastanza golf. Funziona su numeri interi a 32 bit, tempo di esecuzione vicino a 0.

Il codice è la funzione F (nascosta nello snippet) di seguito.
Esegui lo snippet per eseguire il test (in FireFox).

Spiegazione

Numeri positivi, per cominciare. Inizia con una "base" (prova in CJam se vuoi, spazi consentiti)

| gives 0  
||| -- gives 1
||||| ---- gives 2
||||||| ------ gives 3 

Riepilogo: 1 stick, quindi b * 2 stick, quindi b * 2 trattini

Quindi prova ad aggiungere uno o più '- |' nella divisione centrale. Ognuno aggiunge un incremento fisso che è due volte la base di partenza e può essere ripetuto più volte. Quindi abbiamo una formula, con b = base e r = fattore di ripetizione dell'incremento

v=b+r*2*b

b=1, r=0 to 3, inc=2
| || -- 1 
| || -| -- 3 
| || -| -| -- 5 
| || -| -| -| -- 7

b=3, r=0 to 3, inc=6
| |||||| ------ 3
| |||||| -| ------ 9
| |||||| -| -| ------ 15
| |||||| -| -| -| ------ 21

Vedere? Il valore aggiunto aumenta rapidamente e ogni aggiunta è ancora solo 2 caratteri. L'incremento di base fornisce 4 caratteri in più ogni volta.

Dato v e la nostra formula v = b + r * 2 * b, dobbiamo trovare 2 in b b. Non sono un esperto in questo tipo di equazione, ma b = int sqrt (v / 2) è una buona ipotesi iniziale.

Quindi abbiamo un r che b insieme danno un valore vicino a v. Raggiungiamo v esattamente con incrementi ripetuti (|| -) o decrementi (| -).

Segui lo stesso ragionamento per i numeri negativi, purtroppo la formula è simile ma non uguale.


1

JavaScript, 398710

94 byte / caratteri di codice

Ho trovato una soluzione! ... e poi leggi la risposta di Sparr ed era esattamente la stessa.

Ho pensato che l'avrei pubblicato comunque, poiché js consente un numero leggermente inferiore di caratteri.

Ecco una versione non modificata del codice:

function p(a){
    s = "";
    if(a<=0){
        for(i=0; i<-2*a-1;i++)
            s="|"+s+"-";
        return "|"+s;
    }
    return "|"+p(0-a)+"-";
}

1
ok, se stiamo giocando a golf con le soluzioni 398710, gioca! qualcuno verrà fuori con cjam o pyth e ci batterà entrambi, però :(
Sparr

1

Python, 398710 (71 byte)

La soluzione più semplice possibile, credo. Utilizza 4 * n (+/- 1) caratteri dello stickstack per rappresentare n.

def s(n):return'|'*(n*2+1)+'-'*n*2 if n>=0 else'|'*(n*-2)+'-'*(n*-2-1)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.