Швидко висловіть число лише з 0-9 та чотирма операціями плюс ще однією додатковою


14

Пояснення

Befunge - двовимірна програма, яка використовує стеки .

Це означає, що робити 5 + 6, ви пишете 56+, тобто:

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)

Однак ми не можемо натиснути число 56прямо в стек.

Щоб зробити це, ми повинні написати 78*замість цього, який примножує 7і 8і штовхає продукт в стек.

Деталі

Для кожного числа від 1доn знайдіть рядок, що складається лише з цих символів: 0123456789+-*/:(я б не використовував %модуль.)

Мета - знайти найкоротший рядок, який може представляти число, використовуючи описаний вище формат.

Наприклад, якщо вхід є 123, то вихід буде 67*9:*+. Вихід слід оцінювати зліва направо.

Якщо є більш ніж один прийнятний вихід (наприклад 99*67*+, також прийнятний), будь-який з них може бути надрукований (жодного бонусу за друк усіх).

Подальше пояснення

Якщо ви все ще не розумієте, як 67*9:*+оцінюється 123, ось детальне пояснення.

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

Програмі необхідно знайти найкоротший рядок, який може представляти вхід (число), використовуючи вказаний вище формат.

СКОРІННЯ

  • Ми це вже зробили в найкоротшій кількості коду . Цього разу розмір не має значення.
  • Ваша мова на вибір повинна мати безкоштовний компілятор / інтерпретатор для моєї операційної системи (Windows 7 Enterprise).
  • Бонус, якщо ви включите посилання на компілятор / перекладач (я занадто ледачий).
  • По можливості, будь ласка, включіть таймер для моєї зручності. Вихід з таймера є дійсним.
  • Оцінка буде найбільшою nза 1 хвилину.
  • Це означає, що програма повинна надрукувати необхідне представлення 1далі.
  • Немає жорстке кодування, за винятком 0до 9.

(докладніше) ОСОБЛИВОСТІ

  • Програма недійсна, якщо виводить рядок довше, ніж потрібно для будь-якого числа.
  • 1/0=ERROR
  • 5/2=2, (-5)/2=-2, (-5)/(-2)=2,5/(-2)=-2

Однозначність

-Це second-top minus topозначає , що 92-повертається 7.

Точно так само /є second-top divide topзначення, яке 92/повертається 4.

Зразок програми

Луа

Використовує пошук по глибині.

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

Зачекайте, якщо ми не можемо натиснути 56прямо в стек, як ми можемо натиснути 78на стек?
Р. Кап

Ми не можемо натиснути 56п'ятдесят шість безпосередньо в стек, але ми можемо натиснути 7сім і 8вісім окремо в групу.
Лина монашка

1
@ R.Kap: Коли ви робите щось на кшталт 56Befunge, ви натискаєте цифри , тож ви закінчуєте стек [5, 6]. Щоб отримати число 56, потрібно натиснути 7, потім 8на стек, а потім помножити їх, щоб отримати число 56 на стеку.
El'endia Starman

1
:робить речі набагато складнішими, тому я рекомендую надати хороший список тестових випадків, наприклад86387
Sp3000

1
найбільше ціле число за 5 секунд - це поганий показник. час обчислень для більших чисел не збільшиться монотонно, тому багато рішень можуть застрягти на одному важко підрахунковому числі, незважаючи на те, що деякі з них знаходяться набагато швидше або повільніше на сусідніх числах.
Спарр

Відповіді:


7

C ++, вибухаючи всю пам'ять на комп'ютері, що знаходиться поруч

