Serguey Zefirov ([info]thesz) wrote,
@ 2006-06-25 20:36:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:cpp, haskell, программирование, решение

Снова про задачку.

module Main(main) where

import Char
import qualified Data.Map as M
import qualified Data.Set as S
import Control.Monad.State

type CellRef = (String,String)

data Cell =
		CEmpty
	|	CError	String
	|	CInt	Int
	|	CText	String
	|	CFormulae	CExpr

data BinOp =
		BPlus
	|	BMinus
	|	BMul
	|	BDiv

data CExpr =
		CECell	CellRef
	|	CEInt	Int
	|	CEBin	BinOp	CExpr	CExpr

instance Show Cell where
	show CEmpty = ""
	show (CError s) = '#':s
	show (CInt i) = show i
	show (CText t) = t
	show (CFormulae e) = '=':show e

instance Show BinOp where
	show BPlus = "+"
	show BMinus = "-"
	show BMul = "*"
	show BDiv = "/"

instance Show CExpr where
	show (CECell (r,c)) = (c)++r
	show (CEInt i) = show i
	show (CEBin op a b) = show a++show op++show b

splitByTab l = split Nothing l
	where
		split Nothing [] = []
		split (Just acc) [] = [reverse acc]
		split acc (x:xs)
			| x == '\t' = case acc of
				Nothing -> []:split Nothing xs
				Just acc -> reverse acc:split Nothing xs
			| otherwise = case acc of
				Nothing -> split (Just [x]) xs
				Just acc -> split (Just $ x:acc) xs

tabbedListLine = do
	l <- getLine
	return $ splitByTab l

parseWH :: IO (Int,Int)
parseWH = do
	hw <- tabbedListLine
	case hw of
		[hs,ws] -> return (read ws,read hs)
		_ -> error "Illegal height and width line."

allindexes = chars1++nextchars chars1
	where
		chars = ['A'..'Z']
		chars1 = map (\x -> [x]) chars
		nextchars pcs = let cs' = [ c:cs | c <- chars, cs <- pcs] in cs'++nextchars cs'
parseCells w h = parselines M.empty 1
	where
		parselines cells j
			| j > h = return cells
			| otherwise = do
				cells' <- parseline j
				parselines (M.union cells cells') (j+1)
		indexes j = map (\x -> (show j,x)) $ take w allindexes
		parseline j = do
			l <- tabbedListLine
			return $ M.fromList $ zip (indexes j) $ filter notEmpty $ (map parse l)
		notEmpty CEmpty = False
		notEmpty _ = True
		parse "" = CEmpty
		parse ('\'':text) = CText text
		parse ('=':formulae) = parseexpr formulae
		parse i@(x:xs)
			| isDigit x = CInt (read i)
			| otherwise = CError "NAI"
		parseexpr "" = CError "NoExpr"
		parseexpr e = case pexpr e of
			Just e -> CFormulae e
			Nothing -> CError "Parse"
pexpr e = case parg e of
	Just (a,rem) -> case rem of
		(bop:args) -> case binop bop of
			Just bop -> case pexpr args of
				Just b -> Just $ CEBin bop a b
				Nothing -> Nothing
			Nothing -> Nothing
		_ -> Just a
	Nothing -> Nothing
	where
		binop '+' = Just BPlus
		binop '-' = Just BMinus
		binop '*' = Just BMul
		binop '/' = Just BDiv
		binop _ = Nothing
		parg [] = Nothing
		parg e@(c:cs)
			| isDigit c = let
					(n,rest) = span isDigit e
				in
					Just (CEInt (read n),rest)
			| isAlpha c = let
					(c,restc) = span isAlpha e
					(r,restr) = span isDigit restc
				in case (c,r) of
					([],_) -> Nothing
					(c,[]) -> Nothing
					(c,r) -> Just (CECell (r,map toUpper c),restr)
			| otherwise = Nothing

dig c = fromEnum (toUpper c)-fromEnum 'A'+1
base = dig 'z'+1
colToInt col = acc 0 $ map dig col
	where
		acc sum [] = sum
		acc sum (c:cs) = acc (sum*base+c) cs
intToCol :: Int -> String
intToCol 1 = "A"
intToCol i = map (toEnum . (fromEnum 'A'+)) $ reverse $ mods i
	where
		mods 0 = []
		mods x = let (d,m) = divMod x base in (m-1):mods d

celllookup i j cells = case M.lookup (i,j) cells of
	Nothing -> error $ "empty looking up "++show (i,j) -- CEmpty
	Just x -> x

getcell i j = do
	cells <- get
	return $ celllookup i j cells
setcell i j cell = do
	cells <- get
	put $ M.adjust (const cell) (i,j) cells
markerror i j = do
	x <- getcell i j
	setcell i j (CError "cycle")
	return x
			

printCells w h cells = print 1
	where
		printrow i j
			| j<=w = do
				putStr (show $ celllookup (show i) (intToCol j) cells)
				if j/=w then putStr "\t" else return ()
				printrow i (j+1)
			| otherwise = putStrLn ""
		print i
			| i <= h = do
				printrow i 1
				print (i+1)
			| otherwise = return ()

evalcells cells = execState (eval formulaes) cells
	where
		allcells = M.toList cells
		formulaes = map (\((i,j),_) -> (i,j)) $ filter isformulae allcells
		isformulae (_,CFormulae _) = True
		isformulae _ = False
		eval ((i,j):fs) = do
			evalcell i j
			eval fs
		eval [] = return ()
		evalcell i j = do
			x <- getcell i j
			case x of
				CFormulae expr -> do
					markerror i j
					r <- evalexpr expr
					setcell i j r
					return r
				v -> return v
		evalexpr (CECell (i,j)) = evalcell i j
		evalexpr (CEInt i) = return $ CInt i
		evalexpr (CEBin op a b) = do
			ar <- evalexpr a
			case ar of
				CError _ -> return ar
				x -> evalbin op ar b
		evalbin op ar b = case b of
			CEBin op' a' b -> do
				ar' <- evalexpr a'
				evalbin op' (apply op ar ar') b
			_ -> do
				br <- evalexpr b
				return $ apply op ar br
		apply _ ar@(CError _) _ = ar
		apply _ _ br@(CError _) = br
		apply op (CInt x) (CInt y) = case op of
			BPlus -> CInt $ x + y
			BMinus -> CInt $ x - y
			BMul -> CInt $ x * y
			BDiv
				| y == 0 -> CError "DBZ"
				| otherwise -> CInt $ x `div` y
		apply _  _ _ = CError "InvOp"


