Exprese rápidamente un número con solo 0-9 y las cuatro operaciones, más uno más

14

Explicación

Befunge es un programa bidimensional que utiliza pilas .

Eso significa que, para hacer 5 + 6, escribes 56+, lo que significa:

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)

Sin embargo, no podemos insertar el número 56directamente en la pila.

Para ello, debemos escribir 78*en su lugar, lo que multiplica 7y 8y empuja el producto en la pila.

Detalles

Para cada número de 1an , encontrar una cadena formada únicamente por los caracteres: 0123456789+-*/:(yo no uso %de módulo).

El objetivo es encontrar la cadena más corta que pueda representar el número, utilizando el formato descrito anteriormente.

Por ejemplo, si la entrada es 123, entonces la salida sería 67*9:*+. La salida debe evaluarse de izquierda a derecha.

Si hay más de una salida aceptable (por ejemplo, 99*67*+también es aceptable), se puede imprimir cualquiera (no hay bonificación por imprimirlas todas).

Explicación adicional

Si aún no comprende cómo se 67*9:*+evalúa 123, aquí hay una explicación detallada.

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

El programa necesita encontrar la cadena más corta que pueda representar la entrada (número), utilizando el formato especificado anteriormente.

PUNTUACIÓN

  • Ya lo hemos hecho en la menor cantidad de código . Esta vez, el tamaño no importa.
  • Su idioma de elección debe tener un compilador / intérprete gratuito para mi sistema operativo (Windows 7 Enterprise).
  • Bonificación si incluye el enlace al compilador / intérprete (soy demasiado vago).
  • Si es posible, incluya un temporizador para mi conveniencia. La salida del temporizador es válida.
  • El puntaje será el más grande nen 1 minuto.
  • Eso significa que el programa necesita imprimir la representación requerida de 1adelante en adelante.
  • Sin codificación rígida, excepto 0para 9.

(más) ESPECIFICOS

  • El programa no es válido si genera una cadena más larga que la necesaria para cualquier número.
  • 1/0=ERROR
  • 5/2=2, (-5)/2=-2, (-5)/(-2)=2,5/(-2)=-2

Desambiguación

El -es second-top minus top, lo que significa que 92-vuelve 7.

Del mismo modo, el /es second-top divide top, lo que significa que 92/vuelve 4.

Programa de muestra

Lua

Utiliza la búsqueda de profundidad primero.

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
Monja permeable
fuente
Espera, si no podemos empujar 56directamente a la pila, ¿cómo podemos empujar 78a la pila?
R. Kap
No podemos empujar 56cincuenta y seis directamente en la pila, pero podemos empujar 7siete y 8ocho por separado en la pila.
Leaky Nun
1
@ R.Kap: Cuando haces algo como 56en Befunge, estás presionando los dígitos , por lo que terminas con una pila de [5, 6]. Para obtener el número 56, debes empujar 7, luego 8a la pila, y luego multiplicarlos para obtener el número 56 en la pila.
El'endia Starman
1
:hace las cosas mucho más complicadas, por lo que recomendaría proporcionar una buena lista de casos de prueba, por ejemplo86387
Sp3000
1
El entero más grande en 5 segundos es una mala métrica. El tiempo de cálculo para números más grandes no aumentará monotónicamente, por lo que muchas soluciones pueden quedar atrapadas en el mismo número difícil de calcular, a pesar de que algunas de ellas son mucho más rápidas o más lentas en números cercanos.
Sparr

Respuestas:

7

C ++, explotando toda la memoria en una computadora cerca de usted

