I am new to functional programming. Just now I solved my first "real world" task using Haskell and now I'm wondering whether I got it "right" and what can be improved.
The problem arises from tensor calculations (I am a theoretical physicist), where I have the tensor product of \$\frac{n}{2}\$ identical symmetric tensors and I am looking for all indistinguishable distributions of \$n\$ indices, i.e. terms like \$\eta^{a c} \eta^{b e} \eta^{d f}\$.
The combinatorics is easy, there are \$(n-1)\times(n-3)\times\dots\times1\$ different terms and prescriptions to enlist them all are easy to conceive.
I implemented the following algorithm:
input: string of even length ("indices")
output: rose tree representing all permutations as described above, a node is a pair (my implementation: two-element list) of indices, each branch from root node to leaf is one permutation
recursive construction:
From string of length \$n\$ (example: "abcdef"
), construct \$n-1\$ nodes and "remainders", where the first index of each node is the first index of the input string (example: [("ab", "cdef"), ("ac", "bdef"), ("ad", "bcef"), ("ae", "bcdf"), ("af", "bcde")]
). Repeat with all "remainders".
Now this is my implementation in Haskell:
import Control.Applicative
import Data.Tree
--- given string of n indices a:is,
--- construct all (n-1) two-character
--- strings a:i:[] and the reduced
--- string of (n-2) remaining indices
--- undefined if n == 1
innerEtas :: String -> [(String, String)]
innerEtas (x:[]) = undefined
innerEtas (x:xs) = map (\a -> ([x, a], filter (/=a) xs)) xs
innerEtas [] = []
--- build up tree by recursive application
--- of innerEtas
buildEtas :: String -> Forest String
buildEtas [] = []
buildEtas xs = let ys = innerEtas xs
in map (\(node, list) -> Node node $ buildEtas list) ys
--- flatten Tree to list of strings
treeToStrings :: Tree String -> [String]
treeToStrings (Node n []) = [n]
treeToStrings (Node n ts) = liftA2 (++) [n] (forestToStrings ts)
forestToStrings :: Forest String -> [String]
forestToStrings ts = concat $ map treeToStrings ts
--- build ansatz tree from n indices
ansatzEtas :: Integer -> Forest String
ansatzEtas n
| n < 0 = undefined
| n `mod` 2 /= 0 = undefined
| otherwise = buildEtas $ take (fromIntegral n) ['a'..'z']
main = do
putStrLn $ drawForest $ ansatzEtas 8
print $ forestToStrings $ ansatzEtas 8
print $ (forestToStrings $ ansatzEtas 26) !! 10000
I am really impressed by Haskells lazy evaluation, which in the last line allows me to obtain certain combinations of ['a'..'z']
without first calculating them all! So I believe I got this aspect right.
So my question is, did I make any style errors? Did I miss idioms of functional programming and Haskell which would make the code better in some ways? Are there issues with space or time?