Account Options

  1. Sign in
The old Google Groups will be going away soon, but your browser is incompatible with the new version.
Google Groups Home
« Groups Home
Permutations: arrow-point-free->>= much faster than list compression?
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  8 messages - Collapse all  -  Translate all to Translated (View all originals)
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
daucl...@gmail.com  
View profile  
 More options Jun 20 2008, 6:35 pm
Newsgroups: comp.lang.haskell
From: daucl...@gmail.com
Date: Fri, 20 Jun 2008 15:35:10 -0700 (PDT)
Local: Fri, Jun 20 2008 6:35 pm
Subject: Permutations: arrow-point-free->>= much faster than list compression?
Dear all,

Working on Project Euler problem #158 (http://projecteuler.net/
index.php?section=problems&id=158), and ran across list/string
permutation on another maillist (http://www.mail-archive.com/
hask...@haskell.org/msg19032.html) that used list compression to form
the permutations:

> perms [] = [[]]
> perms xs = [y : ps | (y,ys) <- selections xs, ps <- perms ys]
> selections []     = []
> selections (x:xs) = (x,xs) : [(y,x:ys) | (y,ys) <- selections xs]

(**** n.b. the function definition in the original post is erroneous!
use this one for a working `perms`)

The `selections` fn looked very much like the splits that Dirk
Thierbach guided me to develop:

> splits list = list >>= return . (id &&& flip delete list)

(note, I use the arrow/pt-free notation, see
http://logicaltypes.blogspot.com/2008/05/composing-functions-with-arr...
and http://storytotell.org/articles/2007/04/08/haskell-arrows, as
opposed to Dirk's implementation at
http://groups.google.com/group/comp.lang.haskell/browse_thread/thread...)

The functions `selections` and `splits` are extensionally equivalent,
and I thought `selections` would have a faster execution time (as it
does no O(n) delete, as it already "knows" both which item was deleted
and the elements of the sublist), but profiling `chooseBy` ...

> chooseBy :: (Ord a, Eq a) => ([a] -> [(a, [a])]) -> Int -> [a] -> [[a]]
> chooseBy f n list = permsoflen n list []
>   where permsoflen n lst accum | n == 0     = [accum]
>                                | otherwise  = do (a,b) <- f lst
>                                                  permsoflen (pred n) b (a:accum)

... shows the opposite:

length $ chooseBy selections 5 ['a'..'z'] => 7893600 in 95.462925s
length $ chooseBy splits 5 ['a'..'z'] => 7893600 in
24.045515s          --- 4x faster!

I'm running GHC 6.8.2.  Is there a strong prejudice toward using
arrows or point-free notation for that compiler?   Does variable
allocation via the standard lambda abstraction (ya know, standard,
"point-full", function definitions) incur a heavy penalty in execution
time? Is it that iteration with >>= is very much faster than the
recursive call of `selections` in the list compression?  Do calls
within list compression not benefit from TCO/LCO (tail/last call
optimisation)?

If not, what makes the arrow-based, point-free `splits` so much faster
than `selections`?


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Danish  
View profile  
 More options Jun 21 2008, 1:42 am
Newsgroups: comp.lang.haskell
From: Matthew Danish <m...@cs.cmu.edu>
Date: Fri, 20 Jun 2008 22:42:13 -0700 (PDT)
Local: Sat, Jun 21 2008 1:42 am
Subject: Re: Permutations: arrow-point-free->>= much faster than list compression?
It's not really anything special about arrows.  splits trivially
reduces to

splits l = map (\ x -> (x, delete x l)) l

FWIW, GHC seems to fire some map related rewrite rules.  And it
inlines splits, too.

Also, both splits and selections are in O(n^2) worst-case time.
splits, of course, runs delete on the list for every element of the
list.  And selections has to cons onto the front of every result list
(up to n of them) at each step (n of them) of recursion.

Other speculation: splits lets you share a lot of list structure in
memory, while selections force you to create entirely new list
structure for each element of the result.  Not sure if this is
relevant.


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Dirk Thierbach  
View profile  
 More options Jun 21 2008, 3:29 am
Newsgroups: comp.lang.haskell
From: Dirk Thierbach <dthierb...@usenet.arcornews.de>
Date: Sat, 21 Jun 2008 09:29:59 +0200
Local: Sat, Jun 21 2008 3:29 am
Subject: Re: Permutations: arrow-point-free->>= much faster than list compression?

daucl...@gmail.com wrote:
> Working on Project Euler problem #158 (http://projecteuler.net/
> index.php?section=problems&id=158), and ran across list/string
> permutation on another maillist (http://www.mail-archive.com/
> hask...@haskell.org/msg19032.html) that used list compression to form
> the permutations:

