2015-07-30 11 views
5

Za każdym razem, gdy używam przysłówka J M., wydajność znacznie się pogarsza. Ponieważ podejrzewam, że Iverson i Hui są znacznie mądrzejsi ode mnie, muszę zrobić coś złego.Memoization w J

Weź pod uwagę Collatz conjecture. Wydaje się, że istnieje wiele możliwości zapamiętywania tutaj, ale nie ważne gdzie umieszczam M., wydajność jest straszna. Na przykład:

hotpo =: -:`(>:@(3&*))@.(2&|) M. 
collatz =: hotpo^:(>&1)^:a:"0 
#@collatz 1+i.10000x 

Bez M., to działa w około 2 sekundy na moim komputerze. Z M., cóż, czekałem ponad dziesięć minut, aż to się zakończyło i ostatecznie zrezygnowałem. Ja również umieszczone M. w innych pozycjach z podobnie złych wyników, np

hotpo =: -:`(>:@(3&*))@.(2&|) 
collatz =: hotpo^:(>&1)M.^:a:"0 
#@collatz 1+i.10000x  

Może ktoś wyjaśnić prawidłowego wykorzystania M.?

+0

Czy spojrzałeś na wyjaśnienie Nuvoc na wiki J? Przedstawiono w nim niektóre ograniczenia, które mogą spowolnić proces, ale nie mogą. http://www.jsoftware.com/jwiki/Vocabulary/mcapdot – bob

+0

@bob Nie zdziwi Cię, widząc Cię tutaj. :) Sprawdzę to. –

Odpowiedz

7

M. nic tu dla ciebie nie robi.

Kod buduje łańcuch, jeden link na raz:

-:`(>:@(3&*))@.(2&|)^:(>&1)^:a:"0 M. 5 5 
5 16 8 4 2 1 
5 16 8 4 2 1 

Tutaj pamięta, że ​​5 prowadzi do 16, 16 prowadzi do 8, 8 prowadzi do 4, etc ... Ale co Czy to ci służy? Zastępuje pewną prostą arytmetykę przy pomocy przeszukiwania pamięci, ale arytmetyka jest tak banalna, że ​​prawdopodobnie jest szybsza od wyszukiwania. (Jestem zaskoczony, że twój przykład zajmuje 10 pełnych minut, ale to nie ma sensu.)

Aby poprawić pamięć, należy zastąpić droższe obliczenia.

Dla tego konkretnego problemu, może chcesz funkcję, która pobiera liczbę całkowitą i zwraca 1 jeśli i gdy sekwencja przybywa 1. Na przykład:

-:`(>:@(3&*))@.(2&|)^:(>&1)^:_"0 M. 5 5 
1 1 

Ja tylko wymienić ^:a: z ^:_ , aby odrzucić wyniki pośrednie. Nawet wtedy, to nie robi wielkiej różnicy, ale można użyć timespacex aby zobaczyć efekt:

timespacex '-:`(>:@(3&*))@.(2&|)^:(>&1)^:_"0  i.100000' 
17.9748 1.78225e7 
    timespacex '-:`(>:@(3&*))@.(2&|)^:(>&1)^:_"0 M. i.100000' 
17.9625 1.78263e7 

Uzupełnienie: Umieszczenie M. względem "0 nie wydaje się, aby ogromna różnica. Myślałem, że zrobiłem tam błąd, ale szybki test wykazał, że ich zamiana spowodowała ogromne straty wydajności zarówno w czasie i przestrzeni:

timespacex '-:`(>:@(3&*))@.(2&|)^:(>&1)^:_ M. "0 i.100000' 
27.3633 2.41176e7 

M. zachowuje rangę podstawowej czasownika, więc dwa są semantycznie równoważny, ale podejrzewam, że z "0 na zewnątrz w ten sposób, M. nie wie, że ma do czynienia z skalarami. Więc domyślam się, że tutaj jest pewna, że ​​M. wie, z czym ma do czynienia. :)


BTW, jeśli przypuszczenie Collatz okazały się fałszywe, a karmione ten kod kontrprzykład, to wpada w nieskończoną pętlę zamiast produkować odpowiedź.

Aby rzeczywiście wykryć kontrprzykład, należy monitorować wyniki pośrednie, aż do znalezienia cyklu, a następnie zwrócić najniższą liczbę w cyklu. Aby to zrobić, prawdopodobnie chcesz zaimplementować niestandardowe przysłówek, który zastąpi ^:n.

+0

Parę rzeczy do zaabsorbowania, ale najpierw kilka uwag: Nie sądziłem, że pamiętanie "hotpo" zrobi dla mnie wszystko z tych samych powodów, o których mówisz. Nie powinienem był uwzględniać tego w moim pytaniu. Poza tym interesuje mnie "M.", a nie Collatz (który wybrałem losowo), więc chcę uzyskać wyniki pośrednie. W rzeczywistości, obliczanie ich w sposób performatywny jest swego rodzaju punktem, co próbuję zrobić z 'M.'. Wiele wzorców pośrednich powtarza się, więc zapamiętywanie może mieć potężny efekt. Napisałem podobny algorytm w Haskell i uzyskałem ogromne korzyści dzięki memoizacji. Chcę zrobić to samo w J. –

+0

Na przykład, jeśli spojrzysz na redukcję Collatza dla 33, zawiera ona redukcję Collatza dla 34, chociaż zajmie to trochę czasu. Jeśli pamiętasz o tym, możesz uzyskać bardzo dobrą redukcję czasu (choć potencjalnie nieprzyjemny wzrost przestrzeni). –

+0

Co ciekawe, sekwencja '34 17 52 26 13 40 20 10 5 16 8 4 2 1' kończy nieco ponad 40% sekwencji Collatza dla liczb naturalnych poniżej 10000 (i kto wie, ile z nich), co prawdopodobnie stanowi poprawy wydajności dzięki memoizacji w Haskell. –