Drücken Sie schnell eine Zahl mit nur 0-9 und den vier Operationen plus einer zusätzlichen aus


14

Erläuterung

Befunge ist ein zweidimensionales Programm, das Stapel verwendet .

Das heißt, um 5 + 6 zu tun, schreiben Sie 56+, was bedeutet:

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)

Wir können die Nummer jedoch nicht pushen 56 direkt in den Stapel .

Dazu müssen wir 78*stattdessen schreiben , was das Produkt multipliziert 7und 8in den Stapel schiebt.

Einzelheiten

Für jede Zahl aus 1zun , eine Zeichenfolge , die nur aus diesen Zeichen finden: 0123456789+-*/:(Ich würde nicht verwenden %Modulo.)

Das Ziel besteht darin, die kürzeste Zeichenfolge zu finden, die die Zahl darstellen kann, indem das oben beschriebene Format verwendet wird.

Wenn zum Beispiel die Eingabe ist 123, dann wäre die Ausgabe 67*9:*+. Die Ausgabe sollte von links nach rechts ausgewertet werden.

Wenn es mehr als eine akzeptable Ausgabe gibt (z. B. 99*67*+auch akzeptabel), kann eine beliebige gedruckt werden (kein Bonus für das Drucken aller).

Weitere Erklärung

Wenn Sie immer noch nicht verstehen, wie Sie 67*9:*+auswerten sollen 123, finden Sie hier eine ausführliche Erklärung.

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

Das Programm muss die kürzeste Zeichenfolge finden, die die Eingabe (Zahl) darstellen kann, wobei das oben angegebene Format verwendet wird.

WERTUNG

  • Wir haben es bereits in kürzester Zeit geschafft . Diesmal spielt die Größe keine Rolle.
  • Für die Sprache Ihrer Wahl muss ein kostenloser Compiler / Interpreter für mein Betriebssystem (Windows 7 Enterprise) verfügbar sein.
  • Bonus, wenn Sie den Link zum Compiler / Interpreter angeben (ich bin zu faul).
  • Wenn möglich, fügen Sie bitte einen Timer hinzu. Die Ausgabe des Timers ist gültig.
  • Die Punktzahl ist die größte nin 1 Minute.
  • Das heißt, das Programm muss die gewünschte Darstellung ausdrucken 1 .
  • Keine Hardcodierung, außer 0zu 9.

(mehr) SPEZIFIKATIONEN

  • Das Programm ist ungültig, wenn es einen String ausgibt, der länger ist als für eine beliebige Anzahl benötigt.
  • 1/0=ERROR
  • 5/2=2, (-5)/2=-2, (-5)/(-2)=2,5/(-2)=-2

Begriffsklärung

Das -ist second-top minus top, was bedeutet, dass es 92-zurückkehrt 7.

Auch das /ist second-top divide top, was bedeutet , dass die 92/Renditen 4.

Beispielprogramm

Lua

Verwendet die Tiefensuche.

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

Warten Sie, wenn wir nicht 56direkt in den Stapel hineinschieben können, wie können wir dann 78in den Stapel hineinschieben?
R. Kap

Wir können nicht 56sechsundfünfzig direkt in den Stapel schieben, aber wir können 7sieben und 8acht getrennt in den Stapel schieben .
Undichte Nonne

1
@ R.Kap: Wenn du so etwas wie 56in Befunge machst, drückst du die Ziffern , so dass du am Ende einen Stapel hast [5, 6]. Um die bekommen Nummer 56, müssen Sie schieben 7, dann 8auf den Stapel, und dann multiplizieren sie die Nummer 56 auf dem Stapel zu erhalten.
El'endia Starman

1
:macht die Sache viel kniffliger, daher würde ich empfehlen, eine gute Liste von Testfällen 86387
anzugeben

1
Die größte Ganzzahl in 5 Sekunden ist eine schlechte Metrik. Die Rechenzeit für größere Zahlen steigt nicht monoton an, sodass viele Lösungen bei derselben schwer zu berechnenden Zahl hängen bleiben können, obwohl einige von ihnen bei benachbarten Zahlen viel schneller oder langsamer sind.
Sparr

Antworten:


7

In C ++ wird der gesamte Speicher eines Computers in Ihrer Nähe aufgelöst

