0

I need to call this perms function in this nested way:

e = [1,3,7,10, 25,50]
f = perms (perms (perms (perms (perms (perms [[]] e) e) e) e) e) e
g = [[]] ++ f -- final set of permutations 

perms :: (Eq a, Ord a) => [[a]] -> [a] -> [[a]] 
-- set [a] is a set of existing permutations and set [b] are the elements to add to [a] in all possible permutations
perms xss      [] = xss  -- no unique elements to create new permutations from
perms [xs]     ys = [xs ++ [y] | y <- ys \\ xs ]                         -- last list of xs in list xss 
perms (xs:xss) ys = [xs] ++ [ xs ++ [y] | y <- ys\\xs ] ++ perms xss ys 

In short I'm wondering if there is a function like a fancy use of map with (.) or something which can do six of these nested calls for me in a cleaner syntax than:

f = perms (perms (perms (perms (perms (perms [[]] e) e) e) e) e) e

I'm confident I could add another Int argument to the perms function for counting through the P(n, r) for r <- [1..6] but that's not of interest to me here. I'm wondering how I can do 6 nests of the same function calling itself recursively without the literal nesting?

But maybe it comes down to a bad choice of recursion design in my function and whole approach needs to be scrapped for something better?


Background:

This is me trying to work out a part of the Countdown game solution finder posited in this Graham Hutton Introduction to Haskell lecture. (His solution here which is more elegant I'm sure)

Using the Data.List library permutations function doesn't work, because in addition to the (n=720) 6-element permutations, I need the (720) 5-element permutations, the (360) 4-element permutations, the (120) 3-element permutations …, the (6) 1 element permutations and a null set solution. Because in this game you can use as many or few of the 6 numbers randomly selected as you wish.

I looked at the source code for permutations on hackage.org and it's a bit beyond my comprehension. So I thought I'd better try rolling this one for myself.

This is Hutton's solution for finding the choices of number permutations which I've only just chosen to look at.

-- Combinatorial functions

subs :: [a] -> [[a]]
subs []     = [[]]
subs (x:xs) = yss ++ map (x:) yss
              where yss = subs xs

interleave :: a -> [a] -> [[a]]
interleave x []     = [[x]]
interleave x (y:ys) = (x:y:ys) : map (y:) (interleave x ys)

perms :: [a] -> [[a]]
perms []     = [[]]
perms (x:xs) = concat (map (interleave x) (perms xs))

choices :: [a] -> [[a]]
choices = concat . map perms . subs
wide_eyed_pupil
  • 3,153
  • 7
  • 24
  • 35
  • 1
    "interleave" has a more or less accepted meaning of "weaving", it is `[[a]] -> [a]` or at the very least `[a] -> [a] -> [a]`. – Will Ness May 17 '21 at 13:19
  • thanks. I'll deconstruct his code when I get a chance to make sure I understand how it's doing what it does. The source code for `permutations` also uses an interweave internal function but it was confusing to me what it was doing. – wide_eyed_pupil May 18 '21 at 05:05

4 Answers4

4

I would use iterate, as in

iterate (flip perms e) [[]] !! 6
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • why? it is much less *self-evidently* correct -- one needs to know that indexing is 0-based, and that `iterate` keeps the initial as the head of the result -- than `replicate` which just plainly says what it does. (OTOH if OP wants all the results for 1..6 repetitions, then of course `iterate` is the ticket). – Will Ness May 17 '21 at 14:42
  • 1
    @WillNess Funny. For me, this is much *more* self-evidently correct. With `foldr`, I must simulate in my mind what this does for a few small lists before I can understand that, ah!, the `foldr` is just applying one function to another's output. And then the replicate specializes that to iterating a single function... and then I start to wonder, why did they write this custom recursion instead of using `iterate`? Which makes me double-guess whether something clever is happening in the fold that I missed... – Daniel Wagner May 17 '21 at 15:31
  • Hmm. I got accustomed to foldr so much by now, I _just know_ it makes a chain of nested applications of the reducer function (which is exactly what OP wanted). and `replicate` shows me on what, and with how many --- that's what I was referring to, the possible off-by-one error. --- back to `foldr`, yes, _any_ foldr is just applying functions. I had this realization once that ``foldr f z = foldr ($) z . map f = ($ z) . foldr (.) id . map f = (`appEndo` z) . foldMap (Endo . f)`` so I don't need to think about it anymore. – Will Ness May 17 '21 at 15:48
  • I don't understand ($) so I looked at the source and it said `f $ x = f x ` — okay great why use it? Hackage: "Application operator. This operator is redundant, since ordinary application (f x) means the same as (f $ x). However, $ has low, right-associative binding precedence, so it sometimes allows parentheses to be omitted; for example: `f $ g $ h x = f (g (h x))` It is also useful in higher-order situations, such as `map ($ 0) xs`, or `zipWith ($) fs xs`. Note that ($) is levity-polymorphic in its result type, so that `foo $ True where foo :: Bool -> Int#` is well-typed." – wide_eyed_pupil May 18 '21 at 04:46
  • @wide_eyed_pupil "why use it?" because what would we write as the foldr operator, `foldr (...) .....`, nothing? empty space? :) "levity" if you don't use the "magic hash" (like in `Int#`) in your code, you don't need to concern yourself with it. – Will Ness May 18 '21 at 10:02
