แสดงหมายเลขได้อย่างรวดเร็วด้วยการดำเนินการเพียงแค่ 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+-*/:(ฉันจะไม่ใช้%modulo)

เป้าหมายคือการหาที่สั้นที่สุดสตริงที่ที่สามารถเป็นตัวแทนของตัวเลขโดยใช้รูปแบบที่อธิบายไว้ข้างต้น

ตัวอย่างเช่นถ้าใส่เป็นแล้วออกจะเป็น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

โปรแกรมต้องการค้นหาที่สั้นที่สุดสตริงที่ที่สามารถเป็นตัวแทนของอินพุต (ตัวเลข) โดยใช้รูปแบบที่ระบุข้างต้น

SCORING

  • เราได้ทำไปแล้วในจำนวนรหัสที่สั้นที่สุดปริมาณที่สั้นของรหัสเวลานี้ขนาดไม่สำคัญ
  • ภาษาที่คุณเลือกต้องมีคอมไพเลอร์ / ล่ามฟรีสำหรับระบบปฏิบัติการของฉัน (Windows 7 Enterprise)
  • โบนัสถ้าคุณรวมลิงค์ไปยังคอมไพเลอร์ / ล่าม (ฉันขี้เกียจเกินไป)
  • ถ้าเป็นไปได้โปรดรวมเวลาเพื่อความสะดวกของฉัน เอาต์พุตจากตัวจับเวลาใช้งานได้
  • คะแนนจะมากที่สุดnใน 1 นาที
  • นั่นหมายความว่าโปรแกรมจำเป็นต้องพิมพ์การแสดงที่ต้องการจาก1เป็นต้นไป
  • ไม่มีการเข้ารหัสที่ยากยกเว้นไป09

(เพิ่มเติม) ข้อมูลจำเพาะ

  • โปรแกรมไม่ถูกต้องหากมันส่งออกสตริงยาวเกินความจำเป็นสำหรับหมายเลขใด ๆ
  • 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ผลตอบแทน

โปรแกรมตัวอย่าง

Lua

ใช้การค้นหาเชิงลึกก่อน

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ห้าสิบหกโดยตรงลงในสแต็ก แต่เราสามารถผลัก7เจ็ดและ8แปดแยกเป็นกอง
Leun Nun

1
@ R.Kap: เมื่อคุณทำอะไรบางอย่าง56ใน Befunge คุณจะผลักตัวเลขดังนั้นคุณจึงจบด้วยสแต็[5, 6]ก ในการรับหมายเลข 56 คุณต้องกด7จากนั้น8เข้าสู่สแต็กแล้วคูณพวกเขาเพื่อรับหมายเลข 56 บนสแต็ก
El'endia Starman

1
:ทำให้สิ่งต่าง ๆ มีความซับซ้อนมากขึ้นดังนั้นฉันขอแนะนำให้จัดทำรายการทดสอบที่ดีเช่น86387
Sp3000

1
จำนวนเต็มที่มากที่สุดใน 5 วินาทีเป็นตัวชี้วัดที่ไม่ดี เวลาในการคำนวณสำหรับจำนวนที่มากขึ้นจะไม่เพิ่มขึ้นซ้ำซากจำเจดังนั้นวิธีแก้ปัญหาจำนวนมากอาจติดอยู่ที่หมายเลขที่คำนวณได้ยากเหมือนกัน
Sparr

คำตอบ:


7

C ++, กระจายหน่วยความจำทั้งหมดในคอมพิวเตอร์ที่อยู่ใกล้คุณ