Hint: For this problem, you're much better off if you don't calculate
the permutations explicitely (unless you want concrete numbers for
small problem sizes to check your formulas). Instead, try to find
a recurrence formula for the result (with memoization), or even
an explicit formula.

I can get the answer nearly instantanously on the toplevel,
without any compilation, even on my old computer.

> Is there a strong prejudice toward using arrows or point-free
> notation for that compiler?

In general, arrows or point-free notation are often potentially slower,
because the compiler has to inline those functions. (There may be
rare cases when optimizations may work better with combinators, when
the compiler has optimization rules that match the particular situation).

But the speed differences you get this way are nothing compared to the
speed differences you get by changing your approach, and using a different
algorithm.

> length $ chooseBy selections 5 ['a'..'z'] => 7893600 in 95.462925s
> length $ chooseBy splits 5 ['a'..'z'] => 7893600 in
> 24.045515s          --- 4x faster!
> If not, what makes the arrow-based, point-free `splits` so much faster
> than `selections`?

I don't know -- optimizing Haskell for speed can be a black art :-)
If you really want to find out, look at the intermediate code GHC
produces.

But as I said, you get a much better speedup if you change your
approach, instead of trying low-level optimizations. Many Project
Euler problems are very good examples for this kind of attitude :-)

- Dirk


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
daucl...@gmail.com  
View profile  
 More options Jun 21 2008, 2:11 pm
Newsgroups: comp.lang.haskell
From: daucl...@gmail.com
Date: Sat, 21 Jun 2008 11:11:06 -0700 (PDT)
Local: Sat, Jun 21 2008 2:11 pm
Subject: Re: Permutations: arrow-point-free->>= much faster than list compression?

Dear Matthew, you wrote:

On Jun 21, 1:42 am, Matthew Danish <m...@cs.cmu.edu> wrote:

> It's not really anything special about arrows.  splits trivially
> reduces to

> splits l = map (\ x -> (x, delete x l)) l

Okay, then, but if we rewrite your fn to:

> matthew l = map (\x -> (x, delete x l)) l

and mine to

> doug list = list >>= return . (id &&& flip delete list)

the profiling of these two functions still shows a difference:

*Problem158> run (length $ chooseBy matthew 5 alf)
"7893600"
24.842459s
*Problem158> run (length $ chooseBy doug 5 alf)
"7893600"
23.781618s

of almost a second, so even a rewrite of the point-free-arrow to point-
full, ehrm, non-arrow fn still incurs a cost.  Diving into more
detail.  If we divide the function into four equivalent functions: 1.
pt-free-arrows, 2. pt-full-arrows, 3. pt-free-non-arrows, 4. mapped
(pt-full-non-arrows), then we have the following definitions:

> arrowsplits, splits, mapSplits, ptSplits :: Eq a => [a] -> [(a, [a])]
> arrowsplits list = list >>= return . ((\x -> x) &&& \y -> delete y list)
> ptSplits list = list >>= return . (join (flip (,) . flip delete list))
> mapSplits list = map (\x -> (x, delete x list)) list
> splits list = list >>= return . (id &&& flip delete list)

