0에서 9까지의 숫자와 네 가지 연산 그리고 하나 더 추가 된 숫자를 빠르게 표현


14

설명

Befunge스택 을 사용하는 2 차원 프로그램입니다 .

즉, 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)를위한 무료 컴파일러 / 인터프리터가 있어야합니다.
  • 컴파일러 / 통역사에 대한 링크를 포함하면 보너스입니다 (너무 게으 릅니다).
  • 가능한 경우 편의를 위해 타이머를 포함하십시오. 타이머의 출력이 유효합니다.
  • 점수는 n1 분 안에 가장 커집니다.
  • 즉, 프로그램은 1이후에 필요한 표현을 인쇄해야합니다 .
  • 더 열심히하지 코딩을 제외하고, 09.

(더) SPECIFICS

  • 프로그램이 원하는 수보다 긴 문자열을 출력하면 유효하지 않습니다.
  • 1/0=ERROR
  • 5/2=2, (-5)/2=-2, (-5)/(-2)=2,5/(-2)=-2

명확성

-것입니다 second-top minus top그 의미 92-반환 7.

마찬가지로 /is는 second-top divide top92/반환합니다 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넣을 수 있습니까?
R. Kap

56스택으로 직접 56을 밀어 넣을 수는 없지만 스택으로 77과 88을 별도로 밀어 넣을 수 있습니다 .
Leaky Nun

1
@ R.Kap : 56Befunge에서 와 같은 일을 할 때 숫자를 밀고 있으므로 스택이 끝납니다 [5, 6]. 숫자 56 을 얻으려면 78눌렀다가 스택에 놓은 다음 곱하여 숫자 56을 스택에 줘야합니다.
El'endia Starman

1
:상황이 훨씬 86387
까다로워 지므로

1
5 초 안에 가장 큰 정수는 잘못된 메트릭입니다. 더 큰 숫자의 계산 시간은 단조 증가하지 않으므로 많은 솔루션이 계산하기 어려운 동일한 숫자에 멈출 수 있습니다. 일부 숫자는 근처 숫자에서 훨씬 빠르거나 느리더라도 불구하고 계산할 수 없습니다.
Sparr

답변:


7

C ++, 가까운 컴퓨터의 모든 메모리를 폭발

계산에서 부호있는 32 비트 정수의 오버플로가 발생하지 않는 가장 짧은 문자열을 생성합니다 (따라서 모든 중간 결과가 범위 내에 있음) [-2147483648, 2147483647]

내 시스템에서 이것은 최대를 포함하여 모든 숫자에 대한 솔루션을 생성합니다. 483432 1.8G 메모리를 사용하는 동안 30 초를 . 숫자가 높을수록 메모리 사용량이 빠르게 폭발합니다. 시스템에서 처리 할 수있는 가장 높은 숫자는5113906 입니다. 계산에는 거의 9 분 24GB가 걸립니다. 완료되면 내부적 398499338으로 모든 32 비트 정수의 약 9 % (양수 및 음수)에 대한 솔루션이 있습니다.

C ++ 11 컴파일러가 필요합니다. 리눅스에서 컴파일 :

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