Erzeugt die kürzeste Zeichenfolge, bei der die Berechnung nirgendwo einen Überlauf einer vorzeichenbehafteten 32-Bit-Ganzzahl verursacht (alle Zwischenergebnisse liegen also im Bereich) [-2147483648, 2147483647]

Auf meinem System generiert dies eine Lösung für alle Nummern bis einschließlich 48343230 Sekunden bei Verwendung von 1,8 G Speicher. Noch höhere Zahlen werden die Speichernutzung schnell explodieren. Die höchste Zahl, die ich auf meinem System verarbeiten kann, ist 5113906. Die Berechnung dauert fast 9 Minuten und 24 GB. Wenn es fertig ist, hat es intern eine Lösung für398499338 Werte, etwa 9% aller 32-Bit-Ganzzahlen (positiv und negativ)

Benötigt einen C ++ 11 Compiler. Unter Linux kompilieren mit:

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

Fügen Sie diese -DINT64Option hinzu, um einen 64-Bit-Integer-Bereich anstelle von 32-Bit für Zwischenergebnisse zu verwenden (dies beansprucht etwa 50% mehr Zeit und Speicher). Dies erfordert einen eingebauten 128-Bit-Typ. Möglicherweise müssen Sie den gcc-Typ ändern __int128. Kein Ergebnis, zumindest der Bereich [1..483432]ändert sich, indem größere Zwischenergebnisse zugelassen werden.

Fügen Sie -DOVERFLOWals Option hinzu, dass kein größerer ganzzahliger Typ zum Überprüfen auf Überlauf verwendet wird. Dies hat den Effekt, dass ein Überlauf und ein Werteverlauf zugelassen werden.

