web developer & system programmer

coder . cl

ramblings and thoughts on programming...


combinatorics problem in haskell

published on 12-02-2012 / updated: 12-02-2012
posted in: development, haskell, programming, tips
by Daniel Molina Wegener

Here is another challenge called difficult, but seems to be quite easy to solve. It is a simple feasible operation search algorithm between lists of numbers, so it is not so complex to solve and took few minutes to get it working on Haskell, because Haskell provides various tools to work with algorithms. Also this solution does the extra credit, no matter if the list has up to four integers.

Today, your challenge is to create a program that will take a series of numbers (5, 3, 15), and find how those numbers can add, subtract, multiply, or divide in various ways to relate to each other. This string of numbers should result in 5 * 3 = 15, or 15 /3 = 5, or 15/5 = 3. When you are done, test your numbers with the following strings:


  • 4, 2, 8
  • 6, 2, 12
  • 6, 2, 3
  • 9, 12, 108
  • 4, 16, 64

For extra credit, have the program list all possible combinations.
for even more extra credit, allow the program to deal with strings of greater than three numbers. For example, an input of (3, 5, 5, 3) would be 3 * 5 = 15, 15/5 = 3. When you are finished, test them with the following strings.

  • 2, 4, 6, 3
  • 1, 1, 2, 3
  • 4, 4, 3, 4
  • 8, 4, 3, 6
  • 9, 3, 1, 7

Here is the full solution in Haskell, just to practice a little. Compile it using ghc --make prog.hs -o prog or run it using runghc prog.hs input.txt


module Main where

import Data.String.Utils
import Data.List
import Data.Maybe
import Math.Combinat.Sets
import System.Environment
import Text.Printf

data OperationResult = OperationResult {
     oper              :: Int -> [Int] -> Bool
     , msg             :: String
}

availableOperations :: [OperationResult]
availableOperations =
                    [
                    OperationResult { oper = testMatchMul,
                                      msg = "mul" },
                    OperationResult { oper = testMatchDiv,
                                      msg = "div" },
                    OperationResult { oper = testMatchSum,
                                      msg = "add" },
                    OperationResult { oper = testMatchSub,
                                      msg = "sub" }
                    ]

liftLine :: String -> [Int]
liftLine l = sort $ fmap ( read . strip ) $ split "," l

matchMessage :: String -> [Int] -> Int -> String
matchMessage s xs n = printf "%s does %s with %s"
                      s ( show n ) ( show xs )

testMatchMul :: Int -> [Int] -> Bool
testMatchMul n xs = n == foldr (*) 1 xs

testMatchDiv :: Int -> [Int] -> Bool
testMatchDiv n xs = dm == n && dr == 0
                    where (dm,dr) = divMod ( head xs ) ( last xs )

testMatchSum :: Int -> [Int] -> Bool
testMatchSum n xs = n == foldr (+) 0 xs

testMatchSub :: Int -> [Int] -> Bool
testMatchSub n xs = n == foldr (-) 0 xs

applyOperation :: [Int] -> Int -> OperationResult -> Maybe String
applyOperation xs x y = if oper y x xs
                           then Just $ matchMessage ( msg y ) xs x
                           else Nothing

matchOperation :: [OperationResult] -> [Int] -> Int -> [Maybe String]
matchOperation ops xs x = fmap (applyOperation xs x) ops

findMatch :: [Int] -> [Int] -> [String]
findMatch rs xs = fmap fromJust
                  $ filter ( /= Nothing )
                  $ join []
                  $ fmap (matchOperation availableOperations rs) xs

createCombinations :: [[Int]] -> [[Int]]
createCombinations xs = join [] $ fmap (choose 2) xs

procResults :: [String] -> IO ()
procResults = foldr ((>>) . putStrLn) (putStrLn "End!")

main :: IO ()
main = do [i] <- getArgs
          f <- readFile i
          let rows = fmap liftLine $ lines f
              comb = createCombinations rows
              oset = join [] $ nub $ join []
                     $ fmap ( x -> fmap (findMatch x) rows ) comb
              in procResults oset
                 >> print comb

Where having a sample input file with three numbers per line as follows:

4, 2, 8
6, 2, 12
6, 2, 3
9, 12, 108
4, 16, 64

Should be processed as follows:


08:21 [dmw@www:1 exercises]$ ghc --make challenge20120212.hs -o challenge20120212
[1 of 1] Compiling Main             ( challenge20120212.hs, challenge20120212.o )
Linking challenge20120212 ...
08:21 [dmw@www:1 exercises]$ ./challenge20120212 ./challenge20120212.txt
mul does 8 with [2,4]
add does 6 with [2,4]
mul does 16 with [2,8]
add does 12 with [4,8]
add does 8 with [2,6]
mul does 12 with [2,6]
mul does 6 with [2,3]
add does 9 with [3,6]
mul does 108 with [9,12]
mul does 64 with [4,16]
End!
[[2,4],[2,8],[4,8],[2,6],[2,12],[6,12],[2,3],[2,6],[3,6],[9,12],[9,108],[12,108],[4,16],[4,64],[16,64]]

Also, this program can process files with lines containing up to 4 numbers, no matter how many of them are present on the file, it will try all combinations thanks to the Math.Combinat.Sets module which is used to create the proper permutations with each line which are converted to Int lists with the liftLine function. Probably this is a kind of very basic MapReduce and non distributed implementation where you have various sets of unique permutations — or antisymmetric power of each list — and operations to check whether are executed.


6 comments to “combinatorics problem in haskell”

  1. Solution in C#:

    
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    
    namespace testcombinatoria
    {
    	class MainClass
    	{
    		public static void Main (string[] args)
    		{
    			string line;
    			using (var reader = new StreamReader ("/tmp/numbers.txt"))
    				while ((line = reader.ReadLine ()) != null)
    					Run (line);
    		}
    
    		private static void Run (string line)
    		{
    			var parts = line.Split (',');
    			var operations = new Dictionary <string, Func <int, int, int>>
    			{
    				{"{0} + {1} = {2}", (x, y) => (x + y)},
    				{"{0} - {1} = {2}", (x, y) => (x - y)},
    				{"{0} * {1} = {2}", (x, y) => (x * y)},
    				{"{0} / {1} = {2}", (x, y) => (x / y)}
    			};
    			var valuesIndexes = parts.Select ((x, index) => new { Index = index, Value = int.Parse (x.Trim ()) });
    			var matches =
    				from x in valuesIndexes
    				from y in valuesIndexes
    				from z in valuesIndexes
    				from op in operations
    				where x.Index != y.Index
    				where x.Index != z.Index
    				where y.Index != z.Index
    				where op.Value (x.Value, y.Value) == z.Value
    				select string.Format (op.Key, x.Value, y.Value, z.Value);
    
    			foreach (string m in matches)
    				Console.WriteLine (m);
    		}
    	}
    }
    
  2. seems that your example does not create the number list combinations, as the Haskell solution does.

  3. Can you provide a sample output? Anyway, I get this error when I try to run your solution:
    Could not find module `Math.Combinat.Sets'

  4. Well, on that case, you should try using cabal to install additional module. On this case you can reach the combinat hackage here:

    combinat

    And you can use cabal install combinat to install that hackage.

  5. Thanks. Please also provide a sample output for the program. Thanks.

  6. OK, I’ve added sample output.

post a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>