-DINT64중간 결과에 32 비트 대신 64 비트 정수 범위를 사용하는 옵션으로 추가 합니다 (시간과 메모리가 약 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 "캐리"(dup)로 시작하여 생성 된 모든 숫자도 인쇄하십시오.
  • -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 (리눅스 만)이 정도의 추가 메모리가 할당되면 종료

몇 가지 흥미로운 옵션 조합 :

대상까지 모든 숫자를 생성하고이 숫자보다 긴 발전기가 필요한 가장 작은 숫자를 계산하십시오.

befour -fE target

대상 만 생성 (-s), 대상 만 인쇄 (-O)

befour -sO target

주어진 시간 및 / 또는 메모리 제약 조건에서 시스템에서 생성 할 수있는 가장 높은 숫자를 찾으십시오 (이를 실행하면 시스템 메모리가 부족합니다. 마지막 "찾는"출력에서 1을 빼고 마지막 안전 값으로 표시합니다. ) :

befour -S 1

음수 중간 결과를 사용하지 않고 솔루션 생성 ( 30932가장 짧은 문자열에 대해 음수 중간 결과가 필요한 첫 번째 값) :

befour -r0 target

추진하지 않고 솔루션을 생성하십시오 0(이것은 차선책으로 이어지지는 않습니다).

befour -b1 target

다음을 포함한 솔루션을 생성하십시오 a..f (10..15).

befour -B15 target

dup을 사용하지 않고 솔루션 생성 :( -r0이 경우에는 음의 중간 값이 절대로 흥미롭지 않기 때문에 추가 )

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

사용하여 단지 주어진 문자열의 길이를 생성 할 수 없습니다 첫 번째 값 찾기 +, -, *과를 /:

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

이것은 실제로 https://oeis.org/A181898 의 처음 몇 용어를 생성 하지만 14771OEIS 시리즈로 길이 15 대신 길이 13 문자열로 숫자를 수행 할 수 있도록 절단 나누기를 사용하기 때문에 분기 되기 시작합니다. 기대합니다 :

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 : Bummer, 나는 양수 만 고려했습니다. 음수도 처리 할 수 ​​있도록 프로그램을 확장하는 것은 어렵지 않지만 메모리와 속도가 많이 걸리는 것으로 예상됩니다.
Ton Hospel

@ Sp3000 음수 임시로 업데이트되었습니다. 도달 할 수있는 범위는 실제로 약간 내려 갔다
Ton Hospel

int main(int argc, char const* const* argv)나는 평균 Joe보다 C를 잘 모르지만 이것이 무엇입니까? char에 대한 const 포인터에 대한 const 포인터? 그렇지 않아야합니까 char const *argv[](또는 char const **argv하드 코어 인 경우)?
고양이

@cat 상수 char에 대한 상수 포인터에 대한 포인터입니다. 상수 char에 대한 배열입니다. 최상위 레벨 포인터를 일정하게 만들려면 argv 앞에 직접 또 다른 const를 추가해야합니다 (arrgv를 변경하지 않았으므로 작동합니다). 기본적으로 나는 논쟁이나 논쟁에 대한 포인터를 바꾸지 않겠다고 약속합니다.
Ton Hospel

2

자바 스크립트 노드 무차별 대입

프로그램 파일 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 다운로드 및 설치Chromes V8 JavaScript 엔진의 독립형 구현 .
  2. 파일 이름 "bfCodes.js"를 사용하여 위의 프로그램 파일을 작업 디렉토리에 저장하십시오 (Windows 파일 이름은 대소 문자를 구분하지 않습니다).
  3. 작업 디렉토리를 마우스 오른쪽 단추로 클릭하고 대상이있는 명령 쉘 프로그램 (예 : DOS 상자)에 대한 단축키를 작성하십시오. cmd.exe
  4. 바로 가기 속성을 편집하고 작업 폴더를 작업 디렉토리 이름으로 설정하십시오 (위치 표시 줄을 클릭하고 복사).
  5. 열다 cmd.exe바로 가기를 사용하여 DOS 프롬프트가 작업 디렉토리로 시작하는지 확인하십시오.
  6. 따옴표없이 "node bfCodes"를 입력하고 처음으로 실행 노드를 다시 실행하는 것보다 시간이 오래 걸릴 수 있습니다.
  7. 최대 16 개의 코드를 표시하려면 "node bfCodes 16"을 입력하십시오 . 많은 수를 사용하지 마십시오!

최적화

알고리즘은 길이가 1 인 코드 문자열로 시작하는 모든 펀지 문자 조합을 순환합니다. 주문 숫자가 높을수록 속도가 느려집니다.bfCodes실행 속도를 최적화하기 위해 스택 길이를 0 또는 음수로 만들거나 스택에 둘 이상의 숫자를 남겨 두는 생성 된 코드를 평가하지 않습니다.

무차별 대입 문제

15 자 코드 세트의 경우 주어진 길이의 모든 조합을 실행하는 데 걸리는 시간은 다음과 같습니다.

T len = 15 * T len-1

즉, 프로그램이 내 것보다 15 배 빠르게 실행되면 동시에 하나의 추가 문자 코드 문자열 만 확인할 수 있습니다. 동시에 두 문자를 더 확인하려면 프로그램이 225 배 빠르게 실행되어야합니다. 무차별 대입 방식으로 소요되는 시간은 코드 문자열의 길이가 증가함에 따라 기하 급수적으로 증가합니다. 그리고 숫자의 크기는 반드시 그것을 생성하는 데 필요한 befunge 바이트 수를 나타냅니다.

일부 인물.

Windows 7 32 비트 메모장에서 최대 정수까지 코드 목록을 생성하는 대략적인 시간

  • 9 : 1 밀리 초
  • 10:16 밀리 초
  • 32 : 156 밀리 초
  • 81 : 312 밀리 초
  • 93 : 18.5 초
  • 132 : 28 초

3727 (66 제곱 + 6)에 대한 befunge를 자체적으로 생성하는 데 1 시간 47 분이 걸렸습니다. 578*+:*6+

최적의 코드 생성

최단 길이를 확인하지 않고 숫자에 대한 펀드를 생성하는 것은 비교적 간단합니다. 정수 제곱근과 나머지를 사용하는 재귀 알고리즘을 사용하여 최대 132 개의 숫자를 인코딩하는 데 28 초가 아닌 약 3msec가 걸렸습니다. 그들은 최적이 아니었다. 방식 때문에 그것은 생산이 특정 알고리즘 일 638:*-:*+(그래서 대신 한 시간) 1 밀리 초에 대해에 3727에 대한이 일어난 최적 할 수 있습니다.

무차별 강제 방법을 제공하는 문제는 모든 경우에 최적임을 증명하고 있습니다. 행운을 빕니다!


당신은 당신의 문자열에 유효한 평가 나무를 표현해야 함을 관찰하여 많은하여 지수를 낮출 수 있어야 +-*/내부 노드에와 0-9: 잎에 (그리고 :왼쪽을하지 않을 수 있습니다.) 따라서 단계 n에서 크기가 2 * n + 1 인 모든 유효한 트리를 생성하고 평가하고 (n은 0에서 시작) 필요할 때 문자열로 변환합니다
Ton Hospel

3727은 61이 아니라 66 : 6이고 제곱 플러스
팀 Vermeulen에게

1

자바 스크립트

JS 스 니펫으로 할 수 있습니까? 내 컴퓨터에서 Firefox 64 비트, 60 초에 416

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>

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.