Wenn Ihr System über tcmalloc ( https://github.com/gperftools/gperftools ) verfügt, können Sie eine Verknüpfung mit diesem Programm herstellen, was zu einem Programm führt, das im Allgemeinen etwas schneller ist und etwas weniger Speicher benötigt. Auf einigen UNIX-Systemen können Sie eine Vorabladung verwenden, z

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

Grundsätzliche Verwendung: Generiere und drucke alle Zahlen bis zum Ziel:

befour target

Optionen:

  • -a Drucken Sie auch alle Zahlen aus, die beim Ausarbeiten des Ziels generiert wurden
  • -c Drucken Sie auch alle Zahlen aus, die mit einem "Übertrag" (dup) beginnen.
  • -f Suchen und drucken Sie die erste Zahl hinter dem Ziel, die nicht generiert wurde
  • -s Stoppen, wenn das Ziel generiert wird, auch wenn nicht alle Nummern zuvor generiert wurden
  • -SWie -sund -fin einer automatischen Schleife. Sobald das Ziel generiert wurde, suchen Sie die erste noch nicht generierte Nummer und machen Sie diese zum neuen Ziel
  • -EVerlasse das Spiel nicht sofort, wenn das Ziel erreicht ist. Beende zuerst alle Saiten der aktuellen Länge
  • -OGeben Sie die Zeichenfolgen nicht für alle Zahlen bis zum Ziel aus. Nur die Zeichenfolge für das Ziel
  • -o Zulässige Anweisungen (Standardeinstellung ist +-*/:
  • -b numNiedrigstes Literal, das verschoben werden kann (Standardeinstellung 0)
  • -B numHöchstes Literal, das verschoben werden kann (Standardeinstellung 9)
  • -r numDas niedrigste erlaubte Zwischenergebnis. Wird verwendet, um Unterlauf zu vermeiden. (Standardeinstellung ist INT32_MIN,-2147483648
  • -R numDas höchstzulässige Zwischenergebnis. Wird verwendet, um ein Überlaufen zu vermeiden. (Standardeinstellung ist INT32_MAX,2147483647
  • -m memory (nur Linux) Beenden, wenn ungefähr so ​​viel zusätzlicher Speicher zugewiesen wurde

Einige interessante Optionskombinationen:

Generieren Sie alle Zahlen bis zum Ziel und berechnen Sie die kleinste Zahl, die einen längeren Generator benötigt als alle diese Zahlen:

befour -fE target

Nur Ziel generieren (-s), nur Ziel drucken (-O)

befour -sO target

Ermitteln Sie die höchste Zahl, die in Ihrem System aufgrund von Zeit- und / oder Speicherbeschränkungen generiert werden kann. (Dies führt dazu, dass Ihr System nicht genügend Speicher hat, wenn Sie es laufen lassen. Subtrahieren Sie 1 von der letzten Ausgabe, nach der Sie gesucht haben und die Sie als letzten sicheren Wert sehen ):

befour -S 1

Generieren Sie Lösungen, ohne negative Zwischenergebnisse zu verwenden ( 30932ist der erste Wert, der negative Zwischenergebnisse für die kürzeste Zeichenfolge benötigt):

befour -r0 target

Generieren Sie Lösungen, ohne jemals Druck auszuüben 0(dies scheint nicht zu suboptimalen Lösungen zu führen):

befour -b1 target

Lösungen generieren, einschließlich a..f (10..15):

befour -B15 target

Lösungen ohne dup generieren :(addieren, -r0da negative Zwischenwerte für diesen Fall nie interessant sind)

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

Finden Sie den ersten Wert, der nicht für eine bestimmte Zeichenfolge Länge erzeugt werden kann unter Verwendung von nur +, -, *und /:

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

Dies wird in der Tat die ersten Begriffe von https://oeis.org/A181898 generieren , wird jedoch abweichend sein, 14771da wir eine abschneidende Division verwenden, so dass die Zahl mit einer Zeichenfolge der Länge 13 anstelle der Länge 15 als OEIS-Reihe erfolgen kann erwartet:

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

Anstatt von

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

Da eine Division ohne Kürzung sinnlos erscheint, kann die OEIS-Reihe besser mit erzeugt werden

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

Unter der Annahme, dass die Aufteilung unbrauchbar bleibt, gab mir dies 3 zusätzliche Begriffe, bevor ich aus dem Gedächtnis herauskam:

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

Eine andere Version dieses Programms, die einen Teil der Daten in externen Dateien speichert, fügt 135153107 und 675854293 hinzu, nach denen alle 32-Bit-Ganzzahlen generiert wurden.

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);
}

Einige Testfälle:

  • 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*:*+

Gute Arbeit, das geht angesichts der Schwierigkeit des Problems eindrucksvoll schnell! Ein bisschen Ärger allerdings: für 38950002dein Programm gibt es 89*7+:::**1-*, was ziemlich gut ist, aber du kannst es 299*-::*:*+für kürzer machen. Ich denke, dies bestätigt die Zweifel, die ich über negative Zahlen hatte ...
Sp3000

@ Sp3000: Schade, ich hatte nur positive Zahlen berücksichtigt. Es ist nicht schwer, das Programm zu erweitern, um auch negative Zahlen zu verarbeiten, aber ich gehe davon aus, dass es einen schwerwiegenden Speicher- und Geschwindigkeitsschub erfordern wird
Ton Hospel

@ Sp3000 Aktualisiert für negative temporäre Werte. Der erreichbare Bereich ist in der Tat deutlich gesunken
Ton Hospel

int main(int argc, char const* const* argv)Ich kenne C nicht besser als der durchschnittliche Joe, aber was ist das? ein const-Zeiger auf einen const-Zeiger auf ein Zeichen? Sollte es nicht char const *argv[]so sein (oder char const **argvwenn du so hardcore bist)?
Katze

@cat Es ist ein Zeiger auf (ein Array von) Konstantenzeiger auf (ein Array von) Konstantenzeichen. Um den Zeiger der obersten Ebene konstant zu machen, müsste ich noch eine weitere Konstante direkt vor argv einfügen (was funktionieren würde, da ich auch argv nicht ändere). Grundsätzlich verspreche ich, die Argumente oder die Zeiger auf die Argumente nicht zu ändern.
Ton Hospel

2

JavaScript Node Brute Force

Programmdatei 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);

Laufen unter Windows

  1. Laden und installieren Sie Nodejs , eine eigenständige Implementierung der Chromes V8-JavaScript-Engine.
  2. Speichern Sie die obige Programmdatei in einem Arbeitsverzeichnis unter dem Dateinamen "bfCodes.js" (Windows-Dateinamen berücksichtigen die Groß- und Kleinschreibung nicht).
  3. Klicken Sie mit der rechten Maustaste in das Arbeitsverzeichnis und erstellen Sie eine Verknüpfung zum Kommando-Shell-Programm (DOS-Box für Oldies) mit Ziel cmd.exe
  4. Bearbeiten Sie die Eigenschaften der Verknüpfung und stellen Sie den Arbeitsordner auf den Namen Ihres Arbeitsverzeichnisses ein (klicken Sie in die Adressleiste und kopieren Sie).
  5. Öffnen Sie cmd.exemit der Verknüpfung und überprüfen Sie, ob die DOS-Eingabeaufforderung mit dem Arbeitsverzeichnis beginnt
  6. Geben Sie "node bfCodes" ohne Anführungszeichen ein und geben Sie ein, dass node zum ersten Mal ausgeführt werden soll. Dies kann länger dauern als das erneute Ausführen.
  7. Geben Sie "node bfCodes 16" ein, um Codes bis 16 anzuzeigen. Verwenden Sie keine große Zahl!

Optimierung

Der Algorithmus durchläuft alle Kombinationen von befungenen Zeichen, beginnend mit einer Code-Zeichenfolge der Länge 1. Stellen Sie sich vor, Sie drehen einen Kilometerzähler zur Basis 15 von der niedrigstwertigen Stelle. Zahlen höherer Ordnung klickten mit zunehmender Langsamkeit umher. bfCodesDer generierte Code wird nicht ausgewertet, wenn die Stack-Länge null oder negativ wäre oder mehr als eine Zahl auf dem Stack verbleibt, um die Ausführungsgeschwindigkeit zu optimieren.

Das Brute-Force-Problem

Bei einem Codesatz von 15 Zeichen wird die Zeit, die zum Durchlaufen aller Kombinationen einer bestimmten Länge benötigt wird, durch angegeben

T len = 15 · T len-1

Das heißt, wenn Ihr Programm fünfzehnmal schneller läuft als meins, können Sie nur eine zusätzliche Zeichenkette gleichzeitig prüfen. Um zwei weitere Zeichen gleichzeitig zu prüfen, müsste ein Programm 225-mal schneller ausgeführt werden. Die Zeit, die mit einem Brute-Force-Ansatz benötigt wird, steigt exponentiell an, wenn die Länge der Code-Strings zunimmt. Und die Größe einer Zahl gibt notwendigerweise die Anzahl der zu ihrer Erzeugung erforderlichen befunge Bytes an.

Einige Zahlen.

Ungefähre Zeiten zum Generieren einer Codeliste auf einem Windows 7 32-Bit-Notizblock für ganze Zahlen bis zu

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

Das Erzeugen von Befunge für 3727 (das ist 66 Quadrat plus 6) für sich allein dauerte 1 Stunde 47 Minuten und wurde generiert 578*+:*6+

Optimale Codegenerierung

Das Generieren von Befunge für Zahlen ohne Überprüfung auf kürzeste Längen ist relativ einfach. Unter Verwendung eines rekursiven Algorithmus, der ganzzahlige Quadratwurzeln und Reste verwendete, dauerte die Codierung für Zahlen bis 132 etwa 3 ms anstelle von 28 Sekunden. Sie waren nicht optimal. Aufgrund der Funktionsweise dieses speziellen Algorithmus, der 638:*-:*+für 3727 in ungefähr 1 ms (anstelle von ungefähr einer Stunde) erzeugt wurde, geschah dies optimal.

Das Problem mit der Bereitstellung einer nicht-brute-force-Methode zeigt, dass sie in jedem Fall optimal ist. Viel Glück!


Sie sollten in der Lage sein, Ihren Exponenten um ein Vielfaches zu senken, indem Sie beobachten, dass Ihre Zeichenfolge einen gültigen Bewertungsbaum mit +-*/an den inneren Knoten 0-9und :an den Blättern darstellen muss (und :nicht ganz links sein darf). Generieren und bewerten Sie daher alle gültigen Bäume der Größe 2 * n + 1 in Schritt n (n beginnt bei 0) und konvertieren Sie sie bei Bedarf in Zeichenfolgen
Ton Hospel

3727 ist 61 Quadrat plus 6, nicht 66 :)
Tim Vermeulen

1

JavaScript

Was kann man mit einem JS-Snippet machen? In meinem Rechner Firefox 64 Bit, 416 in 60 Sekunden

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>

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.