Kasey Speakman raised the bar very high very early. :-)
My holidays during the "Kölsche Karneval" in Cologne are over now, so I'll throw one of my implementations in, before the work begins again.
This is the ranking of my attempts, so far:
Python (Slices): 157s
Haskell with singly-linked lists: 51 s (I didn't expect that to be fast because accessing an element by index is of course in O(n))
Java with doubly-linked list with additional state keeping to avoid O(n) indexing: 28 s
Go with doubly-linked list with additional state keeping: 10 s
Go with appending to slices similar to the Haskell deleteEveryNth function: 10 s
Go with array (in-place changes): 6.5 s
Haskell with Boxed Vectors: 3 s
C with array (in-place changes): 2 s
Haskell with Unboxed Vectors: ~600 msec
importControl.Monad.StateimportControl.Monad.Loops(untilM)importqualifiedData.Vector.UnboxedasVimportData.Vector.Unboxed((!))-- this one unqualified, please!typeIndex=InttypeVec=V.Vector-- To delete every nth element from a list:-- The state is the remaining list with elements to delete-- take n elements and leave the rest as statetakeM::V.Unboxa=>Int->State(Veca)(Veca)takeM=state.V.splitAt-- input list in state empty?end::V.Unboxa=>State(Veca)Boolend=get>>=return.V.null-- take n-1 elements and drop the nth. Repeat until the end of the list.deleteEveryNth::V.Unboxa=>Int->Veca->VecadeleteEveryNthn=V.concat.evalState(takeM(n-1)<*takeM1`untilM`end)-- To iterate the deletion passes with different step widths:-- The step width can be found at index i, then do a deletion pass over xsdeleteUnlucky::(VecInt,Index)->(VecInt,Index)deleteUnlucky(xs,i)=(deleteEveryNth(xs!i)xs,i+1)outOfRange::(VecInt,Index)->BooloutOfRange(xs,i)=i>=len||xs!i>lenwherelen=V.lengthxsluckyNumbersn=fst$untiloutOfRangedeleteUnlucky(V.fromList[1,3..n],1)main=letl=luckyNumbers1000000inprint(l,V.lengthl)
The inner loop is deleteEveryNth, which is done by monadic parsing. The combinator <* in the parsing function takeM (n-1) <* takeM 1 means: First take n-1 elements from the input, then take 1, but return only the n-1 taken at first.
The outer loop is done by until outOfRange deleteUnlucky, both functions being quite straight forward.
The code, btw, is logically exactly the same as the version with linked lists, the only difference are the "V."-functions instead of the equally named list functions, especially V.length (O(1)) instead of length (O(n)) and also important, the O(1) index operator for vectors ! instead of O(n) !! for linked lists.
PS C:\Users\Heiko\Documents\Programmieren\Haskell> .\GluecklicheZahlen-Vector.exe +RTS -s
71918
4,586,444,408 bytes allocated in the heap
31,005,008 bytes copied during GC
9,441,112 bytes maximum residency (35 sample(s))
2,704,656 bytes maximum slop
24 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 7189 colls, 0 par 0.016s 0.037s 0.0000s 0.0003s
Gen 1 35 colls, 0 par 0.000s 0.011s 0.0003s 0.0046s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.547s ( 0.536s elapsed)
GC time 0.016s ( 0.048s elapsed)
EXIT time 0.000s ( 0.002s elapsed)
Total time 0.562s ( 0.585s elapsed)
%GC time 2.8% (8.1% elapsed)
Alloc rate 8,386,641,203 bytes per MUT second
Productivity 97.2% of total user, 91.8% of total elapsed
PS C:\Users\Heiko\Documents\Programmieren\Haskell>
I'd like to get to a C implementation nearly as fast as the Haskell- and the F#-version. I don't know why my C is slower. I'll share my attempts soon...
So I pretty much went back and inlined everything in the F# solution and updated my post. Got the time way down. It should very nearly read like especially bad C code if you want to see how it does ported to C.
Your Haskell implementation is quite impressive in performance and terseness.
Great to hear that. I'm afraid I can't fully claim credit for the performance. That seems an achievement of the author of the vector library (Roman Leshchinskiy) and the authors of the Glasgow Haskell Compiler. ;-)
You sped up your program, so I let the compiler inline deleteEveryNthunsing the compiler pragma {-# INLINE deleteEveryNth #-}
I had a run in 469 ms CPU time. :-D (Of course, it all depends on hardware. But I also wanted to do the next step and profit of inlining.)
I need some more time to go through your code to understand and port it to C. Great.
I added a lot of comments. Hopefully that will help with a C translation. I just noticed you are starting with odds whereas I am filtering them out at cost of extra CPU time. I'll update my version tonight to do that also.
I'm still quite in awe of the Haskell version. It is quite expressive while performing the same. I don't understand all the specific functions or operators, but I "get" it in a general sense. Whereas I had to remove any semblance of human thought to run mine faster.
Kasey Speakman raised the bar very high very early. :-)
My holidays during the "Kölsche Karneval" in Cologne are over now, so I'll throw one of my implementations in, before the work begins again.
This is the ranking of my attempts, so far:
deleteEveryNth
function: 10 sThe inner loop is
deleteEveryNth
, which is done by monadic parsing. The combinator<*
in the parsing functiontakeM (n-1) <* takeM 1
means: First take n-1 elements from the input, then take 1, but return only the n-1 taken at first.The outer loop is done by
until outOfRange deleteUnlucky
, both functions being quite straight forward.The code, btw, is logically exactly the same as the version with linked lists, the only difference are the "V."-functions instead of the equally named list functions, especially V.length (O(1)) instead of length (O(n)) and also important, the O(1) index operator for vectors
!
instead of O(n)!!
for linked lists.I'd like to get to a C implementation nearly as fast as the Haskell- and the F#-version. I don't know why my C is slower. I'll share my attempts soon...
So I pretty much went back and inlined everything in the F# solution and updated my post. Got the time way down. It should very nearly read like especially bad C code if you want to see how it does ported to C.
Your Haskell implementation is quite impressive in performance and terseness.
Great to hear that. I'm afraid I can't fully claim credit for the performance. That seems an achievement of the author of the vector library (Roman Leshchinskiy) and the authors of the Glasgow Haskell Compiler. ;-)
You sped up your program, so I let the compiler inline
deleteEveryNth
unsing the compiler pragma{-# INLINE deleteEveryNth #-}
I had a run in 469 ms CPU time. :-D (Of course, it all depends on hardware. But I also wanted to do the next step and profit of inlining.)
I need some more time to go through your code to understand and port it to C. Great.
I added a lot of comments. Hopefully that will help with a C translation. I just noticed you are starting with odds whereas I am filtering them out at cost of extra CPU time. I'll update my version tonight to do that also.
I'm still quite in awe of the Haskell version. It is quite expressive while performing the same. I don't understand all the specific functions or operators, but I "get" it in a general sense. Whereas I had to remove any semblance of human thought to run mine faster.
Turned out starting with odds didn't change the runtime at all really. But it led me to some shorter code that accomplished the same thing.