สร้างสตริงที่สั้นที่สุดที่การคำนวณไม่มีสาเหตุทำให้เกิดการล้นของจำนวนเต็มแบบ 32 บิตที่ลงชื่อ (ดังนั้นผลลัพธ์ระดับกลางทั้งหมดจะอยู่ในช่วง [-2147483648, 2147483647]

ในระบบของฉันสิ่งนี้จะสร้างวิธีแก้ปัญหาสำหรับตัวเลขทั้งหมดรวมถึง483432ภายใน 30 วินาทีในขณะที่ใช้หน่วยความจำ 1.8G ตัวเลขที่สูงขึ้นจะระเบิดการใช้หน่วยความจำอย่างรวดเร็ว 5113906จำนวนมากที่สุดฉันสามารถจัดการในระบบของฉันคือ การคำนวณใช้เวลาเกือบ 9 นาทีและ 24GB เมื่อเสร็จสิ้นภายในจะมีวิธีแก้ปัญหาสำหรับ398499338ค่าประมาณ 9% ของจำนวนเต็ม 32 บิตทั้งหมด (บวกและลบ)

ต้องการคอมไพเลอร์ C ++ 11 บน linux คอมไพล์ด้วย:

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

เพิ่ม-DINT64เป็นตัวเลือกเพื่อใช้ช่วงจำนวนเต็ม 64- บิตแทน 32- บิตสำหรับผลลัพธ์ระดับกลาง (ซึ่งจะใช้เวลาและหน่วยความจำเพิ่มขึ้นประมาณ 50%) ต้องใช้ชนิด 128 บิตในตัว __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ผลลัพธ์ระดับกลางที่ต่ำที่สุดที่อนุญาต ใช้เพื่อหลีกเลี่ยง underflow (ค่าเริ่มต้น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แต่จะเริ่มต้นที่แตกต่าง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: คนเกียจคร้านฉันคิดแค่ตัวเลขที่เป็นบวกเท่านั้น ไม่ใช่เรื่องยากที่จะขยายโปรแกรมให้จัดการกับจำนวนลบ แต่ฉันคาดว่าจะใช้หน่วยความจำและความเร็วในการเข้าชมที่รุนแรง
Ton Hospel

@ Sp3000 อัปเดตสำหรับชั่วขณะเชิงลบ ช่วงที่สามารถเข้าถึงได้ลดลงอย่างแน่นอน
Ton Hospel

int main(int argc, char const* const* argv)ฉันไม่รู้ C ดีกว่าค่าเฉลี่ย Joe แต่นี่คืออะไร ตัวชี้ const ไปยังตัวชี้ const เพื่อ char หรือไม่? มันควรจะเป็นchar const *argv[]อย่างนั้นหรือไม่ (หรือchar const **argvถ้าคุณเป็นฮาร์ดคอร์นั้น)
แมว

@cat มันเป็นตัวชี้ไปยังพอยน์เตอร์ (อาเรย์ของ) พอยน์เตอร์ไปยัง (อาเรย์ของ) ค่าคงที่ถ่าน เพื่อให้ตัวชี้ระดับบนสุดคงที่ฉันจะต้องเพิ่ม const อื่นตรงด้านหน้า argv ด้วย (ซึ่งจะใช้ได้เนื่องจากฉันไม่เปลี่ยน argv เช่นกัน) โดยทั่วไปฉันสัญญาว่าจะไม่เปลี่ยนแปลงข้อโต้แย้งหรือพอยน์เตอร์เป็นข้อโต้แย้ง
Ton Hospel

2

JavaScript โหนดกำลังดุร้าย

ไฟล์โปรแกรม 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 สำหรับ oldies) โดยมีเป้าหมาย cmd.exe
  4. แก้ไขคุณสมบัติของทางลัดและตั้งค่าโฟลเดอร์การทำงานเป็นชื่อของไดเรกทอรีการทำงานของคุณ (คลิกในแถบตำแหน่งและคัดลอก)
  5. เปิดcmd.exeโดยใช้ทางลัดและตรวจสอบว่าพร้อมท์ DOS เริ่มต้นด้วยไดเรกทอรีทำงาน
  6. ป้อน "node bfCodes" โดยไม่ต้องใส่เครื่องหมายอัญประกาศและป้อน - runing node ในครั้งแรกอาจใช้เวลานานกว่าการรันอีกครั้ง
  7. ป้อน "node bfCodes 16" เพื่อแสดงรหัสสูงสุด 16 อย่าใช้จำนวนมาก!

การเพิ่มประสิทธิภาพ

อัลกอริธึมวนผ่านชุดอักขระ befunge ทั้งหมดที่เริ่มต้นด้วยสตริงรหัสที่มีความยาว 1 ลองคิดว่ามันเป็นการหมุนฐานระยะทาง 15 ฐานจากตัวเลขที่มีนัยสำคัญน้อยที่สุด ตัวเลขการสั่งซื้อที่สูงขึ้นคลิกไปที่การเพิ่มความเชื่องช้าbfCodesไม่ประเมินรหัสที่สร้างขึ้นซึ่งจะทำให้ความยาวของสแต็กเป็นศูนย์หรือเป็นลบหรือปล่อยให้มีมากกว่าหนึ่งหมายเลขบนสแต็กเพื่อพยายามเพิ่มประสิทธิภาพความเร็วของการเรียกใช้งาน

ปัญหากำลังดุร้าย

สำหรับชุดโค้ดที่มีอักขระ 15 ตัวจะใช้เวลาในการรันผ่านชุดค่าผสมความยาวที่กำหนดทั้งหมด

T len = 15 * T len-1

ซึ่งหมายความว่าหากโปรแกรมของคุณรันเร็วกว่าของฉันถึงสิบห้าครั้งคุณจะสามารถตรวจสอบสตริงรหัสอักขระเพิ่มเติมได้เพียงหนึ่งรายการในเวลาเดียวกัน ในการตรวจสอบตัวละครอีกสองตัวในเวลาเดียวกันโปรแกรมจะต้องรัน 225 ครั้งเร็วขึ้น เวลาที่ใช้วิธีการเดรัจฉานบังคับเพิ่มขึ้นเป็นทวีคูณเมื่อความยาวของสตริงโค้ดเพิ่มขึ้น และขนาดของตัวเลขนั้นจำเป็นต้องบ่งบอกถึงจำนวนไบต์ที่ต้องใช้เพื่อสร้างมัน

ตัวเลขบางตัว

เวลาโดยประมาณในการสร้างรายการรหัสบนแผ่นจดบันทึก Windows 7 32 บิตสำหรับจำนวนเต็มจนถึง

  • 9: 1 มิลลิวินาที
  • 10: 16 msec
  • 32: 156 มิลลิวินาที
  • 81: 312 msec
  • 93: 18.5 วินาที
  • 132: 28 วินาที

เพื่อสร้าง befunge สำหรับ 3727 (ซึ่งเป็น 66 กำลังสองบวก 6) ด้วยตัวเองใช้เวลา 1 ชั่วโมง 47 นาทีและสร้าง 578*+:*6+

การสร้างรหัสที่เหมาะสมที่สุด

การสร้าง befunge สำหรับตัวเลขโดยไม่ตรวจสอบความยาวที่สั้นที่สุดนั้นค่อนข้างง่าย การใช้อัลกอริทึมแบบเรียกซ้ำซึ่งใช้รากที่สองจำนวนเต็มและส่วนที่เหลือการเข้ารหัสสำหรับตัวเลขที่สูงถึง 132 ใช้เวลาประมาณ 3 มิลลิวินาทีแทนที่จะเป็น 28 วินาที พวกเขาไม่ดีที่สุด เนื่องจากวิธีการทำงานของอัลกอริทึมนี้ผลิตขึ้น638:*-:*+สำหรับ 3727 ในประมาณ 1 มิลลิวินาที (แทนที่จะเป็นหนึ่งชั่วโมงหรือมากกว่านั้น) ซึ่งเกิดขึ้นได้ดีที่สุด

ปัญหาที่เกิดขึ้นจากการให้วิธีการบังคับแบบไม่ดุเดือดกำลังพิสูจน์ว่ามันเหมาะสมที่สุดในทุกกรณี โชคดี!


คุณควรจะสามารถลดเลขชี้กำลังของคุณด้วยจำนวนมากโดยการสังเกตว่าสตริงของคุณจะต้องแสดงแผนภูมิการประเมินที่ถูกต้องด้วย+-*/ที่โหนดด้านใน0-9และ:ที่ใบไม้ (และ:ไม่สามารถซ้ายสุด) ดังนั้นสร้างและประเมินต้นไม้ที่ถูกต้องซึ่งมีขนาด 2 * n + 1 ที่ขั้นตอน n (n เริ่มจาก 0) และแปลงเป็นสตริงเมื่อต้องการ
Ton Hospel

3727 คือ 61 กำลังสองบวก 6, ไม่ใช่ 66 :)
ทิม Vermeulen

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>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.