倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

spire で多項式 Polynomial を使ってみる (4) : 係数・項を扱うメソッド

spire を使ってみるシリーズ(目次)。 今回は Polynomial 型に定義されているメソッドのうち、係数や項を扱うメソッドを見ていきます。 記事中のサンプルコードでは以下の import 分が書かれているものとします:

import spire.math._
import spire.implicits._

toDense, toSparse

spire で多項式 Polynomial を使ってみる (1) : ファクトリ・メソッド』で見たように、Polynomial 型の実装には係数を配列として持つもの(dense() メソッドで生成)と係数をマップとして持つもの(sparse() メソッドで生成)がありました。 一度オブジェクトを生成すれば大抵の場合は実装を気にする必要はないと思いますが、必要なら toDense もしくは toSparse を呼び出して実装を指定することもできます。

係数をコレクションとして扱うメソッド

係数を配列として取得するには coeffsArray 、マップとして取得するには data を用います:

  val p = Polynomial("x^3 + 2x^2 + 3")

  // coeffsArray で係数を配列として取得
  assert( p.coeffsArray.toList == List(r"3", r"0", r"2", r"1") )

  // data で係数をマップとして取得
  assert( p.data == Map(0 -> r"3", 2 -> r"2", 3 -> r"1") )  // 係数が0ならエントリに入らない

「r"1"」などは spire で有理数 Rational 型のリテラルを表します。 これらは Int 値で書いても上記のアサーションは通りますが、(次数ではなく)係数であることを明示するためにこの記法で書いています。

各係数に対して処理を行う場合は、コレクションに定義されているようなメソッドが使えます。

foreach() メソッドは、次数と係数のタプルに対して処理を行います。 処理は次数の低い方から行われるようです(dense の場合、配列の先頭から行われると思えば自然):

  val p = Polynomial("x^3 + 2x^2 + 3")

  // foreach() メソッド その1
  p.foreach((n, c) => println(s"${n}次の項の係数は${c}です。"))  // nは次数、cは係数
  // 【出力結果】
  // 0次の項の係数は3です。
  // 2次の項の係数は2です。
  // 3次の項の係数は1です。

係数が0の項があると、dense と sparse の実装に対して異なる結果になるので注意が必要です:

  val p = Polynomial("x^3 + 2x^2 + 3")

  // foreach() メソッド その2:dense に対して呼び出す
  p.toDense.foreach((n, c) => println(s"${n}次の項の係数は${c}です。"))
  // 【出力結果】
  // 0次の項の係数は3です。
  // 1次の項の係数は0です。
  // 2次の項の係数は2です。
  // 3次の項の係数は1です。

  // foreach() メソッド その3:spares に対して呼び出す
  p.toSparse.foreach((n, c) => println(s"${n}次の項の係数は${c}です。"))
  // 【出力結果】
  // 0次の項の係数は3です。
  // 2次の項の係数は2です。
  // 3次の項の係数は1です。

係数が0の1次の項に対して、dense の場合は処理が行われ、sparse の場合は行われません。 どちらの実装に対しても係数が0の項に対して処理を行いたくない場合は foreachNonZero() メソッドを使います:

  val p = Polynomial("x^3 + 2x^2 + 3")

  p.toDense.foreachNonZero((n, c) => println(s"${n}次の項の係数は${c}です。"))
  // 0次の項の係数は3です。
  // 2次の項の係数は2です。
  // 3次の項の係数は1です。

  p.toSparse.foreachNonZero((n, c) => println(s"${n}次の項の係数は${c}です。"))
  // 【出力結果】
  // 0次の項の係数は3です。
  // 2次の項の係数は2です。
  // 3次の項の係数は1です。

特に問題ないなら foreachNonZero() メソッドを使っておいた方がいい気がします(メソッド名が長いのが難点)。

