Expresse rapidamente um número com apenas 0-9 e as quatro operações, além de mais uma


14

Explicação

O Befunge é um programa bidimensional que utiliza pilhas .

Isso significa que, para fazer 5 + 6, você escreve 56+, significando:

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)

No entanto, não podemos enviar o número 56diretamente para a pilha.

Para isso, devemos escrever 78*em vez disso, que se multiplica 7e 8e empurra o produto para a pilha.

Detalhes

Para cada número de 1atén , encontre uma sequência composta apenas por esses caracteres: 0123456789+-*/:(eu não usaria o %módulo.)

O objetivo é encontrar a string mais curta que possa representar o número, usando o formato descrito acima.

Por exemplo, se a entrada for 123, a saída seria 67*9:*+. A saída deve ser avaliada da esquerda para a direita.

Se houver mais de uma saída aceitável (por exemplo, 99*67*+também é aceitável), qualquer uma poderá ser impressa (sem bônus por imprimir todas elas).

Explicação adicional

Se você ainda não entende como 67*9:*+avalia 123, aqui está uma explicação detalhada.

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

O programa precisa encontrar a string mais curta que possa representar a entrada (número), usando o formato especificado acima.

PONTUAÇÃO

  • Já fizemos isso na menor quantidade de código . Desta vez, o tamanho não importa.
  • Seu idioma de escolha deve ter um compilador / intérprete gratuito para o meu sistema operacional (Windows 7 Enterprise).
  • Bônus se você incluir o link para o compilador / intérprete (estou com preguiça).
  • Se possível, inclua um cronômetro para minha conveniência. A saída do temporizador é válida.
  • A pontuação será a maior nem 1 minuto.
  • Isso significa que o programa precisa imprimir a representação necessária a partir de então 1.
  • Sem codificação, exceto 0para 9.

(mais) ESPECÍFICOS

  • O programa é inválido se gerar uma sequência maior que o necessário para qualquer número.
  • 1/0=ERROR
  • 5/2=2, (-5)/2=-2, (-5)/(-2)=2,5/(-2)=-2

Desambiguação

O -é second-top minus top, o que significa que 92-retorna 7.

Da mesma forma, o /é second-top divide top, o que significa que 92/retorna 4.

Programa de exemplo

Lua

Usa a pesquisa em profundidade.

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

Espere, se não podemos empurrar 56diretamente para a pilha, como podemos empurrar 78para dentro da pilha?
R. Kap

Não podemos 56inserir 56 diretamente na pilha, mas podemos inserir 7sete e 8oito separadamente na pilha.
Leaky Nun

1
@ R.Kap: Quando você faz algo parecido 56com o Befunge, está pressionando os dígitos e acaba com uma pilha de [5, 6]. Para obter o número 56, você tem que empurrar 7, em seguida, 8para a pilha e , em seguida, multiplicá-los para obter o número 56 na pilha.
El'endia Starman

1
:torna as coisas mais complicadas muito, então eu recomendo fornecer uma boa lista de casos de teste, por exemplo86387
SP3000

1
o maior número inteiro em 5 segundos é uma métrica ruim. o tempo computacional para números maiores não aumentará monotonicamente; muitas soluções podem ficar presas no mesmo número difícil de calcular, apesar de algumas serem muito mais rápidas ou lentas em números próximos.
Sparr

Respostas:


7

C ++, explodindo toda a memória em um computador perto de você