main = do
	(width,height) <- parseWH
	cells <- parseCells width height
	printCells width height $ evalcells cells
Выдержать "промышленный уровень" мне не удалось. ;)

Пальцы сами не хотят писать evalCells или cellLookup. ;)

Три часа работы. Два затруднения - нумерация ячеек и порядок вычисления выражений (откель там и появился evalbin;).

С циклами проблем не возникло, по крайней мере, в первом приближении. Есть небольшая неприятность - если встречается цикл, то сообщение об ошибке из него распространяется на все ссылающиеся на этот цикл элементы.

Проблем с проектированием типов данных (на это делается упор при рассмотрении решения на C++) не возникло.

Расширение в сторону операций со строками делается просто - добавляются необходимые сравнения с образцом в apply.

Для обработки больших таблиц необходимо не хранить пустые элементы в cells (сейчас они хранятся).



(Post a new comment)


[info]the_lazy_guy
2006-06-25 05:44 pm UTC (link)
Не вижу почему почем всё то же самое нельзя написать на C++. Отсутствие алгебраических типовов (во всяком случае, таких простых) уже давно научились кое-как обходить, делая соответствующую иерархию классов, что конечно более многословно, но вполне работает. Паттерн-матчинг здесь вроде используется только для вычисления выражений и проверок не является один из операндов ошибкой. В таком варианте он легко реализуется с помощью if/switch.

В текущем варианте - типичная олимпиадная задача на технику. Технически подкованный олимпиадник (то бишь умеющий очень быстро писать работающий код на выброс) напишет за полчаса-час. Сделав вместо алгебраических типов струтуру struct Cell { int type; ... };

Я не против Haskell, скорее даже за (хотя его не знаю). Но этот пример, по-моему, не демонстрирует его принципиальных преимуществ.

(Reply to this) (Thread)


[info]thesz
2006-06-25 08:12 pm UTC (link)
В текущем варианте - типичная олимпиадная задача на технику. Технически подкованный олимпиадник (то бишь умеющий очень быстро писать работающий код на выброс) напишет за полчаса-час. Сделав вместо алгебраических типов струтуру struct Cell { int type; ... };

Эта задача давалась перед собеседованием. Это домашнее задание, не на час-полтора.

В зависимости от того, какую структуру ты создашь, ты совершишь ту или иную ошибку. ;)

Всего ключевых ошибок можно совершить пять штук. И бесконечно большое количество менее значимых. ;)

Попробуй, пожалуйста, написать ее самостоятельно. После этого будет видно, о чем можно говорить.

Я не против Haskell, скорее даже за (хотя его не знаю). Но этот пример, по-моему, не демонстрирует его принципиальных преимуществ.

Он демонстрирует типичные огрехи C++. ;)

