3
\$\begingroup\$

I was happy with my shell script that needed to generate alphanumeric combinations of length N (in this case, 3)

for i in {a..z}{a..z}{a..z}; do ...

Now I became less happy when I needed alphanumeric combinations (specially if I choose a larger value for N)

for i in {0..9}{a..z}{a..z} \
         {a..z}{0..9}{a..z} \
         {a..z}{a..z}{0..9} \
         {a..z}{a..z}{a..z} \
         {0..9}{0..9}{a..z} \
         {0..9}{a..z}{0..9} \
         {a..z}{0..9}{0..9} \
         {0..9}{0..9}{0..9}; do ...

So I decided to write it in Haskell:

import Data.List
c 0 _ = [[]]
c n xs = [ y:ys | y:xs' <- Data.List.tails xs, ys <- c (n-1) xs']
main = sequence_ . map putStrLn . c 3 $ ['a' .. 'z'] ++ ['0' .. '9']

Now, I couldn't find a way to incorporate the above code in my shell script, so I had to reduce it to a one-liner to run with ghc -e, finally giving me the code I have right now:

for i in $(ghc -e "
  let c n l = if n == 0 then [[]] else \
    [y:s | y:q <- Data.List.tails l, s <- c (n-1) q] in
      sequence_. map putStrLn . c 3 $ ['a'..'z'] ++ ['0'..'9']"); do ...

Now, I actually like this style (elsewhere I have inline calls to awk too, etc). But I think this code is too big and too ugly; for example, I'm using if n == 0 instead of guards, etc. Haskell is supposed to be a lot more concise than that.

My only requirement is that I may want to change the length to be larger than 3, but also ['a'..'z'] ++ ['0'..'9'] to another set (perhaps to include - or _).

\$\endgroup\$
9
  • \$\begingroup\$ Does your code work the way it should as presented? There are ellipsis at the end of two code blocks, which hints that this could be stub code. Please clarify! \$\endgroup\$
    – Phrancis
    Commented May 19, 2015 at 1:09
  • \$\begingroup\$ Yes, the code actually works fine (it's part of a larger script). It's just that I feel that this should be a simpler one-liner; at the very least, I wanted to eliminate the if n == 0 .. else .. by turning the let binding into a where binding, but I couldn't figure out how to write it in one line. \$\endgroup\$
    – darque
    Commented May 19, 2015 at 1:31
  • \$\begingroup\$ PS: this was moved to Code Review by someone else, but the question isn't "is this style fine" but "how could something like this be written in a more concise way" \$\endgroup\$
    – darque
    Commented May 19, 2015 at 1:33
  • 2
    \$\begingroup\$ Is it ok that you shell code and haskell code produce different results? I.e. cross product vs. set of subsequences. \$\endgroup\$ Commented May 19, 2015 at 6:39
  • 1
    \$\begingroup\$ Actually even easier, runghc --. \$\endgroup\$
    – bisserlis
    Commented May 19, 2015 at 20:22

1 Answer 1

1
\$\begingroup\$

In Haskell you can always use ;, see The Haskell Report.

So your original code could be written as an one-liner as follows:

import Data.List ; c 0 _ = [[]] ; c n xs = [ y:ys | y:xs' <- tails xs, ys <- c (n-1) xs'] ; main = mapM_ putStrLn . c 3 $ ['a' .. 'b'] ++ ['0' .. '1']

A small improvement - sequence_ . map f is mapM_ f.

Of course you'll lose the nice indentation.

IF you don't mind repeating elements (which is what you have in the original shell script), you can use replicateM:

import Control.Monad
main = mapM_ putStrLn . replicateM 3 $ ['a' .. 'b'] ++ ['0' .. '1']
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.