diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2025, Henning Thielemann
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Henning Thielemann nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+run-test:	update-test
+	runhaskell Setup configure --user --enable-tests
+	runhaskell Setup build
+	runhaskell Setup test countdown-numbers-test --show-details=streaming
+
+update-test:
+	doctest-extract-0.1 -i src/ -o test/ --executable-main=TestMain.hs Solve
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/countdown-numbers-game.cabal b/countdown-numbers-game.cabal
new file mode 100644
--- /dev/null
+++ b/countdown-numbers-game.cabal
@@ -0,0 +1,78 @@
+Name:                countdown-numbers-game
+Version:             0.0
+Synopsis:            Solve problems from the number round of the Countdown game show
+Description:
+  Solver for the numbers round of the Countdown game show:
+  .
+  * <https://en.wikipedia.org/wiki/Countdown_(game_show)>
+  .
+  * <https://www.youtube.com/watch?v=pfa3MHLLSWI>
+  .
+  That is, given six numbers and a target number,
+  find an arithmetical expression containing exactly those six numbers
+  that yields the target number.
+  The solver however is neither limited to a certain number of operands
+  nor to a certain magnitude of numbers
+  nor to uniqueness of the operands.
+  Please note, that the solver also emits solutions
+  where not all of the given operands are used.
+  .
+  Example:
+  .
+  > $ countdown-numbers-solve 23 42 3 4 5 --result 777
+  > (23*3+5)*42/4
+  .
+  The solver employs a brute-force search,
+  but ensures that expressions are unique up to commutativity and associativity.
+  It determines all solutions of a problem within seconds.
+Homepage:            https://hub.darcs.net/thielema/countdown-numbers-game
+License:             BSD3
+License-File:        LICENSE
+Author:              Henning Thielemann
+Maintainer:          haskell@henning-thielemann.de
+Category:            Math
+Build-Type:          Simple
+Cabal-Version:       >=1.10
+
+Extra-Source-Files:
+  Makefile
+
+Source-Repository this
+  Tag:         0.0
+  Type:        darcs
+  Location:    https://hub.darcs.net/thielema/countdown-numbers-game
+
+Source-Repository head
+  Type:        darcs
+  Location:    https://hub.darcs.net/thielema/countdown-numbers-game
+
+Executable countdown-numbers-solve
+  Build-Depends:
+    optparse-applicative >=0.11 && <0.19,
+    non-empty >=0.3.2 && <0.4,
+    utility-ht >=0.0.11 && <0.1,
+    base >=4.5 && <5
+  Default-Language:    Haskell2010
+  GHC-Options:         -Wall
+  Hs-Source-Dirs:      src
+  Main-is:             Main.hs
+  Other-Modules: Solve
+
+Test-Suite countdown-numbers-test
+  Type: exitcode-stdio-1.0
+  Build-Depends:
+    doctest-exitcode-stdio >=0.0 && <0.1,
+    doctest-lib >=0.1 && <0.2,
+    -- 2.8 for 'shuffle'
+    QuickCheck >=2.8 && <3,
+    non-empty >=0.3.2 && <0.4,
+    utility-ht >=0.0.11 && <0.1,
+    base >=4.5 && <5
+  Default-Language:    Haskell2010
+  GHC-Options:         -Wall
+  Hs-Source-Dirs:      src, test
+  Main-is:             TestMain.hs
+  Other-Modules:
+    Test.Solve
+    Test.Utility
+    Solve
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,27 @@
+module Main where
+
+import qualified Solve
+
+import qualified Options.Applicative as OP
+
+
+
+options :: OP.Parser ([Integer], Integer)
+options =
+   OP.liftA2 (,)
+      (OP.many $
+       OP.argument OP.auto
+         (OP.help "given operand" <> OP.metavar "INTEGER"))
+      (OP.option OP.auto
+         (OP.help "wanted result" <> OP.long "result" <> OP.metavar "INTEGER"))
+
+description :: OP.InfoMod a
+description =
+   OP.fullDesc
+   <>
+   OP.progDesc "Solve problems from the number round of the Countdown game show"
+
+main :: IO ()
+main = do
+   problem <- OP.execParser $ OP.info (OP.helper <*> options) description
+   mapM_ (putStrLn . Solve.format) $ Solve.run problem
diff --git a/src/Solve.hs b/src/Solve.hs
new file mode 100644
--- /dev/null
+++ b/src/Solve.hs
@@ -0,0 +1,188 @@
+module Solve where
+
+import qualified Data.NonEmpty as NonEmpty
+import qualified Data.List.HT as ListHT
+import qualified Data.List as List
+import Data.NonEmpty ((!:))
+import Data.Traversable (traverse)
+import Data.Tuple.HT (mapPair, mapFst)
+
+import Control.Monad (liftM2, guard, mfilter)
+
+{- $setup
+>>> import qualified Solve
+>>> import qualified Data.List as List
+>>> import Data.Eq.HT (equating)
+>>> import Control.Functor.HT (void)
+>>> import Test.Utility
+>>>    (solve, genOperands, genResult, genEquation,
+>>>     normalizeSubExpr, normalizeSum)
+>>> import qualified Test.QuickCheck as QC
+-}
+
+
+type NEList = NonEmpty.T []
+
+{- |
+The 'Ord' instance is needed for normalization in the tests
+but does not refer to the value of the expression.
+In order to prevent accidental use of '==' or '<'
+we would have to implement those functions manually,
+which is possible but a bit cumbersome.
+-}
+data SubExpr a = Number Integer | SubExpr a
+   deriving (Eq, Ord, Show)
+data Sum =
+      Sum {positive :: NEList (SubExpr Product), negative :: [SubExpr Product]}
+   deriving (Eq, Ord, Show)
+data Product =
+      Product {normal :: NEList (SubExpr Sum), reciprocal :: [SubExpr Sum]}
+   deriving (Eq, Ord, Show)
+
+
+split :: [a] -> [([a],[a])]
+split =
+   map (mapPair (map fst, map fst) . List.partition snd) .
+   sequence . flip (ListHT.outerProduct (,)) [True, False]
+
+neSplit :: NEList a -> [(NEList a, [a])]
+neSplit (NonEmpty.Cons x xs) =
+   map (mapFst (NonEmpty.Cons x)) $ split xs
+
+splitMultiOrdered :: [a] -> [[NEList a]]
+splitMultiOrdered [] = return []
+splitMultiOrdered (x:xs) = do
+   (ys,zs) <- neSplit (x!:xs)
+   map (ys:) $ splitMultiOrdered zs
+
+neSplitMultiOrdered :: [a] -> [(NEList (NEList a), [a])]
+neSplitMultiOrdered xs = do
+   (us,w,ws) <- ListHT.splitEverywhere xs
+   (ys,zs) <- neSplit (w!:ws)
+   map (mapPair (NonEmpty.cons ys, (us++))) $
+      ([], zs) : map (mapFst NonEmpty.flatten) (neSplitMultiOrdered zs)
+
+
+subExprsFromSet :: (NEList Integer -> [a]) -> NEList Integer -> [SubExpr a]
+subExprsFromSet f xt@(NonEmpty.Cons x xs) =
+   if null xs then [Number x] else map SubExpr $ f xt
+
+topExprsFromSet :: [Integer] -> [SubExpr Sum]
+topExprsFromSet xs = do
+   y:ys <- fmap fst $ split xs
+   flip subExprsFromSet (y!:ys) $ \yt ->
+      exprsFromSet yt ++ map topProduct (exprsFromSet yt)
+
+topProduct :: SubExpr Product -> Sum
+topProduct zs = Sum (NonEmpty.singleton zs) []
+
+anyExprsFromSet ::
+   (Expression a) =>
+   (NEList (SubExpr a) -> [SubExpr a] -> expr) ->
+   NEList Integer -> [expr]
+anyExprsFromSet cons xs = do
+   (lhs,rs) <- neSplitMultiOrdered $ NonEmpty.flatten xs
+   rhs <- splitMultiOrdered rs
+   guard $ not $ null (NonEmpty.tail lhs) && null rhs
+   liftM2 cons
+      (traverse exprsFromSet lhs)
+      (traverse exprsFromSet rhs)
+
+
+class Expression expr where
+   exprsFromSet :: NEList Integer -> [expr]
+   format :: expr-> String
+   eval :: expr -> Maybe Integer
+
+instance (Expression a) => Expression (SubExpr a) where
+   exprsFromSet = subExprsFromSet exprsFromSet
+   format = formatSubExpr format
+   eval (Number k) = Just k
+   eval (SubExpr expr) = eval expr
+
+instance Expression Sum where
+   exprsFromSet = anyExprsFromSet Sum
+   format (Sum pos neg) =
+      let NonEmpty.Cons p ps = fmap format pos
+          ns = map format neg
+      in p ++ concatMap ("+"++) ps ++ concatMap ("-"++) ns
+   eval (Sum pos neg) =
+      mfilter (>=0) $
+      liftM2 (-)
+         (fmap NonEmpty.sum $ traverse eval pos)
+         (fmap sum $ mapM eval neg)
+
+instance Expression Product where
+   exprsFromSet = anyExprsFromSet Product
+   format (Product norm rec) =
+      let NonEmpty.Cons n ns =
+              fmap (formatSubExpr (addParen.format)) norm
+          rs = map (formatSubExpr (addParen.format)) rec
+      in n ++ concatMap ("*"++) ns ++ concatMap ("/"++) rs
+   eval (Product norm rec) = do
+      denom <- fmap product $ mapM eval rec
+      guard $ denom/=0
+      (q,r) <- fmap (flip divMod denom . NonEmpty.product) $ traverse eval norm
+      guard $ r==0
+      return q
+
+formatSubExpr :: (a -> String) -> SubExpr a -> String
+formatSubExpr _fmt (Number k) = show k
+formatSubExpr fmt (SubExpr expr) = fmt expr
+
+addParen :: String -> String
+addParen str = "("++str++")"
+
+
+{- |
+>>> solve [25, 50, 75, 100, 3, 6] 952
+25+6*75*(3+100)/50
+(3*75*(6+100)-50)/25
+
+>>> solve [75, 50, 2, 3, 8, 7] 812
+50+(2+75)*(3+7)-8
+2*7*(8+50)
+...
+50*(7+75)/(2+3)-8
+(3+(2+75)/7)*(8+50)
+
+>>> solve [100, 75, 50, 10, 5, 1] 102
+1+100+5*10/50
+1+100+50/5/10
+...
+(100+50/(75-5*10))/1
+(100+(5+75)/(50-10))/1
+(100+(75-5-50)/10)/1
+
+
+prop> :{
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genResult xs) $ \x ->
+      not $ null $ Solve.run (xs,x)
+:}
+
+prop> :{
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genResult xs) $ \x ->
+   QC.forAll (QC.shuffle xs) $ \xs1 ->
+      void (Solve.run (xs,x)) == void (Solve.run (xs1,x))
+:}
+
+prop> :{
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genResult xs) $ \x ->
+   QC.forAll (QC.shuffle xs) $ \xs1 ->
+      equating (List.sort . map (normalizeSubExpr normalizeSum))
+         (Solve.run (xs,x)) (Solve.run (xs1,x))
+:}
+
+prop> :{
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genEquation xs) $ \(expr,x) ->
+      elem expr $
+      List.sort $ map (normalizeSubExpr normalizeSum) $ Solve.run (xs,x)
+:}
+-}
+run :: ([Integer], Integer) -> [SubExpr Sum]
+run (operands, result) =
+   filter ((Just result ==) . Solve.eval) $ Solve.topExprsFromSet operands
diff --git a/test/Test/Solve.hs b/test/Test/Solve.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Solve.hs
@@ -0,0 +1,81 @@
+-- Do not edit! Automatically created with doctest-extract from src/Solve.hs
+{-# LINE 12 "src/Solve.hs" #-}
+
+module Test.Solve where
+
+import Test.DocTest.Base
+import qualified Test.DocTest.Driver as DocTest
+
+{-# LINE 13 "src/Solve.hs" #-}
+import     qualified Solve
+import     qualified Data.List as List
+import     Data.Eq.HT (equating)
+import     Control.Functor.HT (void)
+import     Test.Utility
+       (solve, genOperands, genResult, genEquation,
+        normalizeSubExpr, normalizeSum)
+import     qualified Test.QuickCheck as QC
+
+test :: DocTest.T ()
+test = do
+ DocTest.printPrefix "Solve:138: "
+{-# LINE 138 "src/Solve.hs" #-}
+ DocTest.example(
+{-# LINE 138 "src/Solve.hs" #-}
+    solve [25, 50, 75, 100, 3, 6] 952
+  )
+  [ExpectedLine [LineChunk "25+6*75*(3+100)/50"],ExpectedLine [LineChunk "(3*75*(6+100)-50)/25"]]
+ DocTest.printPrefix "Solve:142: "
+{-# LINE 142 "src/Solve.hs" #-}
+ DocTest.example(
+{-# LINE 142 "src/Solve.hs" #-}
+    solve [75, 50, 2, 3, 8, 7] 812
+  )
+  [ExpectedLine [LineChunk "50+(2+75)*(3+7)-8"],ExpectedLine [LineChunk "2*7*(8+50)"],WildCardLine,ExpectedLine [LineChunk "50*(7+75)/(2+3)-8"],ExpectedLine [LineChunk "(3+(2+75)/7)*(8+50)"]]
+ DocTest.printPrefix "Solve:149: "
+{-# LINE 149 "src/Solve.hs" #-}
+ DocTest.example(
+{-# LINE 149 "src/Solve.hs" #-}
+    solve [100, 75, 50, 10, 5, 1] 102
+  )
+  [ExpectedLine [LineChunk "1+100+5*10/50"],ExpectedLine [LineChunk "1+100+50/5/10"],WildCardLine,ExpectedLine [LineChunk "(100+50/(75-5*10))/1"],ExpectedLine [LineChunk "(100+(5+75)/(50-10))/1"],ExpectedLine [LineChunk "(100+(75-5-50)/10)/1"]]
+ DocTest.printPrefix "Solve:158: "
+{-# LINE 158 "src/Solve.hs" #-}
+ DocTest.property(
+{-# LINE 158 "src/Solve.hs" #-}
+        
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genResult xs) $ \x ->
+      not $ null $ Solve.run (xs,x)
+  )
+ DocTest.printPrefix "Solve:164: "
+{-# LINE 164 "src/Solve.hs" #-}
+ DocTest.property(
+{-# LINE 164 "src/Solve.hs" #-}
+        
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genResult xs) $ \x ->
+   QC.forAll (QC.shuffle xs) $ \xs1 ->
+      void (Solve.run (xs,x)) == void (Solve.run (xs1,x))
+  )
+ DocTest.printPrefix "Solve:171: "
+{-# LINE 171 "src/Solve.hs" #-}
+ DocTest.property(
+{-# LINE 171 "src/Solve.hs" #-}
+        
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genResult xs) $ \x ->
+   QC.forAll (QC.shuffle xs) $ \xs1 ->
+      equating (List.sort . map (normalizeSubExpr normalizeSum))
+         (Solve.run (xs,x)) (Solve.run (xs1,x))
+  )
+ DocTest.printPrefix "Solve:179: "
+{-# LINE 179 "src/Solve.hs" #-}
+ DocTest.property(
+{-# LINE 179 "src/Solve.hs" #-}
+        
+   QC.forAll genOperands $ \xs ->
+   QC.forAll (genEquation xs) $ \(expr,x) ->
+      elem expr $
+      List.sort $ map (normalizeSubExpr normalizeSum) $ Solve.run (xs,x)
+  )
diff --git a/test/Test/Utility.hs b/test/Test/Utility.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Utility.hs
@@ -0,0 +1,192 @@
+module Test.Utility where
+
+import qualified Solve
+
+import qualified Data.NonEmpty.Class as NonEmptyC
+import qualified Data.NonEmpty as NonEmpty
+import qualified Data.List.HT as ListHT
+import qualified Data.List as List
+import Data.NonEmpty ((!:))
+import Data.Tuple.Strict (zipWithPair)
+import Data.Tuple.HT (mapPair)
+
+import Control.Monad (guard)
+import Control.Applicative ((<$>))
+
+import qualified Test.QuickCheck as QC
+
+
+newtype FormatMany a = FormatMany [a]
+
+instance (Solve.Expression a) => Show (FormatMany a) where
+   show (FormatMany xs) = unlines $ map Solve.format xs
+
+solve :: [Integer] -> Integer -> FormatMany (Solve.SubExpr Solve.Sum)
+solve =
+   curry $
+   FormatMany . List.sort . map (normalizeSubExpr normalizeSum) . Solve.run
+
+
+
+type List1 = NonEmpty.T []
+type List2 = NonEmpty.T List1
+
+genResult :: [Integer] -> QC.Gen Integer
+genResult xs0 =
+   maybe (return 0) genResultNE $ NonEmpty.fetch xs0
+
+genResultNE :: List1 Integer -> QC.Gen Integer
+genResultNE (NonEmpty.Cons x0 xs0) =
+   case NonEmpty.fetch xs0 of
+      Nothing -> return x0
+      Just xs1 -> do
+         (ys,zs) <- genSplit $ x0!:xs1
+         y <- genResultNE ys
+         z <- genResultNE zs
+         QC.elements $
+            (y+z) :
+            (y*z) :
+            abs (y-z) :
+            (let a = max y z; b = min y z in
+             if b/=0 && mod a b == 0 then [div a b] else [])
+
+genEquation :: [Integer] -> QC.Gen (Solve.SubExpr Solve.Sum, Integer)
+genEquation xs0 =
+   maybe (return (Solve.Number 0, 0)) genEquationNE $ NonEmpty.fetch xs0
+
+genEquationNE :: List1 Integer -> QC.Gen (Solve.SubExpr Solve.Sum, Integer)
+genEquationNE (NonEmpty.Cons x0 xs0) =
+   case NonEmpty.fetch xs0 of
+      Nothing -> return (Solve.Number x0, x0)
+      Just xs1 -> do
+         (ys,zs) <- genSplit $ x0!:xs1
+         y <- genEquationNE ys
+         z <- genEquationNE zs
+         QC.elements $
+            zipWithPair (addExpr, (+)) y z :
+            zipWithPair (mulExpr, (*)) y z :
+            (if snd y >= snd z
+             then zipWithPair (subExpr, (-)) y z
+             else zipWithPair (subExpr, (-)) z y) :
+            (if snd y >= snd z then divEqu y z else divEqu z y)
+
+addExpr ::
+   Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum
+addExpr a b =
+   case (sumFromSubExpr a, sumFromSubExpr b) of
+      (Solve.Sum posA negA, Solve.Sum posB negB) ->
+         Solve.SubExpr $
+         Solve.Sum (mergeByNE (<) posA posB) (ListHT.mergeBy (<) negA negB)
+
+subExpr ::
+   Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum
+subExpr a b =
+   case (sumFromSubExpr a, sumFromSubExpr b) of
+      (Solve.Sum posA negA, Solve.Sum posB negB) ->
+         Solve.SubExpr $
+         Solve.Sum
+            (mergyLeftByNE (<) posA negB) 
+            (ListHT.mergeBy (<) negA $ NonEmpty.flatten posB)
+
+sumFromSubExpr :: Solve.SubExpr Solve.Sum -> Solve.Sum
+sumFromSubExpr (Solve.SubExpr a) = a
+sumFromSubExpr (Solve.Number a) =
+   Solve.Sum (NonEmpty.singleton $ Solve.Number a) []
+
+mulExpr ::
+   Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum
+mulExpr a b =
+   case (productFromSubExpr a, productFromSubExpr b) of
+      (Solve.Product normA recA, Solve.Product normB recB) ->
+         Solve.SubExpr $ singletonSum $
+         Solve.SubExpr $
+            Solve.Product
+               (mergeByNE (<) normA normB)
+               (ListHT.mergeBy (<) recA recB)
+
+
+divEqu ::
+   (Solve.SubExpr Solve.Sum, Integer) ->
+   (Solve.SubExpr Solve.Sum, Integer) ->
+   [(Solve.SubExpr Solve.Sum, Integer)]
+divEqu (exprA,resA) (exprB,resB) =
+   guard (resB/=0 && mod resA resB == 0) >>
+      [(divExpr exprA exprB, div resA resB)]
+
+divExpr ::
+   Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum -> Solve.SubExpr Solve.Sum
+divExpr a b =
+   case (productFromSubExpr a, productFromSubExpr b) of
+      (Solve.Product normA recA, Solve.Product normB recB) ->
+         Solve.SubExpr $ singletonSum $
+         Solve.SubExpr $
+            Solve.Product
+               (mergyLeftByNE (<) normA recB)
+               (ListHT.mergeBy (<) recA $ NonEmpty.flatten normB)
+
+productFromSubExpr :: Solve.SubExpr Solve.Sum -> Solve.Product
+productFromSubExpr (Solve.Number a) = singletonProduct $ Solve.Number a
+productFromSubExpr (Solve.SubExpr (Solve.Sum (NonEmpty.Cons expr []) [])) =
+   case expr of
+      Solve.SubExpr a -> a
+      Solve.Number a -> singletonProduct $ Solve.Number a
+productFromSubExpr a = singletonProduct a
+
+singletonSum :: Solve.SubExpr Solve.Product -> Solve.Sum
+singletonSum a = Solve.Sum (NonEmpty.singleton a) []
+
+singletonProduct :: Solve.SubExpr Solve.Sum -> Solve.Product
+singletonProduct a = Solve.Product (NonEmpty.singleton a) []
+
+mergeByNE ::
+   (a -> a -> Bool) -> NonEmpty.T [] a -> NonEmpty.T [] a -> NonEmpty.T [] a
+mergeByNE lt (NonEmpty.Cons x xs) (NonEmpty.Cons y ys) =
+   if lt x y
+   then NonEmpty.Cons x $ ListHT.mergeBy lt xs (y:ys)
+   else NonEmpty.Cons y $ ListHT.mergeBy lt (x:xs) ys
+
+mergyLeftByNE ::
+   (a -> a -> Bool) -> NonEmpty.T [] a -> [a] -> NonEmpty.T [] a
+mergyLeftByNE lt xt@(NonEmpty.Cons x xs) yt =
+   case yt of
+      [] -> xt
+      y:ys ->
+         if lt x y
+         then NonEmpty.Cons x $ ListHT.mergeBy lt xs (y:ys)
+         else NonEmpty.Cons y $ ListHT.mergeBy lt (x:xs) ys
+
+genSplit :: List2 a -> QC.Gen (List1 a, List1 a)
+genSplit xs0 = do
+   (x1,xs1) <-
+      QC.elements $
+      NonEmpty.flatten $ NonEmpty.flatten $ NonEmpty.removeEach xs0
+   (x2,xs2) <- QC.elements $ NonEmpty.flatten $ NonEmpty.removeEach xs1
+   (xsA,xsB) <-
+      fmap (mapPair (map fst, map fst) . ListHT.partition snd) $
+      mapM (\x -> (,) x <$> QC.arbitrary) xs2
+   return (x1!:xsA, x2!:xsB)
+
+genOperands :: QC.Gen [Integer]
+genOperands =
+   take 5 . map QC.getNonNegative . QC.getNonEmpty <$> QC.arbitrary
+
+
+
+normalizeSubExpr ::
+   (a -> a) -> Solve.SubExpr a -> Solve.SubExpr a
+normalizeSubExpr normalize expr =
+   case expr of
+      Solve.Number k -> Solve.Number k
+      Solve.SubExpr a -> Solve.SubExpr $ normalize a
+
+normalizeSum :: Solve.Sum -> Solve.Sum
+normalizeSum (Solve.Sum pos neg) =
+   Solve.Sum
+      (NonEmptyC.sort $ fmap (normalizeSubExpr normalizeProduct) pos)
+      (List.sort $ map (normalizeSubExpr normalizeProduct) neg)
+
+normalizeProduct :: Solve.Product -> Solve.Product
+normalizeProduct (Solve.Product norm rec) =
+   Solve.Product
+      (NonEmptyC.sort $ fmap (normalizeSubExpr normalizeSum) norm)
+      (List.sort $ map (normalizeSubExpr normalizeSum) rec)
diff --git a/test/TestMain.hs b/test/TestMain.hs
new file mode 100644
--- /dev/null
+++ b/test/TestMain.hs
@@ -0,0 +1,10 @@
+-- Do not edit! Automatically created with doctest-extract.
+module Main where
+
+import qualified Test.Solve
+
+import qualified Test.DocTest.Driver as DocTest
+
+main :: IO ()
+main = DocTest.run $ do
+    Test.Solve.test