2

I think you are looking for foldl, aren't you?

foldl perms [[]] [e,e,e,e,e,e] == perms (perms (perms (perms (perms (perms [[]] e)e)e)e)e)e

Or if e is fixed, you may use replicate as well

foldl perms [[]] (replicate 6 e) == perms (perms (perms (perms (perms (perms [[]] e)e)e)e)e)e

So in your case it would work like that:

Prelude> take 10 $ foldl perms [[]] (replicate 6 e)
[[1],[1,3],[1,7],[1,10],[1,25],[1,50],[1,3],[1,3,7],[1,3,10],[1,3,25]]
radrow
  • 6,419
  • 4
  • 26
  • 53
2

Here's a way:

> foldr ($) [[]] (replicate 1 $ flip perms e)
[[1],[3],[7],[10],[25],[50]]

> foldr ($) [[]] (replicate 2 $ flip perms e)
[[1],[1,3],[1,7],[1,10],[1,25],[1,50],[3],[3,1],[3,7],[3,10],[3,25],[3,50],
 [7],[7,1],[7,3],[7,10],[7,25],[7,50],[10],[10,1],[10,3],[10,7],[10,25],
 [10,50],[25],[25,1],[25,3],[25,7],[25,10],[25,50],[50,1],[50,3],[50,7],
 [50,10],[50,25]]

> foldr ($) [[]] (replicate 6 $ flip perms e)
...............

How does it come about? You have e.g.

