如何将要执行的一系列可选操作链接到"variable"上?



是否有一些模式可以减少ifs的重复和一致使用?

让我解释一下。一些算法本质上是程序性的,依赖于随着时间的推移修改变量/对象/数组的状态。在Haskell中做到这一点并不困难,我们只是为每个步骤声明一个不同的变量。问题是,有时我们只想在变量满足条件时更改该变量的状态,这通常需要大量的ifcases。

因此,例如,考虑以下相当丑陋的代码段:

import           Data.List                              (foldl')
import qualified Data.Sequence as Seq
import           Data.Sequence                          ((|>))
sequencialOps :: Int -> (Int, Seq.Seq (Either Bool Int))
sequencialOps init = foldl' manySteps (init, Seq.empty) [0..init]
where
manySteps (val, valseq) time = if time `mod` 2 == 0
then intermediateSteps (2*val-9)
else (val, valseq)
where
intermediateSteps someNum = (newnum3-1, numseq3 |> Right (newnum2-1))
where
(newnum1, numseq1) = if someNum > 10 || someNum < 12
then (-someNum `div` 3, valseq |> Left True)
else (someNum, valseq)
(newnum2, numseq2) = if someNum `mod` 3 == 0
then (-(someNum `mod` 970), numseq1 |> Right newnum1)
else (newnum1, numseq1)
(newnum3, numseq3) = if someNum == 0
then (time, numseq2 |> Left False)
else (newnum2, numseq2)
main = print $ sequencialOps 6

manySteps多次运行相同的过程。manySteps修改我传递的值(val(只是偶尔(在特定条件下(。

那么,有一些自定义的monad或聪明的方法来编写上面的代码,这样它就不会过度使用ifs(或cases(?

我在想也许解决方案是使用Statemonad,但我不确定这是否是最好的解决方案。

编辑:

上面的虚拟程序只是更长代码的一个示例。我实际上想写的是高斯-乔丹算法的一个小实现。

这是我的实现(使用safe,hmatrix和容器(:

import           Data.List                              (foldl')
import qualified Data.Sequence as Seq
import           Data.Sequence                          ((|>))
import           Data.Maybe                             (fromJust)
import           Data.Foldable                          (toList)
import           Safe.Exact as SP
import           Data.Semigroup                         ((<>))
import           Numeric.LinearAlgebra.Data hiding (toList, (|>))
main = do
let matrix = (2 >< 3) [1,2,3,4,5,6] :: Matrix Double
print matrix
print $ gaussJordan matrix
pure ()
data GJOperations =
MutiplyRowBy Int Double       -- ^ Row and Value to multiply
| ExchangeRows Int Int          -- ^ Row 1 and Row 2
| AddMulRowToRow Double Int Int -- ^ Value to multipy to Row 1 to add to Row 2
deriving (Show)
exchangeRows :: Int -> Int -> Matrix Double -> Maybe (Matrix Double)
exchangeRows i j mat = fromRows <$> newRows
where asRows = toRows mat
(i',j') = if i<j then (i,j) else (j,i)
newRows = do
(rws1, rest1) <- SP.splitAtExactMay i' asRows
(rowI, rest2) <- SP.splitAtExactMay 1 rest1
(rws2, rest3) <- SP.splitAtExactMay (j'-i'-1) rest2
(rowJ, rws3)  <- SP.splitAtExactMay 1 rest3
pure $ rws1 <> rowJ <> rws2 <> rowI <> rws3
multiplyRowby :: Double -> Int -> Matrix Double -> Maybe (Matrix Double)
multiplyRowby val i mat = fromRows <$> newRows
where asRows = toRows mat
cols = size (head asRows)
newRows = do
(rws1, rest1)  <- SP.splitAtExactMay i asRows
([rowI], rws2) <- SP.splitAtExactMay 1 rest1
pure $ rws1 <> [konst val cols * rowI] <> rws2
addMulRowToRow :: Double -> Int -> Int -> Matrix Double -> Maybe (Matrix Double)
addMulRowToRow val i j mat = fromRows <$> newRows
where asRows = toRows mat
(i',j') = if i<j then (i,j) else (j,i)
cols = size (head asRows)
newRows = do
(rws1, rest1)   <- SP.splitAtExactMay i' asRows
([rowI], rest2) <- SP.splitAtExactMay 1 rest1
(rws2, rest3)   <- SP.splitAtExactMay (j'-i'-1) rest2
([rowJ], rws3)  <- SP.splitAtExactMay 1 rest3
pure $ if i < j
then rws1 <> [rowI] <> rws2 <> [rowJ + konst val cols * rowI] <> rws3
else rws1 <> [rowI + konst val cols * rowJ] <> rws2 <> [rowJ] <> rws3
gaussJordan :: Matrix Double -> (Matrix Double, [GJOperations])
gaussJordan mat = ((a,_,o)->(a, toList o)) $ foldl' simplifyCol (mat, 0, Seq.empty) [0..cols-1]
where
(rows, cols) = size mat
simplifyCol nochange@(mat', row, gjops) col = case newrow of
Just nrow -> calculateSteps nrow
Nothing   -> nochange
where
newrow = findRow row
findRow i
| i == rows = Nothing
| colcontent ! i /= 0 = Just i
| otherwise = findRow (i+1)
colcontent = toColumns mat' !! col
calculateSteps nrow = (newmat2, row+1, newgjops2)
where
valCurrentRow = colcontent ! nrow
-- Assuming I get always a Just, I think I always get it but it is nonetheless awful
createUnwrap f = fromJust . f
nextvalues = if valCurrentRow == 1
then (mat', gjops)
else (createUnwrap (multiplyRowby (1/valCurrentRow) nrow) mat', gjops |> MutiplyRowBy nrow (1/valCurrentRow))
(newmat, newgjops) = foldl' calculateRowStep nextvalues [r | r<-[0..rows-1], r/=nrow]
(newmat2, newgjops2) = if row == nrow
then (newmat, newgjops)
else (createUnwrap (exchangeRows row nrow) newmat, newgjops |> ExchangeRows row nrow)
calculateRowStep orig@(mat'', gjops') r =
let valOtherRow = colcontent ! r
change = (- valOtherRow)
newmat' = createUnwrap (addMulRowToRow change nrow r) mat''
in if valOtherRow == 0.0
then orig
else (newmat', gjops' |> AddMulRowToRow change nrow r)

代码有效,但有点难以理解,范围界定级别太多,ifcaseT_T

是的,您可以使用monads编写外观更干净的代码来修改状态:

sequentialOps :: Int -> (Int, Seq.Seq (Either Bool Int))
sequentialOps init = run (traverse_ manySteps [0..init])
where
manySteps time = when (even time) $ do
val <- get
let someNum = 2*val-9
when (someNum > 10 || someNum < 12) $ do
put (-someNum `div` 3)
log (Left True)
when (someNum `mod` 3 == 0) $ do
gets Right >>= log
put (-(someNum `mod` 970))
num2 <- get
when (someNum == 0) $ do
log (Left False)
put time
modify pred
log (Right (num2-1))
log = tell . Seq.singleton
run = runWriter . flip execStateT init

然而,一般来说,许多看似"固有程序化"的算法并没有在实践中。例如,在这种情况下:您反复附加到序列。你能用展开代替吗?

代码中也有几个多余的条件,例如someNum > 10 || someNum < 12总是正确的。另外,我认为log (Left False)从来没有被召唤过。使用这些要点,您可以简化一点:

sequentialOps :: Int -> (Int, Seq.Seq (Maybe Int))
sequentialOps init = run (replicateM_ (1 + init `div` 2) manySteps)
where
manySteps = do
val <- get
let someNum = 2*val-9
put (-someNum `div` 3)
log Nothing
when (someNum `mod` 3 == 0) $ do
gets Just >>= log
put (-(someNum `mod` 970))
modify pred
gets Just >>= log
log = tell . Seq.singleton
run = runWriter . flip execStateT init

您究竟想用这段代码做什么?如果我有一个想法,我可以更容易地向你展示如何将其转换为功能风格。

最新更新