mutable/dense 教程

本教程展示如何原地构造和更新单变量多项式:逐个设置系数、把和与积累加到同一个容器中、保留快照,并把结果交还给不可变代码。

快速开始

moon add Luna-Flow/luna-poly@0.2.0
import {
  "Luna-Flow/luna-poly/mutable",
}
test "mutable dense quick start" {
  let p = @mutable.DensePolynomial::from_coefficients([1, 2, 3])
  p.set_coefficient(1, 5)
  inspect(p, content="1 + 5x^1 + 3x^2")
}

set_coefficient(1, 5) 直接改变 p 本身中 xx 的系数。

日常任务

逐个填入系数

从零开始,设置你已知的系数;数组会按需增长:

test "filling" {
  let p : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::zero()
  for k in 0..<5 {
    p.set_coefficient(k, k * k)
  }
  inspect(p, content="1x^1 + 4x^2 + 9x^3 + 16x^4")
  p.set_coefficient(4, 0)
  debug_inspect(p.degree(), content="Some(3)")
}

把首系数设为零会降低次数,因为多项式始终保持规范形式。

累加到同一个容器中

add_inplace 和 mul_inplace 更新接收者,使循环中不产生中间值:

test "accumulate" {
  let x_plus_1 = @mutable.DensePolynomial::from_coefficients([1, 1])
  let product : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::one()
  let sum : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::zero()
  for _ in 0..<3 {
    product.mul_inplace(x_plus_1)
    sum.add_inplace(product)
  }
  inspect(product, content="1 + 3x^1 + 3x^2 + 1x^3")
  inspect(sum, content="3 + 6x^1 + 4x^2 + 1x^3")
}

sum 为 (x+1)+(x+1)2+(x+1)3(x+1) + (x+1)^2 + (x+1)^3。

保留快照

在进一步修改之前,copy 和 to_immut 会取得独立的快照:

test "snapshots" {
  let p = @mutable.DensePolynomial::from_coefficients([1, 1])
  let saved = p.copy()
  let frozen = p.to_immut()
  p.scale_inplace(2, 10)
  inspect(p, content="10x^2 + 10x^3")
  inspect(saved, content="1 + 1x^1")
  inspect(frozen, content="1 + 1x^1")
}

运算符不会修改

需要新值时请使用运算符;操作数保持不变:

test "operators" {
  let p = @mutable.DensePolynomial::from_coefficients([1, 2])
  let q = p * p + p
  inspect(q, content="2 + 6x^1 + 4x^2")
  inspect(p, content="1 + 2x^1")
}

深入了解

可变地构造,不可变地发布

在可变缓冲区中完成增量工作,然后返回不可变值,使调用者获得值语义:

fn truncated_exp_numerators(n : Int) -> @immut.DensePolynomial[Int] {
  // n! * (1 + x + x^2/2! + ... + x^n/n!)
  let buffer : @mutable.DensePolynomial[Int] = @mutable.DensePolynomial::zero()
  let mut falling = 1
  for k = n; k >= 0; k = k - 1 {
    buffer.set_coefficient(k, falling)
    falling = falling * (if k == 0 { 1 } else { k })
  }
  buffer.to_immut()
}

test "publish" {
  inspect(truncated_exp_numerators(3), content="6 + 6x^1 + 3x^2 + 1x^3")
}

跨两层的泛型代码

操作记录和能力 trait 对可变类型和不可变类型具有相同的形态:

fn[P : @mutable.UnivariatePolynomial] is_linear(p : P) -> Bool {
  @mutable.HasDegree::degree(p) == Some(1)
}

test "generic" {
  inspect(is_linear(@mutable.DensePolynomial::from_coefficients([0, 3])), content="true")
  inspect(is_linear(@immut.DensePolynomial::from_coefficients([1, 0, 2])), content="false")
}

重置与复用

clear(也是 @mutable.Clearable::clear)把缓冲区重置为零以便复用:

test "reuse" {
  let buffer = @mutable.DensePolynomial::from_coefficients([4, 5])
  @mutable.Clearable::clear(buffer)
  assert_true(buffer.is_zero())
  buffer.set_coefficient(2, 1)
  inspect(buffer, content="1x^2")
}

常见陷阱

  • 共享容器。let q = p 使 q 成为同一个容器;修改 q 会改变 p。请使用 p.copy()。
  • 没有带检查的 setter。set_coefficient 遇到负的幂次时中止(abort)。
  • 求导需要 Float、Double 或 BigInt 系数,与不可变类型相同。
  • 委托的操作会进行转换。pow、substitute 和 karatsuba 会把系数复制到不可变类型再复制回来;与操作本身相比这很廉价,但并非没有代价。

后续步骤