Wyjaśnienie
Befunge to dwuwymiarowy program, który wykorzystuje stosy .
Oznacza to, że aby zrobić 5 + 6, piszesz 56+
, co oznacza:
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)
Nie możemy jednak wcisnąć liczby 56
bezpośrednio na stos.
Aby to zrobić, musimy napisać 78*
zamiast, który mnoży 7
i 8
i popycha produkt na stosie.
Detale
Dla każdej liczby od 1
don
, znaleźć ciąg składający się tylko z tych znaków: 0123456789+-*/:
(I byłoby nie wykorzystać %
modulo).
Celem jest znalezienie najkrótszego ciągu, który może reprezentować liczbę, przy użyciu formatu opisanego powyżej.
Na przykład, jeśli dane wejściowe to 123
, dane wyjściowe będą67*9:*+
. Dane wyjściowe należy oceniać od lewej do prawej.
Jeśli istnieje więcej niż jeden akceptowalny wynik (np 99*67*+
Jest również akceptowalny), można wydrukować dowolny z nich (bez premii za wydrukowanie wszystkich).
Dalsze wyjaśnienia
Jeśli nadal nie rozumiesz, jak 67*9:*+
oceniać 123
, oto szczegółowe wyjaśnienie.
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
Program musi znaleźć najkrótszy ciąg znaków, który może reprezentować dane wejściowe (liczbę), przy użyciu formatu określonego powyżej.
PUNKTACJA
- Zrobiliśmy to już przy użyciu jak najmniejszej ilości kodu . Tym razem rozmiar nie ma znaczenia.
- Twój wybrany język musi mieć darmowy kompilator / tłumacz dla mojego systemu operacyjnego (Windows 7 Enterprise).
- Bonus, jeśli podasz link do kompilatora / tłumacza (jestem zbyt leniwy).
- Jeśli to możliwe, proszę podać licznik czasu dla mojej wygody. Dane wyjściowe z timera są prawidłowe.
- Wynik będzie największy
n
w ciągu 1 minuty. - Oznacza to, że program musi wydrukować wymaganą reprezentację
1
. - Nie trudno kodowania, z wyjątkiem
0
do9
.
(więcej) SPECYFIKACJA
- Program jest nieprawidłowy, jeśli generuje ciąg dłuższy niż wymagany dla dowolnej liczby.
1/0=ERROR
5/2=2
,(-5)/2=-2
,(-5)/(-2)=2
,5/(-2)=-2
Ujednoznacznienie
-
Jest second-top minus top
, co oznacza, że 92-
zyski7
.
Podobnie /
jest second-top divide top
, co oznacza, że 92/
zwraca4
.
Przykładowy program
Lua
Używa wyszukiwania w pierwszej kolejności.
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
źródło
56
bezpośrednio do stosu, jak możemy pchać78
do stosu?56
pięćdziesięciu sześciu bezpośrednio do stosu, ale możemy wcisnąć7
siedem i8
osiem osobno do stosu.56
w Befunge, naciskasz cyfry , więc otrzymujesz stos[5, 6]
. Aby uzyskać liczbę 56, musisz popchnąć7
, a następnie8
na stos, a następnie pomnożyć je, aby uzyskać liczbę 56 na stosie.:
sprawia, że jest to o wiele trudniejsze, dlatego zaleciłbym podanie dobrej listy przypadków testowych, np.86387
Odpowiedzi:
C ++, wybuchając całą pamięcią na komputerze w pobliżu
Generuje najkrótszy ciąg, w którym obliczenia nigdzie nie powodują przepełnienia 32-bitowej liczby całkowitej ze znakiem (więc wszystkie wyniki pośrednie znajdują się w zakresie
[-2147483648, 2147483647]
W moim systemie generuje to rozwiązanie dla wszystkich liczb do
483432
30 sekund włącznie przy użyciu pamięci 1.8G. Nawet wyższe liczby szybko zwiększą zużycie pamięci. Najwyższą liczbą, jaką mogę obsłużyć w moim systemie, jest5113906
. Obliczenie zajmuje prawie 9 minut i 24 GB. Po zakończeniu ma wewnętrznie rozwiązanie398499338
wartości, około 9% wszystkich 32-bitowych liczb całkowitych (dodatnich i ujemnych)Potrzebuje kompilatora C ++ 11. Na Linux-ie skompiluj z:
Dodaj
-DINT64
jako opcję użycia 64-bitowego zakresu liczb całkowitych zamiast 32-bitowego dla wyników pośrednich (zajmie to około 50% więcej czasu i pamięci). To wymaga wbudowanego 128-bitowego typu. Może być konieczna zmiana typu gcc__int128
. Brak wyników w co najmniej zakresie[1..483432]
zmiany przez umożliwienie większych wyników pośrednich.Dodaj
-DOVERFLOW
jako opcję, aby nie używać większego typu liczby całkowitej do sprawdzania przepełnienia. Pozwala to na przepełnienie i zawijanie wartości.Jeśli twój system ma tcmalloc ( https://github.com/gperftools/gperftools ), możesz połączyć się z tym, co powoduje, że program jest na ogół nieco szybszy i zużywa nieco mniej pamięci. W niektórych systemach UNIX można użyć wstępnego ładowania, np
Podstawowe użycie: generuj i drukuj wszystkie liczby do celu:
Opcje:
-a
Wydrukuj również wszystkie liczby, które zostały wygenerowane podczas opracowywania celu-c
Wydrukuj również wszystkie liczby, które zostały wygenerowane, zaczynając od „carry” (dup)-f
Znajdź i wydrukuj pierwszą liczbę poza celem, która nie została wygenerowana-s
Zatrzymaj, jeśli cel zostanie wygenerowany, nawet jeśli nie wszystkie liczby zostały wcześniej wygenerowane-S
Jak-s
iw-f
automatycznej pętli. Po wygenerowaniu celu znajdź pierwszy numer, który nie został jeszcze wygenerowany i uczyń go nowym celem-E
Nie wychodź natychmiast po osiągnięciu celu. Najpierw zakończ wszystkie ciągi bieżącej długości-O
Nie wyprowadzaj ciągów dla wszystkich liczb do celu. tylko ciąg dla celu-o
Dozwolone instrukcje (domyślnie+-*/:
-b num
Najniższy literał, który można przesunąć (domyślnie0
)-B num
Najwyższy literał, który można wcisnąć (domyślnie9
)-r num
Najniższy dozwolony wynik pośredni. Służy do uniknięcia niedomiaru. (domyślnieINT32_MIN
,-2147483648
-R num
Najwyższy dozwolony wynik pośredni. Służy do uniknięcia przepełnienia. (domyślnieINT32_MAX
,2147483647
-m memory
(tylko linux) kończy się, gdy w przybliżeniu tyle pamięci zostanie przydzieloneKilka interesujących kombinacji opcji:
Wygeneruj wszystkie liczby do celu i oblicz najmniejszą liczbę, która potrzebuje dłuższego generatora niż wszystkie te liczby:
Generuj tylko cel (-s), drukuj tylko cel (-O)
Znajdź najwyższą liczbę, jaką można wygenerować w systemie, biorąc pod uwagę ograniczenia czasowe i / lub pamięciowe (spowoduje to brak pamięci w systemie, jeśli pozostawisz go uruchomioną. Odejmij 1 z ostatniego wyjścia „szukającego”, które widzisz jako ostatnią bezpieczną wartość ):
Generuj rozwiązania bez użycia negatywnych wyników pośrednich (
30932
jest to pierwsza wartość, która wymaga negatywnych wyników pośrednich dla najkrótszego ciągu):Generuj rozwiązania bez wypychania
0
(wydaje się, że nie prowadzi to do nieoptymalnych rozwiązań):Generuj rozwiązania, w tym
a..f (10..15)
:Generuj rozwiązania bez użycia dup
:
(dodaj,-r0
ponieważ ujemne wartości pośrednie nigdy nie są interesujące w tym przypadku)Znajdź pierwszą wartość, która nie może być generowany dla danej długości łańcucha przy użyciu tylko
+
,-
,*
i/
:To faktycznie wygeneruje kilka pierwszych haseł https://oeis.org/A181898 , ale zacznie się różnić,
14771
ponieważ używamy podziału obcięcia, dzięki czemu liczba może być wykonana za pomocą łańcucha 13 zamiast długości 15 jako serii OEIS oczekuje:zamiast
Ponieważ bez podziału na obcinanie wydaje się bezcelowe, można lepiej wygenerować serię OEIS za pomocą
Zakładając, że podział pozostaje bezużyteczny, to dało mi 3 dodatkowe warunki, zanim straciłem pamięć:
Inna wersja tego programu przechowująca część danych w plikach zewnętrznych dodaje 135153107 i 675854293, po czym zostały wygenerowane wszystkie 32-bitowe liczby całkowite.
befour.cpp
Niektóre przypadki testowe:
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*:*+
źródło
38950002
twój program daje89*7+:::**1-*
, co jest całkiem dobre, ale możesz zrobić to299*-::*:*+
krócej. Myślę, że to potwierdza wątpliwości dotyczące liczb ujemnych ...int main(int argc, char const* const* argv)
Nie znam C lepiej niż przeciętny Joe, ale co to jest? const wskaźnik do const wskaźnik do char? Nie powinno tak byćchar const *argv[]
(lubchar const **argv
jeśli jesteś taki hardkorowy)?JavaScript Node Brute Force
Plik programu bfCodes.js
Działa w systemie Windows
cmd.exe
cmd.exe
za pomocą skrótu i sprawdź, czy monit DOS zaczyna się od katalogu roboczegoOptymalizacja
Algorytm przełącza wszystkie kombinacje znaków befunge, zaczynając od ciągu kodu o długości 1. Pomyśl o tym jak o obracaniu podstawowego 15-metrowego przebiegu od najmniej znaczącej cyfry. Cyfry wyższego rzędu klikają z rosnącym spowolnieniem.
bfCodes
nie ocenia wygenerowanego kodu, który spowodowałby, że długość stosu byłaby zerowa lub ujemna, lub pozostawiłaby na stosie więcej niż jedną liczbę, próbując zoptymalizować szybkość wykonywania.Problem brutalnej siły
W przypadku zestawu kodów złożonego z 15 znaków czas potrzebny na przejście przez wszystkie kombinacje danej długości jest równy
co oznacza, że jeśli twój program działa piętnaście razy szybciej niż mój, będziesz mógł sprawdzić tylko jeden dodatkowy ciąg znaków w tym samym czasie. Aby sprawdzić dwa kolejne znaki w tym samym czasie, program musiałby działać 225 razy szybciej. Czas potrzebny na brutalne podejście rośnie wykładniczo wraz ze wzrostem długości ciągów kodu. Wielkość liczby niekoniecznie wskazuje liczbę bajtów befunge potrzebnych do jej wygenerowania.
Niektóre liczby.
Przybliżony czas wygenerowania list kodów w 32-bitowym notatniku systemu Windows 7 dla liczb całkowitych do
Samo wygenerowanie befunge dla 3727 (czyli 66 do kwadratu plus 6) zajęło 1 godzinę 47 minut i wygenerowano
578*+:*6+
Optymalne generowanie kodu
Generowanie befunge dla liczb bez sprawdzania najkrótszych długości jest stosunkowo proste. Przy użyciu algorytmu rekurencyjnego, który używał całkowitych pierwiastków kwadratowych i reszt, kodowanie liczb do 132 zajęło około 3 ms zamiast 28 sekund. Nie były optymalne. Ze względu na sposób, w jaki działał ten konkretny algorytm wyprodukowany
638:*-:*+
dla 3727 w około 1 ms (zamiast godziny), który okazał się optymalny.Problem z dostarczeniem metody nie brutalnej siły dowodzi, że jest ona optymalna w każdym przypadku. Powodzenia!
źródło
+-*/
w wewnętrznych węzłach0-9
i:
na liściach (i:
nie może być skrajnie w lewo). Tak więc wygeneruj i oceń wszystkie prawidłowe drzewa o rozmiarze 2 * n + 1 w kroku n (n zaczyna się od 0) i w razie potrzeby przekonwertuj je na ciągi znakówJavaScript
Co można zrobić za pomocą fragmentu kodu JS? Na moim komputerze Firefox 64-bitowy, 416 w 60 sekund
źródło