リストを効率的に使う(簡単)
より慣用的なものにする簡単な方法は、逆にstack
することです (これにより、真のスタックに少し似たものになります)。これにより、パターン マッチングを使用でき、リストの先頭でのアタッチ/読み取りがO(1)
操作であるため、コードがより効率的になります。最後にそうしている間、O(n)
すなわち:
solveRPN :: (Read a, Integral a) => [String] -> [String] -> a
solveRPN [] (x:[]) = read x
solveRPN [] (x:y:xs) = solveRPN (y:x:[]) xs -- push them on backwards
solveRPN stack@(s1:s2:rest) (x:xs) -- pattern match on stack with length >= 2
| isOperator x =
let z = calcFunction x s2 s1
in solveRPN rest (z:xs)
| otherwise = solveRPN (x:stack) xs
solveRPN stack (x:xs) -- last case: list with a single element
| isOperator x = error "Stack too small"
| otherwise = solveRPN (x:stack) xs
型システムを使用する (重要!)
文字列を使用して、入力スタックで可能な 2 つの型をカプセル化しています: 操作と数値。これにより、無効な入力が可能になります。慣用的な Haskell は型システムを活用して、関数が常に定義された結果を持つようにします (つまり、すべての関数が合計になるように)。 .
ADT (代数データ型) を使用して可能な操作をカプセル化Either
し、タイプセーフな方法で操作とリテラルを区別することをお勧めします。
data Operation = Plus | Minus | Times | Divide
-- conversion function
readStackVar :: (Read a) => String -> Either Operation a
readStackVar "+" = Left Plus
readStackVar "-" = Left Minux
readStackVar "*" = Left Times
readStackVar "/" = Left Divide
readStackVar other = Right . read $ other
これにより、isOperator
うまく書くことができます (実際には不要ですが):
isOperator :: Either Operation a -> Bool
isOperator (Left _) = False
isOperator _ = True
calcFunction
はタイプセーフになり (型には がないことに注意してくださいEither
)、認識されない操作の可能性はありません:
calcFunction :: (Integral a) => Operation -> a -> a -> a
calcFunction Plus = (+)
calcFunction Minus = (-)
calcFunction Times = (*)
calcFunction Divide = div -- integral division
これらを使用して書き直すことができます( where 句を使用してsolveRPN
空のリストを渡す必要がなくなったことに注意してください)。solveRPN
solveRPN :: (Read a, Integral a) => [Either Operation a] -> a
solveRPN xs = go [] xs
where
go :: (Read a, Integral a) => [a] -> [Either Operation a] -> a
go [] [] = error "Empty"
go (x:xs) [] = x -- finished all the input
go [] (x:y:xs) = go (y:x:[]) xs -- start the stack in reverse order
go (s1:s2:rest) ((Left op):xs) = go ((calcFunction op s1 s2) : rest) xs -- operation
go stack ((Right x):xs) = go (x:stack) xs -- literal
Left
とRight
でパターン マッチングを使用して、Either
操作とリテラルを区別できることに注目してください。(さらにできる (そしてすべき) ことは、 return を作成して、 solveRPN
returnMaybe a
でエラーを示し、 returnNothing
で成功を示すことができるようにすることJust x
です。)
これは次のように使用されます。
solveRPN . map readStackVar . words $ "1 2 + 3 *"
(警告: 私はこれを実際にテストしていないので、タイプミスや小さな論理エラーがあるかもしれません)