Створює найкоротший рядок, де обчислення нікуди не викликає переповнення підписаного 32-бітного цілого числа (тому всі проміжні результати знаходяться в діапазоні [-2147483648, 2147483647]

У моїй системі це створює рішення для всіх чисел, що включають 483432до 30 секунд, в той час як використовується 1,8 Гб пам'яті. Навіть більші цифри швидко вибухнуть з використанням пам'яті. Найбільша кількість, з якими я можу працювати в своїй системі 5113906. Розрахунок займає майже 9 хвилин і 24 ГБ. Після завершення внутрішньо він має рішення для 398499338значень, приблизно 9% з усіх 32 бітових цілих чисел (позитивних і негативних)

Потрібен компілятор C ++ 11. На Linux компілюйте з:

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

Додайте -DINT64як опцію використання 64-розрядного цілого діапазону замість 32-бітного для проміжних результатів (для цього буде витрачено приблизно на 50% більше часу та пам'яті). Для цього потрібен вбудований 128-ти бітний тип. Можливо, вам знадобиться змінити тип gcc __int128. Немає результату щонайменше для [1..483432]зміни діапазону , дозволяючи отримати більш проміжні результати.

Додати -DOVERFLOWяк варіант, щоб не використовувати більший цілий тип для перевірки на переповнення. Це дає змогу забезпечити переповнення та перекриття значень.

Якщо у вашій системі є tcmalloc ( https://github.com/gperftools/gperftools ), ви можете зв’язатись із цим, що призводить до програми, яка, як правило, трохи швидша і використовує трохи менше пам’яті. У деяких системах UNIX можна використовувати попереднє завантаження, наприклад

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

Основне використання: генеруйте та друкуйте всі цифри до цілі:

befour target

Параметри:

  • -a Також надрукуйте всі цифри, які були створені під час розробки цілі
  • -c Також надрукуйте всі створені номери, починаючи з "переносу" (копії)
  • -f Знайдіть та роздрукуйте перше число поза ціллю, яке не було створено
  • -s Зупиняється, якщо генерується ціль, навіть якщо раніше не були створені всі числа
  • -SЯк -sі -fв автоматичному циклі. Як тільки генерується мета, знайдіть перше ще не сформоване число і зробіть це новим
  • -EНе одразу виходьте, коли мета буде досягнута. Спочатку закінчіть всі рядки поточної довжини
  • -OНе виводити рядки для всіх чисел до цільових. просто рядок для цілі
  • -o Дозволені інструкції (за замовчуванням до +-*/:
  • -b numНайнижчий літерал, який можна натиснути (за замовчуванням 0)
  • -B numНайвищий буквальний, який можна натиснути (за замовчуванням 9)
  • -r numНайнижчий дозволений проміжний результат. Використовується для уникнення переливу. (за замовчуванням до INT32_MIN,-2147483648
  • -R numНайвищий дозволений проміжний результат. Використовується для уникнення переповнення. (за замовчуванням до INT32_MAX,2147483647
  • -m memory (лише для Linux) вихід, коли виділено приблизно стільки додаткової пам'яті

Деякі цікаві комбінації варіантів:

Згенеруйте всі числа до націлювання та обчисліть найменше число, якому потрібен довший генератор, ніж усі ці числа:

befour -fE target

Створити лише цільову (-ів), друкувати лише цільову (-о)

befour -sO target

Знайдіть найбільше число, яке можна створити у вашій системі з урахуванням обмежень часу та / або пам’яті (Це призведе до втрати пам’яті вашої системи, якщо ви залишите її працювати. Віднімайте 1 з останнього «шукаючого» виводу, який ви бачите як останнє безпечне значення ):

befour -S 1

Створюйте рішення, не використовуючи ніколи негативних проміжних результатів ( 30932це перше значення, яке потребує негативних проміжних результатів для найкоротшого рядка):

befour -r0 target

Створюйте рішення без жодного натиску 0(це, здається, не призводить до будь-яких неоптимальних рішень):

befour -b1 target

Створюйте рішення, включаючи a..f (10..15):

befour -B15 target

Створюйте рішення без використання дублювання :(додайте, -r0оскільки негативні проміжні значення ніколи не цікаві для цього випадку)

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

Знайти перше значення , яке не може бути створено для заданої довжини рядка , використовуючи тільки +, -, *і /:

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

Це фактично породжує перші кілька термінів https://oeis.org/A181898 , але почнемо розходитись 14771через те, що ми використовуємо поділ обрізання, щоб це число можна було виконати з рядком довжиною 13 замість довжини 15 як серія OEIS очікує:

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

замість

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

Оскільки без поділу укорочення здається безглуздим, серію OEIS можна краще сформувати за допомогою використання

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

Якщо припустити, що поділ залишається марним, це дало мені 3 додаткові умови, перш ніж я вийшов із пам’яті:

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

Інша версія цієї програми, що зберігає частину даних у зовнішніх файлах, додає 135153107 та 675854293, після чого генеруються всі 32-бітні цілі числа.

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

Деякі тестові випадки:

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

Приємна робота, це вражаюче швидко, враховуючи складність проблеми! Трохи клопоту, хоч: для 38950002вашої програми дає 89*7+:::**1-*, що досить добре, але ви можете зробити 299*-::*:*+за коротший. Я думаю, це підтверджує сумніви, які я мав щодо негативних цифр ...
Sp3000

@ Sp3000: Баммер, я вважав лише позитивні цифри. Не важко розширити програму на обробку негативних цифр, але, сподіваюсь, це потребуватиме серйозної пам’яті та швидкості удару
Тон Євангелія,

@ Sp3000 Оновлено для негативних часок. Діапазон досяжності дійсно знизився зовсім небагато
Тон Євангелія

int main(int argc, char const* const* argv)Я не знаю С краще середнього Джо, але що це? const pointer to const pointer to char? Чи не повинно бути це char const *argv[]чи так (або char const **argvякщо ви такий хардкор)?
кіт

@cat Це вказівник на (масив) постійних покажчиків на (масив) постійних знаків. Щоб зробити покажчик верхнього рівня постійним, мені доведеться додати ще один const безпосередньо перед argv (який би спрацював, оскільки я також не змінюю argv). В основному я обіцяю не змінювати аргументів чи покажчиків на аргументи.
Тон Євангелія

2

JavaScript Node Brute Force

Програмний файл 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);

Запуск під Windows

  1. Завантажте та встановіть Nodejs , самостійну реалізацію двигуна JavaScript Chromes V8.
  2. Збережіть програмний файл вище у робочому каталозі, використовуючи ім'я файлу "bfCodes.js" (назви файлів Windows не залежать від регістру).
  3. Клацніть правою кнопкою миші в робочому каталозі та створіть ярлик до програми командної оболонки (поле DOS для старих) із цільовим cmd.exe
  4. Відредагуйте властивості ярлика та встановіть робочу папку на ім'я робочого каталогу (натисніть на панелі розташування та скопіюйте).
  5. Відкрийте cmd.exeза допомогою ярлика і перевірте, чи починається підказка DOS з робочого каталогу
  6. Введіть "вузол bfCodes" без лапок і введіть - перший робочий вузол може зайняти більше часу, ніж запустити його знову.
  7. Введіть "вузол bfCodes 16", щоб показати коди до 16. Не використовуйте велику кількість!

Оптимізація

Алгоритм переглядає всі комбінації підключених символів, починаючи з кодового рядка довжиною 1. Подумайте про це, як накручувати базовий 15 одометр від найменш значущої цифри. Цифри вищого порядку клацають по мірі збільшення повільності. bfCodesне оцінює згенерований код, який би зробив довжину стека нульовою або негативною або залишив більше одного числа на стеку, намагаючись оптимізувати швидкість виконання.

Проблема грубої сили

Для кодового набору з 15 символів відводиться час, необхідний для проходження всіх комбінацій заданої довжини

T len = 15 * T len-1

що означає, що якщо ваша програма працює в п’ятнадцять разів швидше моєї, ви зможете перевірити лише один додатковий рядок символьного коду за один і той же час. Щоб перевірити ще два символи за один і той же час, програмі потрібно було б запустити в 225 разів швидше. Час, прийнятий при грубому підході, збільшується експоненціально, оскільки збільшується довжина рядків коду. А величина числа обов'язково вказує кількість необхідних байтів, необхідних для його генерування.

Деякі цифри.

Приблизний час для створення списків кодів на 32-бітовому блокноті Windows 7 для цілих чисел

  • 9: 1 мсек
  • 10: 16 мсек
  • 32: 156 мсек
  • 81: 312 мсек
  • 93: 18,5 секунди
  • 132: 28 секунд

Щоб створити функцію 3727 (що становить 66 квадратів плюс 6), сама по собі зайняла 1 годину 47 хвилин 578*+:*6+

Оптимальне генерування коду

Зробити помилку для чисел без перевірки на найкоротші довжини порівняно просто. Використовуючи рекурсивний алгоритм, який використовував цілі квадратні корені та залишки, кодування для чисел до 132 зайняло близько 3 мсек замість 28 секунд. Вони не були оптимальними. Через те, як він працював, саме цей алгоритм вироблявся 638:*-:*+за 3727 приблизно за 1 мсек (замість години або близько того), що виявилося оптимальним.

Проблема із застосуванням методу без грубої сили доводить, що він є оптимальним у кожному випадку. Удачі!


Ви повинні мати можливість опустити свій показник на багато, зауваживши, що ваш рядок повинен представляти дійсне дерево оцінювання з +-*/внутрішніх вузлів 0-9і :на листках (і :не може бути лівим зліва). Тож генеруйте та оцінюйте все дійсне дерево розміром 2 * n + 1 на кроці n (n починається з 0) та конвертуйте їх у рядки за необхідності
Тон Евангелія

3727 - це 61 квадрат плюс 6, а не 66 :)
Тім Вермеулен

1

JavaScript

Що можна зробити за допомогою фрагмента JS? У моїй машині Firefox 64 біт, 416 за 60 секунд

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>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.