(Reply to this) (Parent)(Thread)


[info]lomeo
2006-06-26 08:03 am UTC (link)
Слушай, а что это за пять ключевых ошибок таких? Просто интересно.

(Reply to this) (Parent)(Thread)


[info]thesz
2006-06-26 08:35 am UTC (link)
Например, нельзя хранить все в виде строки, несмотря на оправдание "ведь придется встраивать операции со строками." Там какая-то ошибка вылезает.

Правильно построенная иерархия - например, надо заводить отдельную иерархию под значения, их нельзя напрямую наследовать из Ячейки.

Использование исключений.

И какие-то еще две вещи. Забыл. ;)

(Reply to this) (Parent)(Thread)


[info]lomeo
2006-06-26 09:24 am UTC (link)
Ясно. А почему "нельзя напрямую наследовать из Ячейки"?

(Reply to this) (Parent)(Thread)


[info]thesz
2006-06-26 10:45 am UTC (link)
Проблема какая-то. Не помню. Не то с хранением ошибочных значений, не то с хранением строк.

Я объяснения слушал все более и более скучнея лицом. Для меня это звучало диковато и неестественно, поэтому и не запомнил.

(Reply to this) (Parent)(Thread)

(no subject) - [info]lomeo, 2006-06-26 10:53 am UTC
(no subject) - [info]thesz, 2006-06-26 11:13 am UTC
(no subject) - [info]thesz, 2006-06-26 11:23 am UTC
(no subject) - [info]lomeo, 2006-06-26 11:32 am UTC
(no subject) - [info]gaperton, 2007-10-29 11:26 am UTC
(no subject) - [info]lomeo, 2007-10-29 12:03 pm UTC
(no subject) - [info]thesz, 2007-10-29 12:16 pm UTC
(no subject) - [info]lomeo, 2007-10-29 12:23 pm UTC
(no subject) - [info]thesz, 2007-10-29 12:25 pm UTC

[info]_winnie
2006-06-25 10:37 pm UTC (link)
ТЗ выполнено, и нйифёт.

http://www.everfall.com/paste/id.php?c9dn0t8ffx43

(Reply to this) (Thread)


[info]_winnie
2006-06-25 10:38 pm UTC (link)
2.5 часов. И спаааать.

(Reply to this) (Parent)(Thread)


[info]_winnie
2006-06-25 10:44 pm UTC (link)
Ах да, я постарался обработать все ошибки. (Деление на ноль, парсинг, выход за пределы ячеек, и тд).

(Reply to this) (Parent)(Thread)


[info]thesz
2006-06-26 08:24 am UTC (link)
Для ошибок вычислений надо печатать #error как вычисленное значение.

Обработал-то все, но неправильно. ;)

(Reply to this) (Parent)(Thread)


[info]_winnie
2006-06-26 10:51 am UTC (link)
дык эта... Я печатаю #(описание ошибки). Так удобней.

(Reply to this) (Parent)(Thread)


[info]_winnie
2006-06-26 10:51 am UTC (link)
Или где-то не так работает?

(Reply to this) (Parent)(Thread)

(no subject) - [info]thesz, 2006-06-26 11:12 am UTC
(no subject) - [info]thesz, 2006-06-26 11:12 am UTC
(no subject) - [info]_winnie, 2006-06-26 11:25 am UTC
(no subject) - [info]_winnie, 2006-06-26 11:34 am UTC
(no subject) - [info]thesz, 2006-06-26 11:45 am UTC
(no subject) - [info]thesz, 2006-06-26 11:42 am UTC
(no subject) - [info]_winnie, 2006-06-26 11:53 am UTC
(no subject) - [info]thesz, 2006-06-26 12:10 pm UTC
(no subject) - [info]_winnie, 2006-06-26 12:32 pm UTC
(no subject) - [info]thesz, 2006-06-26 12:41 pm UTC
(no subject) - [info]_winnie, 2006-06-26 12:34 pm UTC
(no subject) - [info]thesz, 2006-06-26 12:42 pm UTC
(no subject) - [info]gaperton, 2007-10-29 11:21 am UTC
(no subject) - [info]_winnie, 2007-10-29 11:27 am UTC
(no subject) - [info]thesz, 2007-10-29 12:03 pm UTC
(no subject) - [info]_winnie, 2007-10-29 11:34 am UTC
(no subject) - [info]thesz, 2007-10-29 12:06 pm UTC

[info]thesz
2006-06-26 08:23 am UTC (link)
Не. Не выполнил. ;)

(Reply to this) (Parent)(Thread)


[info]_winnie
2006-06-26 11:37 am UTC (link)
По поводу "смотреть вперёд" -
http://boris-batkin.livejournal.com/8975.html
http://boris-batkin.livejournal.com/8847.html