Gera a string mais curta em que o cálculo em nenhum lugar causa um estouro de um número inteiro de 32 bits assinado (portanto, todos os resultados intermediários estão no intervalo [-2147483648, 2147483647]

No meu sistema, isso gera uma solução para todos os números até 48343230 segundos, inclusive, usando a memória 1.8G. Números ainda maiores explodirão rapidamente o uso da memória. O número mais alto que eu posso manipular no meu sistema é 5113906. O cálculo leva quase 9 minutos e 24 GB. Quando termina internamente, possui uma solução para 398499338valores, cerca de 9% de todos os números inteiros de 32 bits (positivos e negativos)

Precisa de um compilador C ++ 11. No Linux, compile com:

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

Adicione -DINT64como opção usar um intervalo inteiro de 64 bits em vez de 32 bits para resultados intermediários (isso usará cerca de 50% mais tempo e memória). Isso precisa de um tipo interno de 128 bits. Pode ser necessário alterar o tipo de gcc __int128. Nenhum resultado em pelo menos o intervalo [1..483432]muda, permitindo resultados intermediários maiores.

Adicione -DOVERFLOWcomo opção não usar um tipo inteiro maior para verificar se há excesso. Isso tem o efeito de permitir estouro e quebra de valor.

Se o seu sistema tiver tcmalloc ( https://github.com/gperftools/gperftools ), você poderá vincular ao resultado, resultando em um programa geralmente mais rápido e que usa um pouco menos de memória. Em alguns sistemas UNIX, você pode usar uma pré-carga, por exemplo

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

Uso básico: gere e imprima todos os números até o destino:

befour target

Opções:

  • -a Também imprima todos os números que foram gerados durante o trabalho de destino
  • -c Imprima também todos os números que foram gerados começando com um "carry" (dup)
  • -f Encontre e imprima o primeiro número além do destino que não foi gerado
  • -s Pare se o destino for gerado, mesmo que nem todos os números anteriores tenham sido gerados
  • -SComo -se -fem um loop automático. Assim que o alvo for gerado, encontre o primeiro número ainda não gerado e faça com que o novo alvo
  • -ENão saia imediatamente quando o objetivo for alcançado. Primeiro termine todas as cordas do comprimento atual
  • -ONão produza as strings para todos os números até o destino. apenas a string para o alvo
  • -o Instruções permitidas (o padrão é +-*/:
  • -b numLiteral mais baixo que pode ser enviado (o padrão é 0)
  • -B numLiteral mais alto que pode ser enviado (o padrão é 9)
  • -r numO menor resultado intermediário permitido. Usado para evitar o fluxo insuficiente. (o padrão é INT32_MIN,-2147483648
  • -R numO resultado intermediário mais alto permitido. Usado para evitar transbordamentos. (o padrão é INT32_MAX,2147483647
  • -m memory (somente linux) sai quando aproximadamente essa quantidade de memória extra é alocada

Algumas combinações de opções interessantes:

Gere todos os números até o destino e calcule o menor número que precisa de um gerador mais longo do que todos esses números:

befour -fE target

Gere apenas destino (-s), imprima apenas destino (-O)

befour -sO target

Encontre o número mais alto que pode ser gerado no seu sistema, devido a restrições de tempo e / ou memória (isso deixará o sistema sem memória se você deixá-lo em execução. Subtraia 1 da última saída "procurando" que você vê como o último valor seguro ):

befour -S 1

Gere soluções sem nunca usar resultados intermediários negativos ( 30932é o primeiro valor que precisa de resultados intermediários negativos para a string mais curta):

befour -r0 target

Gere soluções sem pressionar 0(isso não parece levar a soluções subótimas):

befour -b1 target

Gere soluções, incluindo a..f (10..15):

befour -B15 target

Gere soluções sem usar dup :(adicione -r0como valores intermediários negativos nunca são interessantes para este caso)

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

Encontrar o primeiro valor que não pode ser gerado para um determinado comprimento da corda usando apenas +, -, *e /:

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

De fato, isso gerará os primeiros termos de https://oeis.org/A181898 , mas começará a divergir em 14771porque usamos a divisão de truncamento para que o número possa ser feito com uma corda 13 de comprimento em vez de 15 como a série OEIS espera:

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

ao invés de

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

Como sem divisão de truncamento parece inútil, a série OEIS pode ser melhor gerada usando

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

Supondo que a divisão permaneça inútil, isso me deu três termos extras antes de eu ficar sem memória:

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

Outra versão deste programa que armazena parte dos dados em arquivos externos adiciona 135153107 e 675854293, após o qual todos os números inteiros de 32 bits foram gerados.

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

Alguns casos de teste:

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

Bom trabalho, isso é impressionantemente rápido, dada a dificuldade do problema! Porém, um pouco de dificuldade: 38950002o programa dá 89*7+:::**1-*, o que é muito bom, mas você pode fazer 299*-::*:*+por um tempo menor. Eu acho que isso confirma as dúvidas que eu tinha sobre os números negativos ...
SP3000

@ Sp3000: Que chatice, eu tinha considerado apenas números positivos. Não é difícil para estender o programa para também lidar com números negativos, mas espero que ele terá uma memória e velocidade grave hit
Ton Hospel

@ Sp3000 Atualizado para temporários negativos. A faixa acessível de fato desceu um pouco
Ton Hospel

int main(int argc, char const* const* argv)Não conheço C melhor que o Joe médio, mas o que é isso? um ponteiro const para um ponteiro const para um char? Não deveria ser char const *argv[]assim (ou char const **argvse você é tão hardcore)?
gato

@cat É um ponteiro para (uma matriz de) ponteiros constantes para (uma matriz de) char constante. Para tornar o ponteiro de nível superior constante, eu também teria que adicionar outra const diretamente na frente do argv (o que funcionaria, pois também não altero o argv). Basicamente, prometo não mudar os argumentos ou os ponteiros para os argumentos.
Ton Hospel

2

Força Bruta do Nó JavaScript

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

Executando no Windows

  1. Baixe e instale o Nodejs , uma implementação autônoma do mecanismo JavaScript do Chromes V8.
  2. Salve o arquivo de programa acima em um diretório ativo usando o nome de arquivo "bfCodes.js" (os nomes de arquivos do Windows não diferenciam maiúsculas de minúsculas).
  3. Clique com o botão direito do mouse no diretório de trabalho e crie um atalho para o programa de shell de comando (caixa do DOS para oldies) com o destino cmd.exe
  4. Edite as propriedades do atalho e defina a pasta de trabalho com o nome do seu diretório de trabalho (clique na barra de localização e copie).
  5. Abra cmd.exeusando o atalho e verifique se o prompt do DOS começa com o diretório de trabalho
  6. Digite "node bfCodes" sem aspas e o nó de execução na primeira vez pode demorar mais do que executá-lo novamente.
  7. Digite "node bfCodes 16" para mostrar códigos de até 16. Não use um número grande!

Otimização

O algoritmo percorre todas as combinações de caracteres anteriores, começando com uma sequência de códigos de comprimento 1. Pense nisso como girar um odômetro de base 15 a partir do dígito menos significativo. Dígitos de ordem superior clicam com o aumento da lentidão. bfCodesnão avalia o código gerado que tornaria o comprimento da pilha zero ou negativo ou deixaria mais de um número na pilha na tentativa de otimizar a velocidade de execução.

O Problema da Força Bruta

Para um conjunto de códigos de 15 caracteres, o tempo necessário para executar todas as combinações de um determinado comprimento é dado por

T len = 15 * T len-1

ou seja, se seu programa for executado quinze vezes mais rápido que o meu, você poderá verificar apenas uma sequência de códigos de caracteres adicionais ao mesmo tempo. Para verificar mais dois caracteres ao mesmo tempo, um programa precisaria executar 225 vezes mais rápido. O tempo gasto com uma abordagem de força bruta aumenta exponencialmente à medida que o comprimento das cadeias de código aumenta. E a magnitude de um número indica necessariamente o número de bytes necessários para gerá-lo.

Algumas figuras.

Tempos aproximados para gerar uma lista de códigos em um bloco de notas do Windows 7 de 32 bits para números inteiros até

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

Gerar befunge para 3727 (que é 66 ao quadrado mais 6) por si só levou 1 hora e 47 minutos e gerou 578*+:*6+

Geração de código ideal

Gerar befunge para números sem verificar os menores comprimentos é relativamente simples. Usando um algoritmo recursivo que usava raízes e números quadrados inteiros, as codificações para números de até 132 demoravam cerca de 3 ms em vez de 28 segundos. Eles não eram ótimos. Devido à maneira como ele funcionou, esse algoritmo específico foi produzido 638:*-:*+para 3727 em cerca de 1 ms (em vez de uma hora), o que foi ótimo.

A questão de fornecer um método de força não bruta está provando que é ideal em todos os casos. Boa sorte!


Você deve poder abaixar muito seu expoente observando que sua cadeia de caracteres deve representar uma árvore de avaliação válida +-*/nos nós internos 0-9e: nas folhas (e :não pode ser mais à esquerda). Então, gerar e avaliar todas árvore válida do tamanho 2 * n + 1 no passo n (n começa em 0) e convertê-los para um cordas quando necessário
Ton Hospel

3727 61 é quadrado mais 6, não 66 :)
Tim Vermeulen

1

Javascript

O Whant pode ser feito com um snippet JS? Na minha máquina, Firefox de 64 bits, 416 em 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>

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.