3

Consider the following program.

chars = [" "] ++ ["A"] ++ ["B"] ++ (repeat "ABCD")

f :: Int -> [(Char,Int)]
f n = (,) <$> (chars !! n) <*> [1..3]

g :: Int -> [[(Char,Int)]]
g 1 = (\a     -> [a    ]) <$> (f 1)
g 2 = (\a b   -> [a,b  ]) <$> (f 1) <*> (f 2)
g 3 = (\a b c -> [a,b,c]) <$> (f 1) <*> (f 2) <*> (f 3)
-- g n = (\x1 x2 ... xn -> [x1,x2,...,xn]) <$> (f 1) <*> (f 2) <*> ... (f n)

How can we write g n generally for all n > 0, without typing out the expansion explicitly as above, ideally only using Prelude (and Control.Applicative if necessary)? Note that f n = f (n-1) for all n>3, hence it may be possible to define g recursively.

The output is like this (ignore the pretty printing):

> g 1
[ [ ( 'A' , 1 ) ] , [ ( 'A' , 2 ) ] , [ ( 'A' , 3 ) ] ]

> g 2
[ [ ( 'A' , 1 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 3 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 2 ) , ( 'B' , 3 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 1 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 2 ) ]
, [ ( 'A' , 3 ) , ( 'B' , 3 ) ]
]

> g 3
[ [ ( 'A' , 1 ) , ( 'B' , 1 ) , ( 'A' , 1 ) ]
, [ ( 'A' , 1 ) , ( 'B' , 1 ) , ( 'A' , 2 ) ]
...
, [ ( 'A' , 3 ) , ( 'B' , 3 ) , ( 'D' , 3 ) ]
]
Will Ness
  • 70,110
  • 9
  • 98
  • 181
hyiltiz
  • 1,158
  • 14
  • 25
  • I don't know if this is possible, but practically, do you really *need* to handle all `n > 0`? At `n = 10` there's going to be close to 4 billion elements in that list. At `n = 15`, there's going to be almost 1E+15 elements. – Mark Seemann Dec 24 '19 at 15:53
  • As written, `f i` returns the same value for all `i >= 3`. – chepner Dec 24 '19 at 16:01
  • I am writing a solver for a 2D "rubik's" cube game with this. It should be possible to solve it/strategically simplify the solution process with group theory. Here I am brute-forcing a group that swaps 1 with 2. Hopefully it doesn't require over `n>8`. Game here: https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/twiddle.html. – hyiltiz Dec 26 '19 at 19:27

3 Answers3

5
g n = traverse f [1 .. n]

traverse is in the Prelude (at least for the last few years).

As that's a bit non-obvious, here's how I got there:

  1. I noticed you were applying f to the numbers from 1 to n, so I started with map f [1 .. n].

  2. This produced a [[(Char, Int)]], which is the desired result type, but it needs to be sort of... turned sideways and multiplied. You want all the lists that come from non-deterministic selection of values in the inner lists. Non-deterministic selection is the essence of the Applicative instance for [], and it turns out that sequence on something of type [[a]] is exactly the operation "produce all the lists you get from combinatorially combining elements from the inner lists". This got me to sequence $ map f [1 .. n].

  3. But the pair of sequence and map is common enough that there's an operation that does both at once. sequence . map f === traverse f. So applying that rule simplified the result. traverse f [1 .. n].

Carl
  • 26,500
  • 4
  • 65
  • 86
  • Not sure how I missed this. This is way better than my answer. – Joseph Sible-Reinstate Monica Dec 24 '19 at 19:00
  • Thanks for the detailed explanation! I played with the list of `Prelude` functions, including both `sequence` and `traverse` (and some others), but didn't have the insight to see something that simple! – hyiltiz Dec 24 '19 at 20:33
  • `(sequence .) . map` is `mapM` actually, which is a clearer name (IMO:YMMV), way shorter to type, and `[]` is a Monad anyway. :) – Will Ness Dec 25 '19 at 08:02
  • (what I forgot to add is that it's with `sequenceA` that it's `traverse` -- what you undoubtedly know, but I note this for the sake of a casual traveler). – Will Ness Dec 26 '19 at 07:20
1
g n = mapM f [1..n]

How to get there.

Your examples are equivalently written with List Comprehension syntax as

g :: Int -> [[(Char,Int)]]
g 1 = [[a    ] | a <- (f 1)]
g 2 = [[a,b  ] | a <- (f 1), b <- (f 2)]
g 3 = [[a,b,c] | a <- (f 1), b <- (f 2), c <- (f 3)]
-- g n = [[a,b,c, ... , z] | a <- (f 1), b <- (f 2), ... , z <- (f n)]

which is exactly how sequence is defined, in Monad Comprehension syntax:

sequence [as,bs,cs, ... , zs] = [[a,b,c, ... , z] | a <- as, b <- bs, ... , z <- zs]

and thus

g n = sequence [(f 1), (f 2), ... , (f n)]
    = sequence $ map f [1..n]

which is, in its turn, the definition (or an equivalent thereof) of mapM.

(mapM is also known as traverse now, for types which are also Applicatives, as well as Monads. Haskell lists [] are both, anyway, and I find the name mapM clearer and shorter to type).


This has been a public service announcement in favor of the underappreciated List Comprehension and Monad Comprehension syntax.

I'm actually fine with g n = sequence $ map f [1..n], and don't feel any particular urge to shorten it any further, as I find it clearer than both alternatives.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • I did start with the list comprehension syntax as yours, and slowly made my way into the `<*>` syntax, and was thinking of something like `fmap (.) fmap` composition to conjure up `a <*> b <*> ...` but couldn't get it to work. Anyway, thx for the explanation! – hyiltiz Dec 26 '19 at 02:36
  • you're most welcome. :) the applicative syntax `...<$>...<*>...<*>...` comes as a substitute for the missing general `liftAn`, and MonadComprehensions can be another alternative for that (the *brackety* syntax; as in the original paper). What's missing is the ability for MonadComprehension to be translated as an Applicative-based code, when possible. I have [several posts](https://stackoverflow.com/search?q=user%3A849891+essentially+Monadic) about what makes a computation *essentially* Monadic. – Will Ness Dec 26 '19 at 07:27
0

Like this:

g 0 = [[]]
g n = (\xs x -> xs ++ [x]) <$> g (n - 1) <*> f n

Or more complicated, but also more efficient:

g = map ($ []) . go
  where
    go :: Int -> [[(Char,Int)] -> [(Char,Int)]]
    go 0 = [id]
    go n = (\xs x -> xs . (x:)) <$> go (n - 1) <*> f n

This uses Hughes lists/difference lists to avoid the quadratic slowdown of \xs x -> xs ++ [x].

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • I did try using `++` in the lambda, but couldn't quite get the recurrent definition of g with itself. Thanks! – hyiltiz Dec 24 '19 at 20:34
  • there's also the simpler option of `g = map reverse . g' where { g' 0 = [[]] ; g' n = [ (x:xs) | xs <- g' (n-1), x <- f n ] }` which might even be a little bit more performant that Hughes', if the latter isn't used with `(\xs x ys -> xs $! (x:ys))` instead of (the equivalent of) `(\xs x ys -> xs ((x:) ys))` -- unless GHC is smart enough to do this on its own. (or maybe I'm way off the mark here; and actually it's all just about the `~ 2n` time vs the `~ 3n` time, so, not that significant anyway :) ) – Will Ness Dec 25 '19 at 09:21