Esprimi rapidamente un numero con solo 0-9 e le quattro operazioni, più un'altra in più


14

Spiegazione

Befunge è un programma bidimensionale che utilizza stack .

Ciò significa che, per fare 5 + 6, scrivi 56+, nel senso:

56+
5    push 5 into stack
 6   push 6 into stack
  +  pop the first two items in the stack and add them up, and push the result into stack

(to those of you who do not know stacks, "push" just means add and "pop" just means take off)

Tuttavia, non possiamo spingere il numero 56 direttamente nello stack.

Per fare ciò, dobbiamo 78*invece scrivere , che si moltiplica 7e8 e spinge il prodotto nello stack.

Dettagli

Per ogni numero da 1an , trovare una stringa costituita solo questi caratteri: 0123456789+-*/:(vorrei non usare% modulo.)

L'obiettivo è trovare il più corto stringa che possa rappresentare il numero, usando il formato sopra descritto.

Ad esempio, se l'input è 123, allora l'output sarebbe 67*9:*+. L'output deve essere valutato da sinistra a destra.

Se ci sono più di un output accettabile (ad esempio 99*67*+è anche accettabile), è possibile stampare uno qualsiasi (nessun bonus per la stampa di tutti).

Ulteriori spiegazioni

Se ancora non capisci come 67*9:*+valuta 123, ecco una spiegazione dettagliata.

stack    |operation|explanation
          67*9:*+
[6]       6         push 6 to stack
[6,7]      7        push 7 to stack
[42]        *       pop two from stack and multiply, then put result to stack
[42,9]       9      push 9 to stack
[42,9,9]      :     duplicate the top of stack
[42,81]        *    pop two from stack and multiply, then put result to stack
[123]           +   pop two from stack and add, then put result to stack

TL; DR

Il programma deve trovare la stringa più breve che può rappresentare l'input (numero), usando il formato sopra specificato.

PUNTEGGIO

  • L'abbiamo già fatto con la minima quantità di codice . Questa volta, le dimensioni non contano.
  • La tua lingua preferita deve avere un compilatore / interprete gratuito per il mio sistema operativo (Windows 7 Enterprise).
  • Bonus se includi il link al compilatore / interprete (sono troppo pigro).
  • Se possibile, si prega di includere un timer per mia comodità. L'uscita dal timer è valida.
  • Il punteggio sarà il più grande nin 1 minuto.
  • Ciò significa che il programma deve stampare la rappresentazione richiesta da quel momento in 1poi.
  • Nessun hard-coding, tranne che 0per 9.

(altro) SPECIFICHE

  • Il programma non è valido se genera una stringa più lunga del necessario per qualsiasi numero.
  • 1/0=ERROR
  • 5/2=2, (-5)/2=-2, (-5)/(-2)=2,5/(-2)=-2

disambiguation

Il -è second-top minus top, nel senso che 92-i rendimenti 7.

Allo stesso modo, /is second-top divide top, significa che 92/ritorna 4.

Programma di esempio

Lua

Utilizza la ricerca approfondita.

local function div(a,b)
    if b == 0 then
        return "error"
    end
    local result = a/b
    if result > 0 then
        return math.floor(result)
    else
        return math.ceil(result)
    end
end

local function eval(expr)
    local stack = {}
    for i=1,#expr do
        local c = expr:sub(i,i)
        if c:match('[0-9]') then
            table.insert(stack, tonumber(c))
        elseif c == ':' then
            local a = table.remove(stack)
            if a then
                table.insert(stack,a)
                table.insert(stack,a)
            else
                return -1
            end
        else
            local a = table.remove(stack)
            local b = table.remove(stack)
            if a and b then
                if c == '+' then
                    table.insert(stack, a+b)
                elseif c == '-' then
                    table.insert(stack, b-a)
                elseif c == '*' then
                    table.insert(stack, a*b)
                elseif c == '/' then
                    local test = div(b,a)
                    if test == "error" then
                        return -1
                    else
                        table.insert(stack, test)
                    end
                end
            else
                return -1
            end
        end
    end
    return table.remove(stack) or -1
end

local samples, temp = {""}, {}

while true do
    temp = {}
    for i=1,#samples do
        local s = samples[i]
        if eval(s) ~= -1 or s == "" then for n in ("9876543210+-*/:"):gmatch(".") do
            table.insert(temp, s..n)
        end end
    end
    for i=1,#temp do
        local test = eval(temp[i])
        if input == test then
            print(temp[i])
            return
        end
    end
    samples = temp
end

Aspetta, se non possiamo spingere 56direttamente nello stack, come possiamo spingere 78nello stack?
R. Kap,

Non possiamo spingere 56cinquantasei direttamente nello stack, ma possiamo spingere 7sette e 8otto separatamente nello stack.
Leaky Nun,

1
@ R.Kap: quando fai qualcosa come 56in Befunge, stai spingendo le cifre , quindi finisci con una pila di [5, 6]. Per ottenere il numero 56, è necessario spingere 7, quindi 8nello stack e quindi moltiplicarli per ottenere il numero 56 nello stack.
El'endia Starman,

1
:rende le cose molto più complicate, quindi consiglierei di fornire un buon elenco di casi di test, ad es.86387
Sp3000,