f3 =                 perms   ( perms   ( perms  [[]]  e )  e )  e
   =                 perms' e ( perms' e ( perms' e  [[]] ))
   =                 perms' e $ perms' e $ perms' e $ [[]]
   = foldr ($) [[]] (perms' e : perms' e : perms' e : [])
   = foldr ($) [[]] (replicate 3 (perms' e))
   = foldr ($) [[]] (replicate 3 $ flip perms e)

perms' e xs = perms xs e
            = flip perms e xs
perms' e    = flip perms e
perms'      = flip perms

and then we abstract the 3 away.

So it's more or less just syntax. (well, not really, but close enough).

Of course we could also treat the perm' itself as the operator,

f3 =                    perms   ( perms   ( perms  [[]]  e )  e )  e
   =                    perms' e ( perms' e ( perms' e  [[]] ))
   =                    e `perms'` e `perms'` e `perms'` [[]]
   = foldr perms' [[]] (e    :     e    :     e    :     [])
   = foldr (flip perms) [[]] (replicate 3 e)
   = foldl perms [[]] (replicate 3 e)

which is even shorter.

Incidentally the very last, foldl snippet, is one rare example of the situation where foldl, and not foldl', is fine to be used. It is used to build the nested computational structure first (of the nested perms calls), which is then run through the lazy evaluation of Haskell.

I was focusing on the structure of the code when I used the ($) itself, which isn't present in the last snippet, only implied.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • Ah, replicate! Any reason why `foldr` rather than `foldl` — I'm still only just getting my head into folds, I still need to do many exercises to be confident in using fold and foldr. – wide_eyed_pupil May 17 '21 at 13:10
  • 1
    I was just translating your code, aiming for its *syntactical* equivalent. – Will Ness May 17 '21 at 13:13
1

Using the Data.List library permutations function doesn't work, because in addition to the (n=720) 6-element permutations, I need the (720) 5-element permutations, the (360) 4-element permutations, the (120) 3-element permutations …, the (6) 1 element permutations and a null set solution. Because in this game you can use as many or few of the 6 numbers randomly selected as you wish.

I'd much rather use permutations and then improve its result than reimplement the whole thing myself, not least because permutations will be implemented more efficiently than I would have thought to do it. In this case it's very easy to do for a general list, without needing to use its length explicitly. Of course this does not solve your direct question ("how to compose a function with itself N times"), but I think it's a much better solution to your real goal ("find permutations of subsets of a set").

import Control.Monad (filterM, (<=<))
import Data.List (permutations, tails)

subsets :: [a] -> [[a]]
subsets = filterM (const [False, True])

permutationsOfSubsets :: [a] -> [[a]]
permutationsOfSubsets = permutations <=< subsets

First we find all the subsets of the input list (setting in advance which numbers we will actually use), and then for each subset we try all its orderings.

*Main> permutationsOfSubsets [1,2,3]
[[],[3],[2],[2,3],[3,2],[1],[1,3],[3,1],[1,2],[2,1],[1,2,3],[2,1,3],[3,2,1],[2,3,1],[3,1,2],[1,3,2]]
*Main> length . permutationsOfSubsets $ [1..6]
1957

Observe that it exactly meets your specifications for a small list. For the full set of 6 numbers, it comes up with 1957 possibilities. Is that correct?

*Main> factorial n = product [1..n]
*Main> n `nPr` r = factorial n `div` factorial (n - r)
*Main> sum . map (6 `nPr`) $ [0..6]
1957

Yes, there are exactly 1957 ways to choose an ordered set of 0 or more elements from a population of 6.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • thanks, this was a learning exercise, not "best practice". learning to crawl before I attempt to walk. – wide_eyed_pupil May 19 '21 at 03:32
  • running some tests, my method does produce duplicates so that's a another problem for me to consider. I think your reasoning is sound, except when considering the number of ordered sets, do we need to consider the null element, because that terminates the expression these elements get used in. For example the first element in any given permutation can be `[ ]` or `a!!0…a!!5` that's 7 choices not 6. then the second slot can be one of `[ ]` or `a!!0..5` less whatever is in slot 1 of this permutation. so it's `7x6x5x4x3x2 = 7!/(7-6)! = 5040` solutions by my reckoning. – wide_eyed_pupil May 19 '21 at 03:56
  • 1
    I don't really understand what you're saying about the null element at all. Valid permutations of `[1,2,3]` include `[2,1]`, not `[2,1,[]]` or something weird like that. – amalloy May 19 '21 at 04:20
  • I was wrong, I missed the fact that your field of possibilities was [0..6] which is 7 possibilities not 6. – wide_eyed_pupil May 20 '21 at 03:16
  • I removed the duplicates from the output of my perms function and indeed there are `1951` elements in the list. – wide_eyed_pupil May 20 '21 at 03:27
  • 1951, not 1957? What 6 elements does my solution produce that yours doesn't? – amalloy May 20 '21 at 04:37