(yes, I came up with ptSplits on my own -- "Combinators, they're not
just for breakfast anymore (TM)")

Then the profiling results are the following:

*Problem158> run (length $ chooseBy splits 5 alf)
"7893600"
23.884974s
*Problem158> run (length $ chooseBy arrowsplits 5 alf)
"7893600"
24.130228s
*Problem158> run (length $ chooseBy ptSplits 5 alf)
"7893600"
25.082024s
*Problem158> run (length $ chooseBy mapSplits 5 alf)
"7893600"
24.822053s

which seems to indicate that the pt-free-arrowed function is fastest,
but egregious use of pt-free slows one down the most.  In general, the
arrowed versions are faster than their non-arrowed counterparts, but
strangely, pt-free has different results ... hm, let's see how pt-free
with map stacks up against pt-full map:

> mapArrowFreeSplits list = map (id &&& flip delete list) list
> mapFreeSplits list = map (join (flip (,) . flip delete list)) list

*Problem158> run (length $ chooseBy mapSplits 5 alf)
"7893600"
24.678785s
*Problem158> run (length $ chooseBy mapArrowFreeSplits 5 alf)
"7893600"
23.076796s
*Problem158> run (length $ chooseBy splits 5 alf)
"7893600"
23.893799s
*Problem158> run (length $ chooseBy mapFreeSplits 5 alf)
"7893600"
23.832586s

Hm, it seems to like map (with pt-free and arrow notation) wins over

>>= . return.

Given all these data, it seems that there is a preference for
(moderate) pt-free notation combined with arrows, or, generally,
arrows over non-arrows.  So, can it then be deduced:

"I feel the need ... the need for SPEED! [Copyright (c) 1986, Top Gun"
"Well, then, use Arrows, it's GOOD for you(TM)"

or are there other, or no, conclusions to be drawn from the above?


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
daucl...@gmail.com  
View profile  
 More options Jun 21 2008, 2:54 pm
Newsgroups: comp.lang.haskell
From: daucl...@gmail.com
Date: Sat, 21 Jun 2008 11:54:37 -0700 (PDT)
Local: Sat, Jun 21 2008 2:54 pm
Subject: Re: Permutations: arrow-point-free->>= much faster than list compression?
On Jun 21, 3:29 am, Dirk Thierbach <dthierb...@usenet.arcornews.de>
wrote:

> daucl...@gmail.com wrote:
> > Working on Project Euler problem #158 [...]
> Hint: For this problem, you're much better off if you don't calculate
> the permutations explicitely (unless you want concrete numbers for
> small problem sizes to check your formulas). Instead, try to find
> a recurrence formula for the result (with memoization), or even
> an explicit formula.

> I can get the answer nearly instantanously on the toplevel,
> without any compilation, even on my old computer.

Actually, I did get the answer right away, if one were to read the
problem literally, ignoring the example case, they say n <= 26, for
letters take n (permute ['a' .. 'z']) for a string length n.  This, of
course, is 67108837, from A000295, see http://www.research.att.com/~njas/sequences/A000295.
N.B. they use the same exact n to describe number of different
characters allowed from the alphabet as they do to bind the length of
the string.  A string of length 5, therefore, has ONLY 5 allowable
different characters in its alphabet to choose from.

But, no, no matter what the specification is as written, Project Euler
seems to wish us to use 26 characters for the alphabet, no matter how
the length of the string is constrained, because it does not accept
any number in the A000295 sequence as the answer.

I'm not happy about this turn of events, but I suppose someday after I
get the other "correct" answer (meaning, "the wrong answer to the
problem as stated, but the 'right' answer to what they wanted the
problem to mean, but weren't precise or careful enough to write
correctly"), I'll grow into a transcendent state of acceptance about
it.

Or, someday, I'll just chill out enough not to allow it to bother me
anymore.

... but I doubt it.

> I don't know -- optimizing Haskell for speed can be a black art :-)
> If you really want to find out, look at the intermediate code GHC
> produces.

ick. No thanks (that's what compilers are for, right?).  At any rate,
in general, GHC is blazingly fast, comparable to low-level C code, and
the benefit is one can write Haskell, qua Haskell, and oftentimes come
out with a fast-enough solution.  So, if I need fast code, I don't
need to resort to writing things in C (or assembler, as I seen done on
some projects).

Who can I shake hands with in thanks for this gift?

The same can be said for other programming languages, ML, Lisp,
Scheme, Dylan, Prolog, Mercury, Clean, Erlang.  All fast, all
declarative.  To all, thanks!

> But as I said, you get a much better speedup if you change your
> approach, instead of trying low-level optimizations. Many Project
> Euler problems are very good examples for this kind of attitude :-)

Yes, despite my spiteful rant above about the wording of problem 158,
I like these problems because there's very little hand-holding
involved, so it sends the investigator on a mathematical journey of
discovery.  Nice.  There are some problems that currently befuddle me
completely, which makes me yearn for a hints link of some sort, but
that's just weakness on my part, because real-world problems usually
don't come with a whispering muse or guru ...

Your post reminds me of a "fastest programming language" contest that
went on in some mail list a few years ago -- "How many hands does 3
kings beat in a game of poker?"  Of course, most everyone set up 5
loops and compared hands to hands to come up with an answer, so all
results were in the more-than-one-minute category.  One Smalltalker
used Pascal's triangle to solve the problem in sub-second time.  He
never crowed "Smalltalk wins", nor even entered the contest (it was
all, "C++ is better" "No, Java is better", etc), but he made mention
of it on the Smalltalk mail list as an aside, showing that good
mathematics handily beats the brute force approach.


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Danish  
View profile  
 More options Jun 21 2008, 4:35 pm
Newsgroups: comp.lang.haskell
From: Matthew Danish <m...@cs.cmu.edu>
Date: Sat, 21 Jun 2008 13:35:02 -0700 (PDT)
Local: Sat, Jun 21 2008 4:35 pm
Subject: Re: Permutations: arrow-point-free->>= much faster than list compression?
I based my translation on the following simple facts about Monads,
Functors, and Arrows:

(1) Relationship between fmap and bind: x >>= (return . f) = fmap f x

(2) fmap = map -- when used on Lists

(3) f &&& g = \ x -> (f x, g x) -- on the Function Arrow a.k.a. the (-

>) Arrow

I think you have missed an important point with (1): that if you find
yourself writing ">>= return" then you should think about using fmap
instead.  The other two points serve to show that your arrow code was
a simple map.  [1]

I would also like to correct your definition of "point-free:" none of
the functions you wrote are point-free.  This is the point-free
version of splits:

splits :: Eq a => [a] -> [(a, [a])]
splits = join (map . (ap (,) . flip delete))
-- or
splits = (ap (,) . flip delete) >>= map

but note that this use of join or >>= is in the ((->) a) Monad, not
the list one.  It's essentially just making sure that the input is
duplicated and supplied to both map and delete.

There is no explicit variable binding, unlike your examples which
always bind 'list'.  Also note that the type signature here is
mandatory, or else you run into the monomorphism restriction because
"splits =" is, at face value, syntactically a constant and constants
are not allowed to have type-class polymorphic types inferred.

Also, I'm not suggesting you USE this point-free version, just
demonstrating it.  As you have probably already discovered to some
extent, point-free code is not always the most readable code.

Finally, I wouldn't count the differences in performance that you
displayed as significant without further investigation.  A difference
of one second within 24 seconds could be within the margin of error.
Did you run it multiple times and get very similar results?  In the
overall scheme of things, one second here or there in a long-running
program is not something I would sacrifice readability to gain.  It
could easily be due to peculiarities in the GHC optimizer or even the
low-level architecture which favor certain orderings of instructions,
memory hits, or whatnot.  That can change from version to version, or
hardware to hardware.

In summary, I'd say the last bit of advice is simply a longer version
of "premature optimization is the root of all evil."

[1] In fact, (&&&) doesn't appear like that in GHC, it is derived from
other Arrow operators -- but presumably those are folded by the
compiler.


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
daucl...@gmail.com  
View profile  
 More options Jun 21 2008, 7:58 pm
Newsgroups: comp.lang.haskell
From: daucl...@gmail.com
Date: Sat, 21 Jun 2008 16:58:51 -0700 (PDT)
Local: Sat, Jun 21 2008 7:58 pm
Subject: Re: Permutations: arrow-point-free->>= much faster than list compression?
Dear Matthew, thanks for your reply,

On Jun 21, 4:35 pm, Matthew Danish <m...@cs.cmu.edu> wrote:

> Finally, I wouldn't count the differences in performance that you
> displayed as significant without further investigation.  A difference
> of one second within 24 seconds could be within the margin of error.
> Did you run it multiple times and get very similar results?

Yes, these are over (many) multiple runs.

> In the
> overall scheme of things, one second here or there in a long-running
> program is not something I would sacrifice readability to gain.

I agree.

>  It
> could easily be due to peculiarities in the GHC optimizer or even the
> low-level architecture which favor certain orderings of instructions,
> memory hits, or whatnot.  That can change from version to version, or
> hardware to hardware.

So you are then saying that GHC favors (semi) pt-free-arrowed code
over other styles?  I'm running the code on a Mac, if you have a PC,
would you try running the system and seeing if the fallout is the
same?  If so, that's a stronger case that its a GHC preference.

> In summary, I'd say the last bit of advice is simply a longer version
> of "premature optimization is the root of all evil."

Heh.  Well said, sir:

|Optimization can reduce readability and add code that is used only to
improve the performance. |This may complicate programs or systems,
making them harder to maintain and debug. As a result, |optimization
or performance tuning is often performed at the end of the development
stage.
|
|Donald Knuth said
|
|    * "We should forget about small efficiencies, say about 97% of
the time: premature |optimization is the root of all evil." (Knuth,
Donald. Structured Programming with go to |Statements, ACM Journal
Computing Surveys, Vol 6, No. 4, Dec. 1974. p.268.)

citation: http://en.wikipedia.org/wiki/Optimization_(computer_science)

However, I find my "optimization" of

> list >>= return . (id &&& flip delete list)

to be just as readable (more readable, to my mind) as the others, and
in my preferred style (I visualize the data flow better with >>= than
with map, I was aware of the >>=/fmap relation when Monad m => m a is
also a Functor), so, in this case, Knuth's admonishment does not apply.

 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Dirk Thierbach  
View profile  
 More options Jun 22 2008, 2:55 am
Newsgroups: comp.lang.haskell
From: Dirk Thierbach <dthierb...@usenet.arcornews.de>
Date: Sun, 22 Jun 2008 08:55:05 +0200
Local: Sun, Jun 22 2008 2:55 am
Subject: Re: Permutations: arrow-point-free->>= much faster than list compression?

daucl...@gmail.com wrote:
> Dirk Thierbach <dthierb...@usenet.arcornews.de> wrote:
> N.B. they use the same exact n to describe number of different
> characters allowed from the alphabet as they do to bind the length
> of the string.  A string of length 5, therefore, has ONLY 5
> allowable different characters in its alphabet to choose from.

No. They say "We now consider strings of n <= 26 different characters
from the alphabet." In other words, you look at a string consisting of
n different characters (which then of course has length n), and you're
guaranteed that the "interesting" n are always less than 26 (because
that's the number of characters you can choose from).

Yes, the wording isn't particularly clear, which really shouldn't happen.

> But, no, no matter what the specification is as written, Project Euler
> seems to wish us to use 26 characters for the alphabet, no matter how
> the length of the string is constrained, because it does not accept
> any number in the A000295 sequence as the answer.

Yes. Otherwise the answer would be too easy. :-)

>> I don't know -- optimizing Haskell for speed can be a black art :-)
>> If you really want to find out, look at the intermediate code GHC
>> produces.
> ick. No thanks (that's what compilers are for, right?).

Yes. But that's the only way you'll really understand what's going on.
*If* you are interested (I am usually not :-).

> Yes, despite my spiteful rant above about the wording of problem 158,
> I like these problems because there's very little hand-holding
> involved, so it sends the investigator on a mathematical journey of
> discovery.  

Though sometimes brute force solves it, and then you can read
afterwards in the forum how you should have really done it. That's
really a pity, there should be some way to put you on the right track
without that.

And for some problems even the really smart people there find no
solution other than essentially brute force (though with some
programming tricks).

> Your post reminds me of a "fastest programming language" contest that
> went on in some mail list a few years ago -- "How many hands does 3
> kings beat in a game of poker?"  

Hadn't heard of this :-)

> showing that good mathematics handily beats the brute force
> approach.

Yep.

- Dirk


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
End of messages
« Back to Discussions « Newer topic     Older topic »