Genera la cadena más corta donde el cálculo en ninguna parte causa un desbordamiento de un entero de 32 bits con signo (por lo que todos los resultados intermedios están en el rango [-2147483648, 2147483647]

En mi sistema, esto genera una solución para todos los números hasta 48343230 segundos inclusive, utilizando una memoria de 1.8G. Incluso números más altos explotarán rápidamente el uso de la memoria. El número más alto que puedo manejar en mi sistema es 5113906. El cálculo lleva casi 9 minutos y 24 GB. Cuando termina internamente, tiene una solución para los 398499338valores, aproximadamente el 9% de todos los enteros de 32 bits (positivo y negativo)

Necesita un compilador de C ++ 11. En Linux compila con:

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

Agregue -DINT64como una opción para usar un rango entero de 64 bits en lugar de 32 bits para obtener resultados intermedios (esto usará aproximadamente un 50% más de tiempo y memoria). Esto necesita un tipo incorporado de 128 bits. Es posible que deba cambiar el tipo de gcc __int128. Ningún resultado en al menos el rango [1..483432]cambia al permitir resultados intermedios más grandes.

Añadir -DOVERFLOW como una opción para no usar un tipo entero más grande para verificar el desbordamiento. Esto tiene el efecto de permitir el desbordamiento y el ajuste del valor.

Si su sistema tiene tcmalloc ( https://github.com/gperftools/gperftools ) puede vincularlo con un programa que generalmente es un poco más rápido y usa un poco menos de memoria. En algunos sistemas UNIX puede usar una precarga, p. Ej.

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

Uso básico: generar e imprimir todos los números hasta el objetivo:

befour target

Opciones:

  • -a Imprima también todos los números que se generaron al calcular el objetivo
  • -c Imprima también todos los números que se generaron comenzando con un "carry" (dup)
  • -f Encuentre e imprima el primer número más allá del objetivo que no se generó
  • -s Deténgase si se genera el objetivo, incluso si no se generaron todos los números anteriores
  • -SMe gusta -sy -fen un bucle automático. Tan pronto como se genere el objetivo, encuentre el primer número que aún no se ha generado y haga que el nuevo objetivo
  • -ENo salgas de inmediato cuando alcances la meta. Primero termine todas las cadenas de la longitud actual
  • -ONo envíe las cadenas para todos los números hasta el objetivo. solo la cadena para el objetivo
  • -o Instrucciones permitidas (el valor predeterminado es +-*/:
  • -b numLiteral más bajo que se puede empujar (el valor predeterminado es 0)
  • -B numLiteral más alto que se puede empujar (el valor predeterminado es 9)
  • -r numEl resultado intermedio más bajo permitido. Se usa para evitar el desbordamiento. (por defecto INT32_MIN,-2147483648
  • -R numEl resultado intermedio más alto permitido. Se usa para evitar el desbordamiento. (por defecto INT32_MAX,2147483647
  • -m memory (solo Linux) sale cuando se ha asignado aproximadamente esta cantidad de memoria adicional

Algunas combinaciones de opciones interesantes:

Genere todos los números hasta el objetivo y calcule el número más pequeño que necesita un generador más largo que todos estos números:

befour -fE target

Generar solo objetivo (-s), imprimir solo objetivo (-O)

befour -sO target

Encuentre el número más alto que se puede generar en su sistema dadas las limitaciones de tiempo y / o memoria (esto hará que su sistema se quede sin memoria si lo deja en funcionamiento. Reste 1 de la última salida "en busca" que ve como el último valor seguro ):

befour -S 1

Genere soluciones sin usar resultados intermedios negativos ( 30932es el primer valor que necesita resultados intermedios negativos para la cadena más corta):

befour -r0 target

Genere soluciones sin presionar nunca 0(esto no parece conducir a soluciones subóptimas):

befour -b1 target

Generar soluciones que incluyen a..f (10..15):

befour -B15 target

Genere soluciones sin usar dup :(agregue -r0ya que los valores intermedios negativos nunca son interesantes para este caso)

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

Encuentra el primer valor que no puede ser generada por una longitud de cadena dada usando simplemente +, -, *y /:

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

De hecho, esto generará los primeros términos de https://oeis.org/A181898 , pero comenzará a divergir 14771porque usamos la división truncada para que el número se pueda hacer con una cadena de longitud 13 en lugar de la longitud 15 como la serie OEIS espera:

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

en lugar de

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

Dado que sin la división de truncamiento parece inútil, la serie OEIS puede generarse mejor utilizando

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

Suponiendo que la división sigue siendo inútil, esto me dio 3 términos adicionales antes de que me quedara sin memoria:

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

Otra versión de este programa que almacena parte de los datos en archivos externos agrega 135153107 y 675854293, después de lo cual se han generado todos los enteros de 32 bits.

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

Algunos casos de prueba:

  • 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*:*+
Ton Hospel
fuente
Buen trabajo, ¡esto es impresionantemente rápido dada la dificultad del problema! Sin embargo, un poco de problemas: para 38950002su programa da 89*7+:::**1-*, que es bastante bueno, pero puede hacerlo 299*-::*:*+por más corto. Creo que esto confirma las dudas que tenía sobre los números negativos ...
Sp3000
@ Sp3000: Bummer, solo había considerado números positivos. No es difícil extender el programa para que también maneje números negativos, pero espero que tenga un severo golpe de memoria y velocidad
Ton Hospel
@ Sp3000 Actualizado para temporarios negativos. El alcance alcanzable se redujo bastante
Ton Hospel el
int main(int argc, char const* const* argv)No sé C mejor que el Joe promedio, pero ¿qué es esto? un puntero constante a un puntero constante a un char? ¿No debería ser más char const *argv[]o menos (o char const **argvsi eres tan duro)?
gato
@cat Es un puntero a (un conjunto de) punteros constantes a (un conjunto de) caracteres constantes. Para que el puntero de nivel superior sea constante, también tendría que agregar otra constante directamente frente a argv (lo que funcionaría ya que tampoco cambio argv). Básicamente prometo no cambiar los argumentos o los punteros a los argumentos.
Ton Hospel
2

Nodo JavaScript Fuerza Bruta

Archivo de programa 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);

Ejecutando bajo Windows

  1. Descargue e instale Nodejs , una implementación independiente del motor de JavaScript Chromes V8.
  2. Guarde el archivo de programa anterior en un directorio de trabajo con el nombre de archivo "bfCodes.js" (los nombres de archivo de Windows no distinguen entre mayúsculas y minúsculas).
  3. Haga clic derecho en el directorio de trabajo y cree un acceso directo al programa de shell de comandos (cuadro DOS para oldies) con destino cmd.exe
  4. Edite las propiedades del acceso directo y configure la carpeta de trabajo con el nombre de su directorio de trabajo (haga clic en la barra de ubicación y copie).
  5. Abra cmd.exeusando el acceso directo y verifique que el indicador de DOS comience con el directorio de trabajo
  6. Ingrese "node bfCodes" sin comillas y entre - ejecutar el nodo la primera vez puede tomar más tiempo que ejecutarlo nuevamente.
  7. Ingrese "node bfCodes 16" para mostrar códigos hasta 16. ¡No use un número grande!

Mejoramiento

El algoritmo recorre todas las combinaciones de caracteres iniciales comenzando con una cadena de código de longitud 1. Piense que gira un odómetro de base 15 desde el dígito menos significativo. Los dígitos de orden superior hacen clic con mayor lentitud.bfCodesno evalúa el código generado que haría que la longitud de la pila fuera cero o negativa o dejara más de un número en la pila en un intento de optimizar la velocidad de ejecución.

El problema de la fuerza bruta

Para un conjunto de códigos de 15 caracteres, el tiempo que lleva ejecutar todas las combinaciones de una longitud determinada es dado por

T len = 15 * T len-1

es decir, si su programa se ejecuta quince veces más rápido que el mío, solo podrá verificar una cadena de código de caracteres adicional al mismo tiempo. Para verificar dos caracteres más al mismo tiempo, un programa necesitaría ejecutarse 225 veces más rápido. El tiempo empleado con un enfoque de fuerza bruta aumenta exponencialmente a medida que aumenta la longitud de las cadenas de código. Y la magnitud de un número indica necesariamente el número de bytes de inicio necesarios para generarlo.

Algunas figuras.

Tiempos aproximados para generar una lista de códigos en un bloc de notas de Windows 7 de 32 bits para enteros de hasta

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

Generar befunge para 3727 (que es 66 al cuadrado más 6) por sí solo tomó 1 hora 47 minutos y generó 578*+:*6+

Generación óptima de código

Generar befunge para números sin verificar longitudes más cortas es relativamente simple. Usando un algoritmo recursivo que usaba raíces cuadradas enteras y restos, las codificaciones para números de hasta 132 tomaron aproximadamente 3 ms en lugar de 28 segundos. No fueron óptimos. Debido a la forma en que funcionó, este algoritmo en particular produjo 638:*-:*+3727 en aproximadamente 1 ms (en lugar de una hora más o menos) que resultó ser óptimo.

El problema de proporcionar un método de fuerza no bruta es demostrar que es óptimo en todos los casos. ¡Buena suerte!

traktor53
fuente
Usted debe ser capaz de reducir el exponente por una gran cantidad mediante la observación de que la cadena debe representar un árbol de evaluación válida +-*/en los nodos interiores y 0-9y :en las hojas (y :no se puede estar más a la izquierda). Por lo tanto, genere y evalúe todos los árboles válidos de tamaño 2 * n + 1 en el paso n (n comienza desde 0) y
conviértalos en
3727 es 61 al cuadrado más 6, no 66 :)
Tim Vermeulen
1

JavaScript

¿Qué se puede hacer con un fragmento de JS? En mi máquina, Firefox 64 bit, 416 en 60 segundos

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>

edc65
fuente