map() メソッドは各係数に対して変換を行い、変換結果を係数とする Polynomial オブジェクトを返します:

  val p = Polynomial("x^3 + 2x^2 + 3")

  val dp: Polynomial[Double] = p.map(_.toDouble)
    // 係数が Double 値の Polynomial オブジェクトを返す
  assert( dp(2.0) == 19.0 )  // Double 値の等値評価はよくない

こちらは係数だけが引数の関数に渡され、次数は扱えません。

項のコレクションとして扱うメソッド

spire.math.poly パッケージには項を表す Term 型があり、Polynomial オブジェクトから Term オブジェクトを取得したり、Polynomial オブジェクトを Term オブジェクトのコレクションとして扱うメソッドがいくつか定義されています。

Term 型はコンパニオン・オブジェクトに apply() ファクトリ・メソッドが定義されていたり、「==」で次数と係数の等値評価ができたりと、直感的に使えます。

では Term を扱うメソッドを見ていきましょう。 まずは Term オブジェクトを取得するメソッド。 maxTerm, minTerm はそれぞれ最高次、最低次の項を取得します:

  import spire.math.poly.Term

  val p = Polynomial("x^3 + 2x^2 + 3")

  // maxTerm
  assert( p.maxTerm == Term(r"1", 3) )

  // minTerm
  assert( p.minTerm == Term(r"3", 0))

r"1", r"3" などは有理数 Rational 型のリテラルでした。

次は Polynomial オブジェクトを Term オブジェクトのコレクションとして扱うようなメソッド。 terms は項を Term の List として返します:

  import spire.math.poly.Term

  val p = Polynomial("x^3 + 2x^2 + 3")

  // terms
  assert( p.terms == List(Term(r"3", 0), Term(r"2", 2), Term(r"1", 3)) )

項の順序は昇順です。 係数が0の項は結果に含まれません(dense, sparse に関係なく)。 同様のメソッドとして Iterator を返す termsIterator というものもあります。 こちらも係数が0の項は含まれません。

mapTerms メソッドは各 Term オブジェクトに対して変換を施します。 変換を施した結果の Term オブジェクトをまとめて新たに Polynomial オブジェクトを生成します。 例えば多項式微分を行う変換を書いてみると

  import spire.math.poly.Term

  val p = Polynomial("x^3 + 2x^2 + 3")

  // mapTerms() メソッド
  val dp = p.mapTerms{
    // Term はパターン・マッチにも使える
    case Term(c, 0) => Term(r"0", 0)
    case Term(c, i) => Term(c*i, i-1)
  }

  assert( dp == Polynomial("3x^2 + 4x") )

のようになります*1。 変換を施した結果に同類項が出てくれば、きちんとまとめてくれます:

  import spire.math.poly.Term

  val p = Polynomial("x^3 + 2x^2 + 3")

  // 全ての項を係数をそのままにして2次の項にしてみる
  // 変換結果に同類項が含まれるとまとめられる
  assert( p.mapTerms(t => Term(t.coeff, 2)) == Polynomial("6x^2") )

Term 型は最初見るとちょっと抵抗感がありますが、使ってみるといろいろ便利な機能があるので、込み入った処理を行う場合には恐れず使っていった方がスッキリしたコードが書けそうですね。 Polynomial の apply() ファクトリ・メソッドにも Term オブジェクトのコレクションを受け取って Polynomial オブジェクトを生成するものがあります(以前の記事『spire で多項式 Polynomial を使ってみる (1) : ファクトリ・メソッド』に追記しました)。

今回までで Polynomial 型に定義されているメソッドは概ね見終わりました*2。 次回は、多項式系の特殊関数に関連する spire.math.poly.SpecialPolynomials を使ってみます。

Scalaスケーラブルプログラミング第3版

Scalaスケーラブルプログラミング第3版

*1:Polynomial の derivative はこのような実装になってないと思いますが。

*2:maxOrderTermCoeff メソッドは、今回扱おうと思っていたのですが以前の記事に入れた方が分類的に自然なので、『spire で多項式 Polynomial を使ってみる (2) : 数学関数としてのメソッド』に追記しました。