==
Даны числа 1, 5, 6 и 7. При помощи произвольного числа арифметических операций и
скобок необходимо составить такое математическое выражение из этих чисел, чтобы его
значение было равным 21. Данные числа можно использовать в выражении только по одному разу. Числа нельзя «склеивать» друг с другом (то есть из 1 и 5 получить 15).
==
Так как на конкурс я опоздал, да и решение у меня получилось наверняка не особо приятное :), опубликую его тут. Итак, в качестве языка выбираем Haskell.
Сначала подключим необходимые модули и опишем требуемые типы данных:
import Data.List
import Control.Monad
data Operation = Add | Sub | Mult | Div | Exp 
data Tree a = Node a | Branch (Operation, Tree a, Tree a) 
Далее опишем функцию, которая будет вычислять математическое выражение, представленное конкретным деревом:
calcTree (Branch (op,x,y)) =
        case op of
  Add -> calcTree x + calcTree y
  Sub -> calcTree x - calcTree y
  Mult -> calcTree x * calcTree y
  Div -> calcTree x / calcTree y
  Exp -> calcTree x ** calcTree y
calcTree (Node x) = x
showOp Add = "+"
showOp Sub = "-"
showOp Mult = "*"
showOp Div = "/"
showOp Exp = "^"
printTree (Branch (op, x, y)) =
 "(" ++ printTree x ++ " " ++ showOp op ++
  " " ++ printTree y ++ ")"
printTree (Node x) = show x
Теперь переходим к самой важной части - генерации деревьев математических выражений.
Сначала опишем функцию divideLst, которая будет принимать некоторый список значений и формировать по нему список пар следующим образом:
для списка [1,2,3,4] => [([1],[2,3,4]), ([1,2],[3,4]), ([1,2,3],[4])]
То есть, исходный список делится на части разными способами.
Эта функция нам будет нужна для основной функции генерации деревьев.
divideLst xs = 
 [(x,y) | 
      (x,y) <- zip (inits xs) (tails xs),
              x /= [], y /= [] 
 ]      
gen [] _  = []
gen [n] _ = [Node n]
gen l op = [ Branch (o,lb,rb) |
   o <- op,
   (lbr, rbr) <- divideLst l,
   lb <- gen lbr op,
   rb <- gen rbr op
    ]
Ну и, собственно, основная функция, которая решает задачу:
findTree n ops nums =
 mapM_ (\(c,t) -> print $ show c ++ " = " ++ t) 
   $ filter (\(c,_) -> c == n) 
 $ map (\t -> (calcTree t, printTree t)) 
 (concat [gen n ops | n <- nub $ permutations nums])     
(concat [gen n ops | n <- nub $ permutations nums]) формирует нам список все возможных вариантов деревьев для списка операций ops и списка чисел nums). Следующий map (\t -> (calcTree t, printTree t)) преобразует его к списку пар, первым значением которых будет идти вычисленное значение выражения для соответствуюшего дерева, а вторым - строчное представление этого дерева. На следующем шаге функцией фильтр мы отбираем из полученного списка только те элементы, первые элементы которых равны значению, на равенство которому мы проверяем наши математические выражения.
Ну и последний шаг просто выводит на экран необходимую информацию (значение выражения и его "сериализованный" вид).
Вызовем эту функцию так:
main =  findTree 21 [Add,Sub,Mult,Div,Exp] [1,5,6,7]
"21.0 = (6.0 / (1.0 - (5.0 / 7.0)))"
Дополнительно к этой задаче Роман задавал две дополнительные небольшие задачи, которые основываются на использовании основного решения.
1. Используя только пять базовых арифметических операций (+, -, *, /, ^),
определить, какие числа от 1 до 100 можно выразить четырьмя двойками?
Программа, её решающая может выглядеть так:
task1 =  [ x | x <- [1..100],  
  not $ null $ filter (\z -> calcTree z == x) 
  $ gen [2,2,2,2] [Add,Mult,Sub,Div,Exp] ] 
[1.0,2.0,3.0,4.0,5.0,6.0,8.0,9.0,10.0,
   12.0,14.0,16.0,18.0,32.0,36.0,64.0]
И вторая задача.
2. При помощи каких четвёрок чисел от 1 до 10 и при помощи только пяти базовых
арифметических операций можно выразить число 1488?
Программа:
task2 =  nub [ sort z | z <- replicateM 4 [1..10], 
         not $ null $ filter (\x -> calcTree x == 1488) 
         $ gen z [Add, Mult, Sub, Div, Exp] ]
[[2.0,6.0,8.0,8.0],[3.0,5.0,5.0,6.0],[4.0,4.0,6.0,8.0]]
Надеюсь, вам было интересно )
 
Комментариев нет:
Отправить комментарий