(Reply to this) (Parent)(Thread)


[info]thesz
2006-06-26 11:45 am UTC (link)
Это же проблемы C++.

У меня таких проблем нет. ;)

(Reply to this) (Parent)(Thread)

(no subject) - [info]_winnie, 2006-06-26 11:55 am UTC
(no subject) - [info]thesz, 2006-06-26 11:58 am UTC

[info]_winnie
2006-06-26 11:43 am UTC (link)
Кстати, что такое "большие таблицы"?
Под этим подразумевается кеширование значений, что бы расчёт больших таблиц не был за (количество ячеек)^2 или таблицы, почти полностью из пустых ячеек?

(Reply to this) (Parent)(Thread)


[info]thesz
2006-06-26 11:58 am UTC (link)
Насколько я понял - последнее.

(Reply to this) (Parent)


[info]lomeo
2006-06-26 02:29 pm UTC (link)
Слушай, не по теме вопрос, а почему ты многие вещи не сокращаешь?

Например, не сразу с allindexes разобрался. Зачем то два (++) вместо одного? Да и сама задача прямо явно для неподвижной точки. Я не задумываясь (ну, чуть чуть привру ;-)) написал бы так:

allindexes = fix nextchars (map return chars)
    where
        chars  = ['A'..'Z']
        nextchars f pcs = pcs ++ f [x:xs | x <- chars, xs <- pcs]


Потом, несколько мест, явно предназначенных для Monad.ap. В одном месте ты не юзаешь update. Ты же вроде любишь функции очень высокого порядка? :-)

Я бы понял, если бы от сокращения они теряли в выразительности, понятности, читабельности, так ведь нет. Мм?

(Reply to this) (Thread)


[info]thesz
2006-06-26 02:43 pm UTC (link)
Эх. Грешен. ;)

Во-первых, я поступил, как десантник. Выучил несколько приемов и стал применять, оттачивая мастерство. ;)

Во-вторых, я пишу... Без оглядки, что-ли. По крайней мере, самый первый вариант. Потом могу и посокращать.

Правда, до применения фиксированной точки пока не доходил. ;)

Буду изучать. ;)

(Reply to this) (Parent)(Thread)


[info]lomeo
2006-06-26 02:54 pm UTC (link)
А.. Я думал тут какое то дзен знание :-)

(Reply to this) (Parent)


[info]_winnie
2006-06-26 10:34 pm UTC (link)
>Во-первых, я поступил, как десантник. Выучил несколько приемов и стал применять, оттачивая мастерство. ;)
Кунфу круче карате!

(Reply to this) (Parent)(Thread)


[info]thesz
2006-06-26 10:39 pm UTC (link)
Сможешь доказать в бою? ;)

Кстати, каратэ круче кунфу. Теперь это уже понятно с вполне объективной точки зрения.

(Reply to this) (Parent)(Thread)


[info]_winnie
2006-06-26 10:47 pm UTC (link)
Предлагаю отсортировать полугигабайтный файл строчек.
Или скининг на CPU. (вершины матрицами сблендить).

(Reply to this) (Parent)(Thread)

(no subject) - [info]thesz, 2006-06-26 11:30 pm UTC
(no subject) - [info]_winnie, 2006-06-26 11:36 pm UTC
evalexpr
[info]rvp74
2006-07-07 04:03 pm UTC (link)
== cut ==
evalexpr (CEBin op a b) = do
ar <- evalexpr a
case ar of
CError _ -> return ar
x -> evalbin op ar b
evalbin op ar b = case b of
CEBin op' a' b -> do
ar' <- evalexpr a'
evalbin op' (apply op ar ar') b
_ -> do
br <- evalexpr b
return $ apply op ar br

== cut ==

По-моему, перемудрил тут.
Я бы начал с такой структуры:

evalexpr (CEBin op a b) = do
v1 <- evalexpr a
v2 <- evalexpr b
return $ calc op v1 v2
where
calc op v1 v2 = do_calc v1 v2 where
do_calc CError _ = CError
do_calc _ CError = CError
do_calc (CInt i1) (CInt i2) = CInt (doBin op i1 i2)

А вообще на тему doCalc, надо придумать что-то типа
prj :: (SubType a) => Value -> a
inj :: (SuperType a) => a -> Value

Подобный подход я встречал в статье "Monad Transformers and Modular Interpretator"

(Reply to this) (Thread)

Re: evalexpr
[info]thesz
2006-07-07 04:16 pm UTC (link)
Я писал "как слышится." ;)

Сейчас интересней некий DSEL сделать. ;)

(Reply to this) (Parent)


Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…