2
\$\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\$

migrated from codegolf.stackexchange.com May 19 '15 at 0:57

This question came from our site for programming puzzle enthusiasts and code golfers.

  • \$\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 May 19 '15 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 May 19 '15 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 May 19 '15 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\$ – max taldykin May 19 '15 at 6:39
  • 1
    \$\begingroup\$ Actually even easier, runghc --. \$\endgroup\$ – bisserlis May 19 '15 at 20:22
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, privacy policy and cookie policy

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