Haskell中的单项式替换

时间:2013-12-30 11:32:07

标签: regex haskell expression

我在Haskell中找到了一些关于字符串替换的例子,但是我没有找到一个在多项式中替换的例子,例如,

1 + x^2 + x^4 /. x^p_ -> f[p]应该返回1+f[2]+f[4]。这种替代是否由Haskell支持?

3 个答案:

答案 0 :(得分:5)

我建议不这样做吗? String实际上是我们消除没有结构的东西的数据类型,你在这里有一个实际的AST,所以解析成代表的东西会更容易。

这样的东西有很多nice个库。但如果你想自己做的话

data Term = Var String
          | Const Integer
          | Term :*: Term
          | Term :+: Term
          | Term :-: Term
          | Term :^: Term
          deriving(Eq, Show, Ord)
infixr 8 :^: -- Mimic (^) from prelude

instance Num Term where
  fromInteger = Const
  (*) = (:*:)
  (+) = (:+:)
  (-) = (:-:)

-- From Data.String
instance IsString Term where
  fromString = Var

现在,如果您打开语言扩展程序OverloadedStrings,则可以编写"x" * 4 + 2并返回Plus (Mult (Var "x") (Const 4)) (Const 2)

所以现在为你的问题,而不是使用正则表达式,使用善良的诚实模式匹配! 例如,您的规则是将x^p的每次出现转换为f p。现在让我们假设f是一些数学函数,可以给出类型Term -> Term。我们将从一个递归重写树的函数开始

 rewrite :: (Term -> Term) -> Term -> Term
 rewrite f c@(Const _) = f c
 rewrite f v@(Var _)   = f v
 rewrite f (t1 :*: t2) = f $ rewrite f t1 :*: rewrite f t2
 rewrite f (t1 :+: t2) = f $ rewrite f t1 :+: rewrite f t2
 rewrite f (t1 :-: t2) = f $ rewrite f t1 :-: rewrite f t2
 rewrite f (t1 :^: t2) = f $ rewrite f t1 :^: rewrite f t2

此功能只是自下而上重写术语树。现在你可能会

 powToF :: Term -> Term
 powToF ("x" :^: p) = f p
 powToF a           = a

 transform = rewrite powToF

那就是它。编写像这样的其他转换也相当容易。我会留给你如何打印一个术语(它不是太糟糕)并解析一个。

答案 1 :(得分:2)

在Haskell中这不是很难做到的;挑战更多地与你有兴趣利用什么类型的结构以及你将做出什么样的替代品有关。 Mathematica通过简单地选择某种结构和替换方法来模糊许多细节。我将探讨几个例子(警告,未经测试的代码)。

如果我们通常只对单项式替换感兴趣,那么可以通过将完全简化的多项式表示为系数列表来完成。

 a + b x^2 + c x^3 + d x^4 + ...
[a , b     , c     , d     , ... ]

我们可以用一个像

这样的函数来简单地替换单项式
type Coef  = Int
type Power = Int
type Poly  = [Coef]

replaceMonomial :: Power -> (Coef -> Poly) -> Poly -> Poly
replaceMonomial pow repl poly = zipWith (+) poly' (repl coef)
  where
    (poly', coef) = splitPoly pow poly [] 

    -- pulls a particular monomial out of a polynomial
    --
    -- >>> splitPoly 3 [1,2,3,4,5] []
    -- ([1,2,3,0,5], 4)
    splitPoly n []     acc = (reverse acc, 0)
    splitPoly 0 (x:xs) acc = (reverse acc ++ (0:xs), x)
    splitPoly n (x:xs) acc = splitPoly (n-1) xs (n:acc)

这种方法快速且规范,但显然我们在“字符串”上实际上已经获得了完整的多项式结构,并且我们只替换单个单项式。不过,它可以很容易地扩展到多单项替换。

在正规性的另一个方向上,我们也可以根据句法上的平等完全替换子树。

data Poly
  = X
  | Poly :*: Poly
  | Poly :+: Poly
  | Act Coef Poly
  deriving ( Eq )   -- important!

-- really inefficient!
replaceSubtree :: Poly -> Poly -> Poly -> Poly
replaceSubtree redex repl target
  | target == redex = repl
  | otherwise = case target of
    X         -> X
    p1 :*: p2 ->     replaceSubtree redex repl p1 
                 :*: replaceSubtree redex repl p2
    p1 :+: p2 ->     replaceSubtree redex repl p1 
                 :+: replaceSubtree redex repl p2
    Act c p   -> Act c $ replaceSubtree redex repl p

在这里,我们将表达式的任何语法子树视为潜在的“可简化表达式”(redex),并使用Haskell内置的结构,句法相等概念,我们搜索目标并尝试替换它们。这是非常低效的,因为每个相等性检查将遍历即将进入的整个子树,但这个想法就是这样。

这种方法的真正不足之处在于它取决于句法上的平等而不是指称的相等性 - 因为我们必须首先将多项式简化为标准形式,所以不存在单项替换的概念。给定一些执行该减少的函数,通过首先对输入多项式进行标准化来构建replaceStandardPolynomial很容易。

最后,有变量替换和捕获的概念。如果我们在多个变量上有一个多项式并希望用可能也包含变量的表达式来替换它们(使它们更加优化),那么我们将需要更多的机制。幸运的是,在编程语言中这是一种常见的操作,只有大量的方法。为此,我强烈建议您研究boundunification-fd库。

答案 2 :(得分:1)

Mathematica和Haskell有不同的哲学。

您在Mathematica中构建的多项式是一个由提供的运算符构建的白盒子。 Haskell的Data.Polynomial库中的多项式是抽象数据类型。您只能通过调用其接口来与抽象数据类型进行交互。在这种情况下,您可以使用coeffMap中的Data.Polynomial将多项式作为Map获取,然后计算地图中条目的总和。

Mathematica在表达式树上有不加区别的模式匹配。 Haskell有抽象,这意味着你不能“窥视”一般的任意值。它迫使您的操作尊重其结构的内部。考虑一下1 + x + x^2 + x^4 /. x^p_ -> f[p]上的操作。这是1+x+f[2]+f[4]。它没有注意到表达的含义。获得1+f[1]+f[2]+f[4]f[0]+f[1]+f[2]+f[4]会更自然,因为xx^1相同。在Mathematica中,可以用“x / .2 - > 3”来代替“3”表达式中的“2”,这对我来说是坦率的可能性。

Haskell故意没有这种类型的模式匹配。您不得不考虑计算领域,并使用尊重其结构的函数,或者如果您无法使用提供的组合器构建操作,则使用Haskell的模式匹配将其拆除。在您的情况下,您应该使用Data.Polynomial提供的接口,或者像其他人所说的那样构建您自己的表达式类型并为其提供替换函数。

我在Mathematica中深入遍历的最接近的事情就是Scrap你的样板库。请参阅hackage上的syb包和http://research.microsoft.com/en-us/um/people/simonpj/papers/hmap/hmap.ps的第2页。我认为这在这里没用。