1
il numero intero più grande in 5 secondi è una metrica errata. il tempo di calcolo per numeri più grandi non aumenterà monotonicamente, quindi molte soluzioni possono rimanere bloccate sullo stesso numero difficile da calcolare, nonostante alcune siano molto più veloci o più lente sui numeri vicini.
Sparr,

Risposte:


7

C ++, esplodendo tutta la memoria su un computer vicino a te

Genera la stringa più breve in cui il calcolo non provoca da nessuna parte un overflow di un intero con segno a 32 bit (quindi tutti i risultati intermedi sono nell'intervallo [-2147483648, 2147483647]

Sul mio sistema, questo genera una soluzione per tutti i numeri fino a 48343230 secondi inclusi, mentre si utilizza la memoria da 1,8 G. Anche numeri più alti esploderanno rapidamente l'utilizzo della memoria. Il numero più alto che posso gestire sul mio sistema è 5113906. Il calcolo richiede quasi 9 minuti e 24 GB. Quando termina internamente ha una soluzione per i 398499338valori, circa il 9% di tutti i numeri interi a 32 bit (positivi e negativi)

Richiede un compilatore C ++ 11. Su Linux compilare con:

g++ -Wall -O3 -march=native -std=gnu++11 -s befour.cpp -o befour

Aggiungi -DINT64come opzione per utilizzare un intervallo intero a 64 bit anziché a 32 bit per risultati intermedi (ciò utilizzerà circa il 50% in più di tempo e memoria). Ciò richiede un tipo incorporato a 128 bit. Potrebbe essere necessario modificare il tipo di gcc __int128. Nessun risultato modifica almeno l'intervallo [1..483432]consentendo risultati intermedi più grandi.

Inserisci -DOVERFLOW come opzione per non utilizzare un tipo intero più grande per verificare l'overflow. Ciò ha l'effetto di consentire l'overflow e il wrapping del valore.

Se il tuo sistema ha tcmalloc ( https://github.com/gperftools/gperftools ) puoi collegarti con quello risultante in un programma che è generalmente un po 'più veloce e usa un po' meno memoria. Su alcuni sistemi UNIX è possibile utilizzare un precarico, ad es

LD_PRELOAD=/usr/lib/libtcmalloc_minimal.so.4 befour 5

Utilizzo di base: genera e stampa tutti i numeri fino al target:

befour target

Opzioni:

  • -a Stampa anche tutti i numeri generati durante l'allenamento del target
  • -c Stampa anche tutti i numeri che sono stati generati a partire da un "carry" (dup)
  • -f Trova e stampa il primo numero oltre l'obiettivo che non è stato generato
  • -s Arresta se il target viene generato anche se non sono stati generati tutti i numeri precedenti
  • -SMi piace -se-f in un ciclo automatico. Non appena viene generato il target, trova il primo numero non ancora generato e crealo come nuovo target
  • -ENon uscire immediatamente quando viene raggiunto l'obiettivo. Prima finisci tutte le stringhe della lunghezza corrente
  • -ONon generare le stringhe per tutti i numeri fino al target. solo la stringa per il bersaglio
  • -o Istruzioni consentite (il valore predefinito è +-*/:
  • -b numIl valore letterale più basso che può essere spinto (impostazione predefinita a 0)
  • -B numValore letterale più alto che può essere spinto (impostazione predefinita a 9)
  • -r numIl risultato intermedio più basso consentito. Utilizzato per evitare underflow. (il valore predefinito è INT32_MIN,-2147483648
  • -R numIl risultato intermedio più alto consentito. Usato per evitare trabocco. (il valore predefinito è INT32_MAX,2147483647
  • -m memory (solo Linux) esce quando è stata allocata approssimativamente molta memoria aggiuntiva

Alcune combinazioni di opzioni interessanti:

Genera tutti i numeri fino al target e calcola il numero più piccolo che necessita di un generatore più lungo di tutti questi numeri:

befour -fE target

Genera solo target (-s), stampa solo target (-O)

befour -sO target

Trova il numero più alto che può essere generato sul tuo sistema dato il tempo e / o i vincoli di memoria (Questo lascerà il tuo sistema esaurito se lo lasci in esecuzione. Sottrai 1 dall'ultimo output "in cerca" che vedi come l'ultimo valore sicuro ):

befour -S 1

Genera soluzioni senza mai usare risultati intermedi negativi ( 30932è il primo valore che richiede risultati intermedi negativi per la stringa più corta):

befour -r0 target

Genera soluzioni senza mai spingere 0(questo non sembra portare a soluzioni non ottimali):

befour -b1 target

Genera soluzioni tra cui a..f (10..15):

befour -B15 target

Genera soluzioni senza usare dup :(aggiungi -r0poiché i valori intermedi negativi non sono mai interessanti per questo caso)

befour -r0 -o "+-*/" target

Trovare il primo valore che non può essere generato per una data lunghezza della stringa utilizzando solo +, -, *e /:

befour -ES -r0 -o "+-*/" 1

Questo in effetti genererà i primi termini di https://oeis.org/A181898 , ma inizieremo a divergere 14771perché usiamo la divisione troncante in modo che quel numero possa essere fatto con una stringa di lunghezza 13 anziché con la lunghezza 15 come serie OEIS si aspetta:

14771: 13: 99*9*9*4+9*4/

invece di

14771: 15: 19+5*6*7*9+7*8+

Poiché senza divisione del troncamento sembra inutile, la serie OEIS può essere generata meglio utilizzando

befour -ES -r0 -o"+-*" 1

Supponendo che la divisione rimanga inutile, questo mi ha dato 3 termini extra prima che mi esaurissi la memoria:

10, 19, 92, 417, 851, 4237, 14771, 73237, 298609, 1346341, 6176426, 25622578

Un'altra versione di questo programma che memorizza parte dei dati in file esterni aggiunge 135153107 e 675854293, dopo di che sono stati generati tutti gli interi a 32 bit.

befour.cpp

/*
  Compile using something like:
g++ -Wall -O3 -march=native -std=gnu++11 -s  befour.cpp -o befour
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <limits>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <chrono>
#include <unordered_map>

using namespace std;

#ifdef __GNUC__
# define HOT        __attribute__((__hot__))
# define COLD       __attribute__((__cold__))
# define NOINLINE   __attribute__((__noinline__))
# define LIKELY(x)  __builtin_expect(!!(x),1)
# define UNLIKELY(x)    __builtin_expect(!!(x),0)
#else // __GNUC__
# define HOT
# define COLD
# define NOINLINE
# define LIKELY(x)  (x)
# define UNLIKELY(x)    (x)
#endif // __GNUC__

#ifdef INT64
using Int  = int64_t;       // Supported value type
# ifndef OVERFLOW
using Int2 = __int128;      // Do calculations in this type. Check overflow
# endif // OVERFLOW
#else // INT64
using Int  = int32_t;       // Supported value type
# ifndef OVERFLOW
using Int2 = int64_t;       // Do calculations in this type. Check overflow
# endif // OVERFLOW
#endif // INT64
#ifdef OVERFLOW
using Int2 = Int;
#endif // OVERFLOW

// Supported value range
Int2 MIN = numeric_limits<Int>::lowest();
Int2 MAX = numeric_limits<Int>::max();
Int HALF_MIN, HALF_MAX;

// The initial values we can push
Int ATOM_MIN = 0;
Int ATOM_MAX = 9;

bool all    = false;    // Output all reached values
bool all_carry  = false;    // Output all values reachable using carry
bool early_exit = true;     // Exit before finishing level if goal reached
bool find_hole  = false;    // Look for first unconstructed > target
bool output = true;     // Output [1..target] instead of just target
bool single = false;    // Only go for target instead of [1..target]
bool explore    = false;    // Don't stop, increase N until out of memory
bool do_dup = false;    // Use operator :
bool do_multiply= false;    // Use operator *
bool do_add = false;    // Use operator +
bool do_subtract= false;    // Use operator -
bool do_divide  = false;    // Use operator /
char const* operators = "+-*/:"; // Use these operators
size_t max_mem  = SIZE_MAX; // Stop if target memory reached

size_t const MEM_CHECK = 1000000;

chrono::steady_clock::time_point start;

NOINLINE size_t get_memory(bool set_base_mem = false) {
static size_t base_mem = 0;
size_t const PAGE_SIZE = 4096;

// Linux specific. Won't hurt on other systems, just gets no result
size_t mem = 0;
std::ifstream statm;
statm.open("/proc/self/statm");
statm >> mem;
mem *= PAGE_SIZE;
if (set_base_mem) base_mem = mem;
else mem -= base_mem;
return mem;
}

// Handle commandline options.
// Simplified getopt for systems that don't have it in their library (Windows..)
class GetOpt {
  private:
string const options;
char const* const* argv;
int nextchar = 0;
int optind = 1;
char ch = '?';
char const* optarg = nullptr;

  public:
int ind() const { return optind; }
char const* arg() const { return optarg; }
char option() const { return ch; }

GetOpt(string const options_, char const* const* argv_) :
options(options_), argv(argv_) {}
char next() {
while (1) {
    if (nextchar == 0) {
    if (!argv[optind] ||
        argv[optind][0] != '-' ||
        argv[optind][1] == 0) return ch = 0;
    if (argv[optind][1] == '-' && argv[optind][2] == 0) {
        ++optind;
        return ch = 0;
    }
    nextchar = 1;
    }
    ch = argv[optind][nextchar++];
    if (ch == 0) {
    ++optind;
    nextchar = 0;
    continue;
    }
    auto pos = options.find(ch);
    if (pos == string::npos) ch = '?';
    else if (options[pos+1] == ':') {
    if (argv[optind][nextchar]) {
        optarg = &argv[optind][nextchar];
    } else {
        optarg = argv[++optind];
        if (!optarg) return ch = options[0] == ':' ? ':' : '?';
    }
    ++optind;
    nextchar = 0;
    }
    return ch;
}
}
};

using ms = chrono::milliseconds;

Int missing, N;
size_t cached, cached_next;

uint8_t const CARRY_MASK = '\x80';
uint8_t const LITERAL    = 0;
struct How {
// Describes how to construct a number
Int left;
Int right;
uint8_t ops, op;

How(uint8_t ops_, uint8_t op_, Int carry_=0, Int left_=0, Int right_=0) :
left(left_),
right(right_),
ops(ops_),
op(carry_ ? CARRY_MASK | op_ : op_)
{}
How() = default;
How(How&&) = default;
How& operator=(How&&) = default;
static How const* predict(Int carry, Int value, int& ops);
static void print_predicted(ostream& out, Int carry, Int value, How const* Value = nullptr);
void print(ostream& out, Int carry = 0, bool length = false) const;
};

ostream& operator<<(ostream& out, How const& how) {
how.print(out, 0, true);
return out;
}

using NumSet  = vector<Int>;
using NumSets = vector<NumSet>;

struct Known: public unordered_map<Int, How>
{
void store(NumSet& L, Int accu, uint8_t ops, uint8_t op,
       Int left=0, Int carry_right=0, Int right=0) {
++cached;
emplace(accu, How(ops, op, carry_right, left, right));
// operator[](accu) = How(ops, op, carry_right, left, right);
L.emplace_back(accu);
}
void maybe_store(Known const& known0, NumSet& L,
         Int accu, uint8_t ops, uint8_t op,
         Int carry_left, Int left, Int carry_right, Int right) {
if (count(accu)) return;
if (carry_left) {
    auto found = known0.find(accu);
    // If we can do as good or better without carry use that
    if (found != known0.end() && found->second.ops <= ops) return;
}
store(L, accu, ops, op, left, carry_right, right);
if (carry_left) return;
if (single) {
    if (UNLIKELY(accu == N)) known0.maybe_explore();
} else if (1 <= accu && accu <= N) --missing;
}
NOINLINE void maybe_explore() const COLD {
--missing;
if (explore && early_exit) do_explore();
}
NOINLINE void do_explore() const COLD {
auto i = N;
while (i < MAX && count(++i));
auto end = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<ms>(end-start).count();

cerr << "Found " << N << " at " << elapsed / 1000. << " s";
auto mem = get_memory();
if (mem) cerr << " (" << mem / 1000 / 1000.  << " MB)";
if (i < MAX || !count(i)) {
    cerr << ", now looking for " << i << endl;
    N = i;
    ++missing;
} else
    cerr << ", every value has now been generated" << endl;
}
};

struct KnowHow {
// Describes all numbers we know how to construct
NumSets num_sets;
Known known;

KnowHow() = default;
~KnowHow() = default;
KnowHow(KnowHow const&) = delete;
KnowHow& operator=(KnowHow const&) = delete;
};
// Describes all numbers we know how to construct for a given carry
// Key 0 is special: the numbers we can construct without carry (the solutions)
unordered_map<Int, KnowHow> known_how;

// Try to predict if a subtree is a delayed How and avoid descending
// into it (since it may not exist yet)
How const* How::predict(Int carry, Int value, int& ops) {
How* Value;
if (carry) {
if (value == carry) {
    Value = nullptr;
    ops = 0;
} else {
    Value = &known_how.at(carry).known.at(value);
    ops = Value->ops;
}
} else {
if (ATOM_MIN <= value && value <= ATOM_MAX) {
    Value = nullptr;
    ops = 0;
} else {
    Value = &known_how.at(0).known.at(value);
    ops = Value->ops;
}
}
return Value;
}

void How::print_predicted(ostream& out, Int carry, Int value, How const* Value) {
if (Value) Value->print(out, carry);
else if (carry) out << ":";
else if (value > 9) out << static_cast<char>(value-10+'a');
else out << value;
}

void How::print(ostream& out, Int carry_left, bool length) const {
if (length) out << 2*ops+1 << ": ";

Int carry_right = 0;
auto op_ = op;

switch(op_) {
case LITERAL:
  How::print_predicted(out, 0, left);
  break;
case '*' | CARRY_MASK:
case '/' | CARRY_MASK:
case '+' | CARRY_MASK:
case '-' | CARRY_MASK:
  carry_right = left;
  op_ &= ~CARRY_MASK;
  // Intentional drop through
case '*':
case '/':
case '+':
case '-':
  {
      int left_ops, right_ops;
      auto Left  = How::predict(carry_left,  left,  left_ops);
      // Int right = 0;
      auto Right = How::predict(carry_right, right, right_ops);

      // Sanity check: tree = left_tree + root + right_tree
      if (ops != left_ops + right_ops +1) {
      char buffer[80];
      snprintf(buffer, sizeof(buffer),
           "Broken number %d %c %d, length %d != %d + %d + 1",
           static_cast<int>(left), op_, static_cast<int>(right),
           ops, left_ops, right_ops);
      throw(logic_error(buffer));
      }

      How::print_predicted(out, carry_left,  left,  Left);
      How::print_predicted(out, carry_right, right, Right);
  }
  // Intentional drop through
case ':':
  out << op_;
  break;
default:
  throw(logic_error("Unknown op " + string{static_cast<char>(op_)}));
  break;
}
}

// carryX indicates Xv was reached using carry. If not we also know [L, known] is known_how[0]
// carryY indicates Y was reached using carry (carryY == Xv if so)
void combine(NumSet& L, Known& known, Known const& known0, int ops, Int carryX, Int2 Xv, Int carryY, NumSet const&Y) HOT;
void combine(NumSet& L, Known& known, Known const& known0, int ops, Int carryX, Int2 Xv, Int carryY, NumSet const&Y) {
for (Int Yv: Y) {
// Yv == 0 can never lead to an optimal calculation
if (Yv == 0) continue;

Int2 accu;

if (do_multiply) {
    accu = Xv * Yv;
    if (accu <= MAX && accu >= MIN)
    known.maybe_store(known0, L, accu, ops, '*', carryX, Xv, carryY, Yv);
}

if (do_add) {
    accu = Xv + Yv;
    if (accu <= MAX && accu >= MIN)
    known.maybe_store(known0, L, accu, ops, '+', carryX, Xv, carryY, Yv);
}

if (do_subtract) {
    accu = Xv - Yv;
    if (accu <= MAX && accu >= MIN)
    known.maybe_store(known0, L, accu, ops, '-', carryX, Xv, carryY, Yv);
}

if (do_divide) {
    accu = Xv / Yv;
    if (accu <= MAX && accu >= MIN)
    known.maybe_store(known0, L, accu, ops, '/', carryX, Xv, carryY, Yv);
}
}
}

// value was constructed using a carry if and only if value != 0
NumSet const& level(KnowHow& known_how0, Int value, int ops) HOT;
NumSet const& level(KnowHow& known_how0, Int value, int ops) {
auto& from_value = known_how[value];
if (from_value.num_sets.size() <= static_cast<size_t>(ops)) {
auto& known = from_value.known;
if (from_value.num_sets.size() != static_cast<size_t>(ops)) {
    if (value == 0 || ops != 1)
    throw(logic_error("Unexpected level skip"));
    // This was because of delayed carry creation.
    // The delay is over. Create the base case
    from_value.num_sets.resize(ops+1);
    known.store(from_value.num_sets[0], value, 0, ':', value);
} else
    from_value.num_sets.resize(ops+1);
auto& L = from_value.num_sets[ops];
if (ops == 0) {
    if (value) {
    known.store(L, value, ops, ':', value);
    } else {
    for (auto i = ATOM_MIN; i <= ATOM_MAX; ++i) {
        if (single) {
        if (i == N) --missing;
        } else {
        if (0 < i && i <= N) --missing;
        }
        known.store(L, i, 0, LITERAL, i);
    }
    }
} else {
    auto& known0 = known_how0.known;
    // for (auto k=ops-1; k>=0; --k) {
    for (auto k=0; k<ops; ++k) {
    auto const& X = from_value.num_sets[ops-1-k];
    auto const& Y = known_how0.num_sets[k];

    for (Int Xv: X) {
        // Plain combine must come before carry combine so a plain
        // solution will prune a same length carry solution
        combine(L, known, known0, ops, value, Xv, 0, Y);
        if (!missing && early_exit) goto DONE;
        if (do_dup && (Xv > ATOM_MAX || Xv < ATOM_MIN)) {
        // Dup Xv, construct something using k operators, combine
        if (k == 0 && Xv != 0) {
            // Delay creation of carry known_how[Xv] for 1 level
            // This is purely a memory and speed optimization

            // Subtraction gives 0 which is never optimal
            // Division    gives 1 which is never optimal

            // Multiplication gives Xv ** 2
            // Could be == Xv if Xv== 0 or Xv == 1, but will be
            // pruned by atom - atom or atom / atom
            Int2 accu = Xv;
            accu *= accu;
            if (accu <= MAX && accu >= MIN) {
            known.maybe_store(known0, L, accu, ops, '*',
                      value, Xv, Xv, Xv);
            }

            // Addition gives Xv * 2 (!= Xv)
            if (HALF_MIN <= Xv && Xv <= HALF_MAX)
            known.maybe_store(known0, L, 2*Xv, ops, '+',
                      value, Xv, Xv, Xv);
        } else {
            auto& Z = level(known_how0, Xv, k);
            combine(L, known, known0, ops, value, Xv, Xv, Z);
        }
        if (!missing && early_exit) goto DONE;
        }
        if (max_mem != SIZE_MAX && cached > cached_next) {
        cached_next = cached + MEM_CHECK;
        if (get_memory() >= max_mem) goto DONE;
        }
    }
    }
}
// L.shrink_to_fit();
}
  DONE:
return from_value.num_sets[ops];
}

void my_main(int argc, char const* const* argv) {
GetOpt options("acfm:sSEOo:b:B:r:R:", argv);
while (options.next())
switch (options.option()) {
    case 'a': all    = true;  break;
    case 'b': {
    auto tmp = atoll(options.arg());
    ATOM_MIN = static_cast<Int>(tmp);
    if (static_cast<long long int>(ATOM_MIN) != tmp)
        throw(range_error("ATOM_MIN is out of range"));
    break;
    }
    case 'B': {
    auto tmp = atoll(options.arg());
    ATOM_MAX = static_cast<Int>(tmp);
    if (static_cast<long long int>(ATOM_MAX) != tmp)
        throw(range_error("ATOM_MAX is out of range"));
    break;
    }
    case 'c': all_carry  = true;  break;
    case 'f': find_hole  = true;  break;
    case 'm': max_mem = atoll(options.arg()); break;
    case 'S': explore    = true;  // intended drop through to single
    case 's': single     = true;  break;
    case 'o': operators  = options.arg(); break;
    case 'E': early_exit = false; break;
    case 'r': {
    auto tmp = atoll(options.arg());
    MIN = static_cast<Int>(tmp);
    if (static_cast<long long int>(MIN) != tmp)
        throw(range_error("MIN is out of range"));
    break;
    }
    case 'R': {
    auto tmp = atoll(options.arg());
    MAX = static_cast<Int>(tmp);
    if (static_cast<long long int>(MAX) != tmp)
        throw(range_error("MAX is out of range"));
    break;
    }
    case 'O': output     = false; break;
    default:
      cerr << "usage: " << argv[0] << " [-a] [-c] [-f] [-D] [-E] [-O] [-s] [-b atom_min] [-B atom_max] [r range_min] [-R range_max] [-m max_mem] [max]" << endl;
      exit(EXIT_FAILURE);
}

// Avoid silly option combinations
if (MIN > MAX) throw(logic_error("MIN above MAX"));
if (ATOM_MIN > ATOM_MAX) throw(logic_error("ATOM_MIN above ATOM_MAX"));
if (ATOM_MIN < 0)  throw(range_error("Cannot represent negative atoms"));
if (ATOM_MAX > 35) throw(range_error("Cannot represent atoms > 35"));
if (ATOM_MIN < MIN) throw(range_error("ATOM_MIN is out of range"));
if (ATOM_MAX > MAX) throw(range_error("ATOM_MAX is out of range"));

HALF_MIN = MIN / 2;
HALF_MAX = MAX / 2;

for (auto ops=operators; *ops; ++ops)
switch(*ops) {
    case '*': do_multiply = true; break;
    case '/': do_divide   = true; break;
    case '+': do_add      = true; break;
    case '-': do_subtract = true; break;
    case ':': do_dup      = true; break;
    default:
      throw(logic_error("Unknown operator"));
}
long long int const NN =
options.ind() < argc ? atoll(argv[options.ind()]) : 1;
if (NN < MIN || NN > MAX)
throw(range_error("Target number is out of range"));
N = NN;
if (N < 1) {
single = true;
output = false;
}
cerr << "N=" << N << ", using " << sizeof(Int) * CHAR_BIT << " bits without overflow" << endl;

missing = single ? 1 : N;
cached = cached_next = 0;
auto& known_how0 = known_how[0];
auto& known = known_how0.known;
auto mem = get_memory(true);
if (!mem && max_mem != SIZE_MAX)
throw(runtime_error("Cannot get memory usage on this system"));

// Start calculation
start = chrono::steady_clock::now();

// Fill in initial values [0..9]
level(known_how0, 0, 0);

// Grow number of allowed operations until all requested numbers are reached
// for (auto ops=1; ops <=5; ++ops) {
for (auto ops=1;;++ops) {
if (missing == 0) {
    if (!explore) break;
    known_how0.known.do_explore();
    if (missing == 0) break;
}
if (max_mem != SIZE_MAX && get_memory() >= max_mem) break;
auto end = chrono::steady_clock::now();
auto elapsed = chrono::duration_cast<ms>(end-start).count();
cerr << "Reaching for " << 2*ops+1 << " instructions at " << elapsed/1000. << " s";
if (mem) cerr << " (" << get_memory() / 1000 / 1000.  << " MB)";
cerr << endl;

auto old_cached = cached;
level(known_how0, 0, ops);
if (cached == old_cached) {
    cerr << "Oops, all possible numbers have been generated and we still weren't finished"  << endl;
    break;
}
}

// We are done generating all numbers.
auto end = chrono::steady_clock::now();

// Report the result
// length = 2*ops + 1
Int limit = known_how0.num_sets.size()*2-1;
cerr << "Some numbers needed " << limit << " instructions" << endl;

auto elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
stringstream out;
out << "Calculation: " << elapsed/1000.  << " s\n";
for (auto i = output ? 1 : N; i <= N; ++i) {
if (single || missing) {
    auto got = known.find(i);
    if (got != known.end())
    cout << i << ": " << got->second << "\n";
    else
    cout << i << " not generated\n";
} else
    cout << i << ": " << known.at(i) << "\n";
}
if (output) {
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "Printing:    " << elapsed/1000. << " s\n";
}

if (find_hole) {
Int hole;
for (auto i = single ? 1 : N+1; 1; ++i) {
    if (!known_how0.known.count(i) || i == 0) {
    hole = i;
    break;
    }
}
out << "First missing value " << hole << "\n";
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "Missing:     " << elapsed/1000. << " s\n";
}

if (all) {
for (auto const& entry: known_how0.known) {
    cout << entry.first << ": " << entry.second << "\n";
}
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "All:         " << elapsed/1000. << " s\n";
}

if (all_carry) {
for (auto const& carry: known_how) {
    auto carry_left = carry.first;
    if (carry_left == 0) continue;
    cout << "Carry " << carry_left << "\n";
    for (auto const& how: carry.second.known) {
    cout << "    " << how.first << ": ";
    how.second.print(cout, carry_left, true);
    cout << "\n";
    }
}
end = chrono::steady_clock::now();
elapsed = chrono::duration_cast<ms>(end-start).count();
start = end;
out << "All carry:   " << elapsed/1000. << " s\n";
}

mem = get_memory();
if (mem) cerr << "used about " << mem / 1000 / 1000.  << " MB\n";

cerr << out.str();
cerr << "Cached " << cached << " results = " << known.size() << " plain + " << cached - known.size() << " carry" << endl;
}

int main(int argc, char const* const* argv) {
try {
my_main(argc, argv);
} catch(exception& e) {
cerr << "Error: " << e.what() << endl;
quick_exit(EXIT_FAILURE);
}
// Cleaning up the datastructures can take ages
quick_exit(EXIT_SUCCESS);
}

Alcuni casi di test:

  • 1: 1: 1
  • 11: 3: 29+
  • 26: 5: 29*8+
  • 27: 3: 39*
  • 100: 5: 19+:*
  • 2431: 9: 56*9*9*1+
  • 3727: 9: 69*7+:*6+
  • 86387: 11: 67*:*1-7*7*
  • 265729: 11: 39*:*:*2/9+
  • 265620: 13: 99*::*6/*7+3*
  • 1921600: 9: 77*:*:*3/
  • 21523360: 9: 99*:*:*2/
  • 57168721: 11: 99*6+:*8-:*
  • 30932: 11: 159*-:4*:*+

Bel lavoro, questo è incredibilmente veloce data la difficoltà del problema! Un po 'di problemi però: per il 38950002tuo programma dà 89*7+:::**1-*, il che è abbastanza buono, ma puoi farlo 299*-::*:*+per brevi. Penso che ciò confermi i dubbi che avevo sui numeri negativi ...
Sp3000,

@ Sp3000: Bummer, avevo considerato solo numeri positivi. Non è difficile estendere il programma per gestire anche i numeri negativi, ma mi aspetto che ci vorrà un duro ricordo e un colpo di velocità
Ton Hospel,

@ Sp3000 Aggiornato per i temporali negativi. Il raggio raggiungibile è effettivamente diminuito un po '
Ton Hospel,

int main(int argc, char const* const* argv)Non conosco C meglio del Joe medio, ma cos'è questo? un puntatore const a un puntatore const a un carattere? Non dovrebbe essere char const *argv[]così (o char const **argvse sei quel hardcore)?
gatto

@cat È un puntatore a (una matrice di) puntatori costanti a (una matrice di) carattere costante. Per rendere costante il puntatore di livello superiore dovrei aggiungere anche un'altra const direttamente di fronte ad argv (che funzionerebbe dal momento che non cambio neanche argv). Fondamentalmente prometto di non cambiare gli argomenti o i puntatori agli argomenti.
Ton Hospel,

2

Forza bruta del nodo JavaScript

File di programma bfCodes.js

function bfCodes( n)
{   var odo = [0], valid = true, valCount=1;

    const vDUP = 10, vADD = 11, vSUB = 12, vMUL=13, vDIV = 14, vMAX = vDIV;
    const vCHARS = "0123456789:+-*/";

    function inc(sd) // increment significant digit, lsd = 0
    {   if(sd >= odo.length) { odo.push(0); console.log("length: " + (sd+1)); ++valCount; return;}
        var v = ++odo[sd]; // increment and read the base 15 odometer digit
        if( v == vDUP)
            if( valCount) {++valCount; return}
            else { odo[ sd] = vMAX; --valCount; valid = false; return;}

        if( v == vADD)
        {    if( (--valCount) < 1) { valid = false; odo[ sd] = vMAX; return;};
        }
        if( v > vMAX) { odo[sd] = 0; ++valCount; valid = true; inc(sd+1); return;}
    }

    function bfDecode( odo)
    {   var a,b,stack = [];
        for(var i = odo.length; i--;)
        {   var v = odo[ i];
            if( v < 10) { stack.push( v); continue;};
            switch(v) {
            case vDUP: stack.push( stack[stack.length-1]); continue;
            case vADD: b=stack.pop(); stack.push( stack.pop()+b); continue;
            case vMUL: b=stack.pop(); stack.push(stack.pop()*b); continue;
            case vDIV: b=stack.pop(); if(!b) return undefined; a = stack.pop(); 
                stack.push( (a < 0 ? b < 0 : b > 0) ? (a/b)>>0 : -(-a/b >>0)); continue;
            }
        }
        return stack[0];
    }
    var codes = [], value;
    for( var got = 0; got < n;)
    {   inc(0);
        if(!valid) continue;
        if(!(value = bfDecode( odo))) continue;
        if( value <= 0 || value > n || codes[ value]) continue;
        ++got;
        for(var i = odo.length, s=""; i--;)  s+=vCHARS[ odo[i]];
        codes[ value] = s;
    }
    return codes;
}

function main( args) // node, script, number
{   n = parseInt( args[2]);
    if(isNaN(n)){ console.log("\nTry:  node bfCodes number\nfor script saved as bfCodes.js"); return;}
    console.log("\ngenerating befunge code for numbers up to " + n);
    var start = Date.now();
    var codes = bfCodes(n);
    var end = Date.now();
    console.log("befunge codes:");
    for( var i = 1; i <=n; ++i) console.log( i + ": " + codes[i]);
    console.log(end-start + " msec");
}
main( process.argv);

In esecuzione su Windows

  1. Scarica e installa Nodejs , un'implementazione autonoma del motore JavaScript di Chromes V8.
  2. Salvare il file di programma sopra in una directory di lavoro utilizzando il nome file "bfCodes.js" (i nomi dei file di Windows non fanno distinzione tra maiuscole e minuscole).
  3. Fare clic con il tasto destro nella directory di lavoro e creare un collegamento al programma della shell dei comandi (casella DOS per vecchi) con destinazione cmd.exe
  4. Modifica le proprietà del collegamento e imposta la cartella di lavoro sul nome della directory di lavoro (fai clic sulla barra degli indirizzi e copia).
  5. Aprire cmd.exeutilizzando il collegamento e verificare che il prompt di DOS inizi con la directory di lavoro
  6. Immettere "nodo bfCodes" senza virgolette e immettere - il nodo in esecuzione la prima volta può richiedere più tempo rispetto all'esecuzione di nuovo.
  7. Immettere "node bfCodes 16" per mostrare i codici fino a 16. Non utilizzare un numero elevato!

Ottimizzazione

L'algoritmo scorre ciclicamente tutte le combinazioni di caratteri befunge a partire da una stringa di codice di lunghezza 1. Pensa a come far girare un contachilometri di 15 base dalla cifra meno significativa. Cifre di ordine superiore cliccano con crescente lentezza.bfCodesnon valuta il codice generato che renderebbe la lunghezza dello stack zero o negativa o lascerebbe più di un numero nello stack nel tentativo di ottimizzare la velocità di esecuzione.

Il problema della forza bruta

Per un set di codici di 15 caratteri, viene dato il tempo necessario per scorrere tutte le combinazioni di una determinata lunghezza

T len = 15 * T len-1

vale a dire che se il tuo programma viene eseguito quindici volte più veloce del mio, sarai in grado di controllare solo una stringa aggiuntiva di codice carattere contemporaneamente. Per controllare altri due caratteri contemporaneamente, un programma dovrebbe essere eseguito 225 volte più velocemente. Il tempo impiegato con un approccio a forza bruta aumenta esponenzialmente all'aumentare della lunghezza delle stringhe di codice. E la grandezza di un numero indica necessariamente il numero di byte befunge necessari per generarlo.

Alcune cifre.

Tempi approssimativi per generare un elenco di codici su un blocco note di Windows 7 a 32 bit per numeri interi fino a

  • 9: 1 msec
  • 10: 16 msec
  • 32: 156 msec
  • 81: 312 msec
  • 93: 18,5 secondi
  • 132: 28 secondi

Per generare befunge per 3727 (che è 66 quadrati più 6) da solo ci sono voluti 1 ora 47 minuti e generato 578*+:*6+

Generazione di codice ottimale

Generare befunge per i numeri senza verificare le lunghezze più brevi è relativamente semplice. Utilizzando un algoritmo ricorsivo che utilizzava radici e residui quadrati interi, la codifica per numeri fino a 132 ha richiesto circa 3 msec anziché 28 secondi. Non erano ottimali. A causa del modo in cui ha funzionato questo particolare algoritmo prodotto 638:*-:*+per 3727 in circa 1 msec (anziché un'ora circa) che risultava essere ottimale.

Il problema con la fornitura di un metodo di forza non bruta sta dimostrando che è ottimale in ogni caso. In bocca al lupo!


Dovresti essere in grado di abbassare di molto l'esponente osservando che la tua stringa deve rappresentare un albero di valutazione valido con +-*/ai nodi interni 0-9e :alle foglie (e :non può essere più a sinistra). Quindi genera e valuta tutto l'albero valido di dimensione 2 * n + 1 al passaggio n (n inizia da 0) e convertili in stringhe quando necessario
Ton Hospel,

3727 è 61 al quadrato più 6, non 66 :)
Tim Vermeulen,

1

JavaScript

Vuoi fare uno snippet JS? Nella mia macchina, Firefox a 64 bit, 416 in 60 secondi

function go() {
    B.disabled=true
    O.textContent = '...wait...'
    setTimeout(run, 100)
}

function run()
{
	var o=[0],	
	t0=performance.now(),	
	te=t0+T.value*1000,
	k=[],t=[...'0123456789'],i=0,n=0,e,v,j,l,x,h
	MainLoop:
	for(;;)
	{
	  for(;!k[n] && (e=t[i++]);) 
	  {
	    if(performance.now()>te)break MainLoop
	    
	    for(v=[],j=0;x=e[j++];l=x)
	      1/x?h=v.push(+x):(b=v.pop(),x>'9'?h=v.push(b,b):(a=v.pop(),h=v.push(x<'+'?a*b:x<'-'?a+b:x<'/'?a-b:a/b|0)))
	    if(!k[v])
	    {
	      k[v]=e
	      //if(!e[10])
	      {
	        if (l==':')
	          t.push(e+'+',e+'*')
	        else if (h>1)
	        {
	          if (l == '1') t.push(e+'+',e+'-')
	          else if (l != '0') t.push(e+'+',e+'-',e+'*',e+'/')
	        }  
	        if (h<4)
	        {
	          if (l<'0'|l>'9') t.push(e+':');
	          [...'0123456789'].forEach(x => t.push(e+x))
	        }
	      }  
	    }
	  }
	  o.push([n,k[n]])
    ++n;
	}  
	o[0]='Run time sec '+(performance.now()-t0)/1000+'\nTried '+t.length+'\nRange 0..'+(n-1)+'\nTop '+k.pop()+' '+k.length
	O.textContent=o.join`\n`
    B.disabled=false
}
Time limit sec:<input id=T type=number value=60><button id=B onclick='go()'>GO</button>
<pre id=O></pre>

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.