Nhanh chóng thể hiện một số chỉ với 0-9 và bốn thao tác, cộng thêm một số nữa


14

Giải trình

Befunge là một chương trình hai chiều sử dụng ngăn xếp .

Điều đó có nghĩa là, để làm 5 + 6, bạn viết 56+, có nghĩa là:

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)

Tuy nhiên, chúng ta không thể đẩy số 56trực tiếp vào ngăn xếp.

Để làm như vậy, chúng ta phải viết 78*thay vào đó, mà nhân lên 78và đẩy sản phẩm vào ngăn xếp.

Chi tiết

Đối với mỗi số từ 1đếnn , hãy tìm một chuỗi chỉ bao gồm các ký tự này: 0123456789+-*/:(Tôi sẽ không sử dụng %modulo.)

Mục tiêu là tìm chuỗi ngắn nhất có thể biểu thị số, sử dụng định dạng được mô tả ở trên.

Ví dụ, nếu đầu vào là 123, thì đầu ra sẽ là 67*9:*+. Đầu ra nên được đánh giá từ trái sang phải.

Nếu có nhiều hơn một đầu ra có thể chấp nhận (ví dụ: 99*67*+cũng được chấp nhận), bất kỳ đầu ra nào cũng có thể được in (không có phần thưởng cho việc in tất cả chúng).

Giải thích thêm

Nếu bạn vẫn không hiểu cách 67*9:*+đánh giá 123, đây là một lời giải thích chi tiết.

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

Chương trình cần tìm chuỗi ngắn nhất có thể biểu thị đầu vào (số), sử dụng định dạng được chỉ định ở trên.

KIẾM

  • Chúng tôi đã thực hiện nó trong số lượng mã ngắn nhất . Lần này, kích thước không thành vấn đề.
  • Ngôn ngữ bạn chọn phải có trình biên dịch / trình thông dịch miễn phí cho hệ điều hành của tôi (Windows 7 Enterprise).
  • Phần thưởng nếu bạn bao gồm liên kết đến trình biên dịch / trình thông dịch (tôi quá lười).
  • Nếu có thể, xin vui lòng bao gồm một bộ đếm thời gian để thuận tiện cho tôi. Đầu ra từ bộ đếm thời gian là hợp lệ.
  • Điểm số sẽ lớn nhất ntrong 1 phút.
  • Điều đó có nghĩa là, chương trình cần in đại diện cần thiết từ 1trở đi.
  • Không cứng mã hóa, ngoại trừ 0đến 9.

(thêm) ĐẶC BIỆT

  • Chương trình không hợp lệ nếu nó xuất ra một chuỗi dài hơn mức cần thiết cho bất kỳ số nào.
  • 1/0=ERROR
  • 5/2=2, (-5)/2=-2, (-5)/(-2)=2,5/(-2)=-2

Định hướng

Các -second-top minus top, có nghĩa là 92-lợi nhuận 7.

Tương tự, /được second-top divide top, có nghĩa là 92/lợi nhuận 4.

Chương trình mẫu

Lua

Sử dụng tìm kiếm theo chiều sâu.

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

Đợi đã, nếu chúng ta không thể đẩy 56trực tiếp vào ngăn xếp, làm sao chúng ta có thể đẩy 78vào ngăn xếp?
R. Kap

Chúng ta không thể đẩy 56năm mươi sáu trực tiếp vào ngăn xếp, nhưng chúng ta có thể đẩy 7bảy và 8tám riêng rẽ vào ngăn xếp.
Leaky Nun

1
@ R.Kap: Khi bạn làm một cái gì đó như 56trong Befunge, bạn đang đẩy các chữ số , vì vậy bạn kết thúc với một chồng [5, 6]. Để có được số 56, bạn phải đẩy 7, sau đó 8vào ngăn xếp, sau đó nhân chúng để lấy số 56 trên ngăn xếp.
El'endia Starman

1
:làm cho mọi thứ phức tạp hơn một rất nhiều, vì vậy tôi khuyên bạn nên cung cấp một danh sách tốt các trường hợp kiểm tra, ví dụ86387
Sp3000

1
số nguyên lớn nhất trong 5 giây là một số liệu xấu. thời gian tính toán cho các số lớn hơn sẽ không tăng đơn điệu, vì vậy nhiều giải pháp có thể bị kẹt ở cùng một số khó tính, mặc dù một số trong số chúng nhanh hơn hoặc chậm hơn nhiều so với các số gần đó.
Sparr

Câu trả lời:


7

C ++, làm nổ tung tất cả bộ nhớ trên máy tính gần bạn

Tạo chuỗi ngắn nhất trong đó tính toán không gây ra tràn số nguyên 32 bit đã ký (vì vậy tất cả các kết quả trung gian đều nằm trong phạm vi [-2147483648, 2147483647]

Trên hệ thống của tôi, điều này tạo ra một giải pháp cho tất cả các số lên đến và bao gồm 483432trong 30 giây trong khi sử dụng bộ nhớ 1.8G. Số thậm chí cao hơn sẽ nhanh chóng bùng nổ việc sử dụng bộ nhớ. Số lượng cao nhất tôi có thể xử lý trên hệ thống của tôi là 5113906. Việc tính toán mất gần 9 phút và 24GB. Khi hoàn thành, bên trong nó có một giải pháp cho 398499338các giá trị, khoảng 9% của tất cả các số nguyên 32 bit (dương và âm)

Cần một trình biên dịch C ++ 11. Trên linux biên dịch với:

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

Thêm -DINT64dưới dạng tùy chọn để sử dụng phạm vi số nguyên 64 bit thay vì 32 bit cho kết quả trung gian (điều này sẽ sử dụng thêm khoảng 50% thời gian và bộ nhớ). Điều này cần một loại 128 bit dựng sẵn. Bạn có thể cần phải thay đổi loại gcc __int128. Không có kết quả trong ít nhất phạm vi [1..483432]thay đổi bằng cách cho phép kết quả trung gian lớn hơn.

Thêm -DOVERFLOWdưới dạng tùy chọn để không sử dụng loại số nguyên lớn hơn để kiểm tra tràn. Điều này có tác dụng cho phép tràn và giá trị gói.

Nếu hệ thống của bạn có tcmalloc ( https://github.com/gperftools/gperftools ), bạn có thể liên kết với chương trình đó thường nhanh hơn một chút và sử dụng ít bộ nhớ hơn. Trên một số hệ thống UNIX, bạn có thể sử dụng tải trước, ví dụ:

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

Sử dụng cơ bản: tạo và in tất cả các số theo mục tiêu:

befour target

Tùy chọn:

  • -a Đồng thời in tất cả các số được tạo trong khi thực hiện mục tiêu
  • -c Đồng thời in tất cả các số được tạo bắt đầu bằng "carry" (dup)
  • -f Tìm và in số đầu tiên ngoài mục tiêu không được tạo
  • -s Dừng nếu mục tiêu được tạo ngay cả khi không phải tất cả các số trước đó đã được tạo
  • -SThích -s-f trong một vòng lặp tự động. Ngay khi mục tiêu được tạo, hãy tìm số đầu tiên chưa được tạo và làm cho mục tiêu mới đó
  • -EĐừng thoát ngay lập tức khi đạt được mục tiêu. Đầu tiên kết thúc tất cả các chuỗi có độ dài hiện tại
  • -OKhông xuất chuỗi cho tất cả các số theo mục tiêu. chỉ chuỗi cho mục tiêu
  • -o Hướng dẫn được phép (mặc định là +-*/:
  • -b numNghĩa đen thấp nhất có thể được đẩy (mặc định là 0)
  • -B numNghĩa đen cao nhất có thể được đẩy (mặc định 9)
  • -r numKết quả trung gian cho phép thấp nhất. Được sử dụng để tránh tràn. (mặc định là INT32_MIN,-2147483648
  • -R numKết quả trung gian cho phép cao nhất. Được sử dụng để tránh tràn. (mặc định là INT32_MAX,2147483647
  • -m memory (chỉ dành cho linux) thoát khi khoảng bộ nhớ thêm này đã được phân bổ

Một số kết hợp tùy chọn thú vị:

Tạo tất cả các số theo mục tiêu và tính toán số nhỏ nhất cần bộ tạo dài hơn tất cả các số này:

befour -fE target

Chỉ tạo mục tiêu (-s), chỉ in mục tiêu (-O)

befour -sO target

Tìm số cao nhất có thể được tạo trên hệ thống của bạn theo thời gian và / hoặc các ràng buộc về bộ nhớ (Điều này sẽ khiến hệ thống của bạn hết bộ nhớ nếu bạn để nó chạy. Trừ 1 từ đầu ra "tìm kiếm" cuối cùng mà bạn xem là giá trị an toàn cuối cùng ):

befour -S 1

Tạo giải pháp mà không bao giờ sử dụng kết quả trung gian âm ( 30932là giá trị đầu tiên cần kết quả trung gian âm cho chuỗi ngắn nhất):

befour -r0 target

Tạo các giải pháp mà không bao giờ thúc đẩy 0(điều này dường như không dẫn đến bất kỳ giải pháp tối ưu nào):

befour -b1 target

Tạo các giải pháp bao gồm a..f (10..15):

befour -B15 target

Tạo giải pháp mà không sử dụng dup :(thêm -r0vì giá trị trung gian âm không bao giờ thú vị trong trường hợp này)

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

Tìm giá trị đầu tiên mà không thể được tạo ra cho một chuỗi dài cho chỉ sử dụng +, -, */:

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

Trên thực tế, điều này sẽ tạo ra một số thuật ngữ đầu tiên của https://oeis.org/A181898 , nhưng sẽ bắt đầu phân kỳ 14771vì chúng tôi sử dụng phép chia cắt để số đó có thể được thực hiện với chuỗi dài 13 thay vì dài 15 như chuỗi OEIS mong đợi:

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

thay vì

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

Vì không có phân chia cắt ngắn dường như vô nghĩa, chuỗi OEIS có thể được tạo tốt hơn bằng cách sử dụng

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

Giả sử bộ phận vẫn vô dụng, điều này đã cho tôi thêm 3 điều khoản trước khi tôi hết bộ nhớ:

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

Một phiên bản khác của chương trình này lưu trữ một phần dữ liệu trong các tệp bên ngoài thêm 135153107 và 675854293 sau đó tất cả các số nguyên 32 bit đã được tạo.

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

Một số trường hợp thử nghiệm:

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

Công việc tốt, đây là ấn tượng nhanh chóng với những khó khăn của vấn đề! Mặc dù có một chút rắc rối: đối với 38950002chương trình của bạn 89*7+:::**1-*, điều này khá tốt, nhưng bạn có thể làm 299*-::*:*+ngắn hơn. Tôi nghĩ rằng điều này xác nhận những nghi ngờ tôi có về số âm ...
Sp3000

@ Sp3000: Bummer, tôi chỉ xem xét các số dương. Không khó để mở rộng chương trình để xử lý các số âm nhưng tôi hy vọng nó sẽ mất một bộ nhớ nghiêm trọng và tốc độ nhanh
TonMedel

@ Sp3000 Cập nhật cho thời gian tiêu cực. Phạm vi có thể tiếp cận thực sự đã giảm xuống một chút
TonMedel

int main(int argc, char const* const* argv)Tôi không biết C tốt hơn Joe trung bình nhưng đây là gì? một con trỏ const đến một con trỏ const đến một char? Không nên char const *argv[]hay như vậy (hoặc char const **argvnếu bạn là người khó tính)?
con mèo

@cat Đó là một con trỏ tới (một mảng) con trỏ không đổi đến (một mảng) hằng char. Để làm cho con trỏ cấp cao nhất không đổi, tôi sẽ phải thêm một const khác ngay trước argv (nó sẽ hoạt động vì tôi cũng không thay đổi argv). Về cơ bản, tôi hứa sẽ không thay đổi các đối số hoặc các con trỏ thành các đối số.
TonMedel

2

JavaScript Node Brute Force

Tệp chương trình 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);

Chạy trong Windows

  1. Tải xuống và cài đặt Nodejs , một triển khai độc lập của công cụ JavaScript Chromes V8.
  2. Lưu tệp chương trình ở trên trong một thư mục làm việc bằng tên tệp "bfCodes.js" (tên tệp Windows không phân biệt chữ hoa chữ thường).
  3. Nhấp chuột phải vào thư mục làm việc và tạo một lối tắt đến chương trình shell lệnh (hộp DOS cho oldies) với đích cmd.exe
  4. Chỉnh sửa thuộc tính của phím tắt và đặt thư mục làm việc thành tên của thư mục làm việc của bạn (nhấp vào thanh vị trí và sao chép).
  5. Mở cmd.exebằng phím tắt và kiểm tra lời nhắc DOS bắt đầu với thư mục làm việc
  6. Nhập "nút bfCodes" không có dấu ngoặc kép và nhập - chạy nút lần đầu tiên có thể mất nhiều thời gian hơn so với chạy lại.
  7. Nhập "nút bfCodes 16" để hiển thị mã lên đến 16. Không sử dụng số lượng lớn!

Tối ưu hóa

Thuật toán quay vòng qua tất cả các kết hợp các ký tự befunge bắt đầu bằng một chuỗi mã có độ dài 1. Hãy nghĩ về nó như quay một 15 mét cơ sở từ chữ số có nghĩa ít nhất. Chữ số thứ tự cao hơn nhấp qua với sự chậm chạp tăng.bfCodeskhông đánh giá mã được tạo sẽ làm cho chiều dài ngăn xếp bằng 0 hoặc âm hoặc để lại nhiều hơn một số trên ngăn xếp nhằm cố gắng tối ưu hóa tốc độ thực hiện.

Vấn đề Brute Force

Đối với bộ mã gồm 15 ký tự, thời gian cần thiết để chạy qua tất cả các kết hợp có độ dài nhất định được cung cấp bởi

T len = 15 * T len-1

điều đó có nghĩa là nếu chương trình của bạn chạy nhanh hơn mười lăm lần so với của tôi, bạn sẽ chỉ có thể kiểm tra thêm một chuỗi mã ký tự. Để kiểm tra thêm hai ký tự cùng lúc, một chương trình sẽ cần chạy nhanh hơn 225 lần. Thời gian thực hiện với cách tiếp cận lực lượng vũ phu tăng theo cấp số nhân khi độ dài của chuỗi mã tăng lên. Và độ lớn của một số không nhất thiết chỉ ra số byte befunge cần thiết để tạo ra nó.

Một số số liệu.

Thời gian gần đúng để tạo danh sách mã trên notepad Windows 7 32 bit cho số nguyên lên đến

  • 9: 1 phút
  • 10: 16 phút
  • 32: 156 phút
  • 81: 312 giây
  • 93: 18,5 giây
  • 132: 28 giây

Để tạo befunge cho 3727 (tức là 66 bình phương cộng với 6), chỉ mất 1 giờ 47 phút và tạo ra 578*+:*6+

Tạo mã tối ưu

Tạo befunge cho các số mà không kiểm tra độ dài ngắn nhất là tương đối đơn giản. Sử dụng thuật toán đệ quy sử dụng căn bậc hai và số dư còn lại, mã hóa cho các số lên tới 132 mất khoảng 3 ms thay vì 28 giây. Chúng không tối ưu. Do cách thức hoạt động, thuật toán đặc biệt này được tạo ra 638:*-:*+cho 3727 trong khoảng 1 ms (thay vì một giờ hoặc lâu hơn), điều này xảy ra là tối ưu.

Vấn đề với việc cung cấp một phương pháp không vũ phu đang chứng minh rằng nó là tối ưu trong mọi trường hợp. Chúc may mắn!


Bạn sẽ có thể hạ thấp số mũ của mình xuống rất nhiều bằng cách quan sát rằng chuỗi của bạn phải đại diện cho một cây đánh giá hợp lệ với +-*/tại các nút bên trong 0-9:tại các lá (và :không thể ở ngoài cùng). Vì vậy, tạo và đánh giá tất cả cây hợp lệ có kích thước 2 * n + 1 ở bước n (n bắt đầu từ 0) và chuyển đổi chúng thành chuỗi khi cần
TonMedel

3727 là 61 bình phương cộng với 6, không phải 66 :)
Tim Vermeulen

1

JavaScript

Ai có thể được thực hiện với một đoạn mã JS? Trong máy của tôi, Firefox 64 bit, 416 trong 60 giây

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>

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.