0
0

More than 3 years have passed since last update.

【React&数学】既約ピタゴラス数生成マシンをつくってみた

Posted at

まず自己紹介

  • 元ものづくりエンジニア🔧
  • 独学でReact、Railsを使った開発をしてる人

はじめに

Reactに慣れてきたので趣味である数学を使って遊んでみました。
今回は任意の数字を入力すると既約ピタゴラス数を作ってくれるツールをつくってみました。
本記事はこちらの方が対象です。

  1. Reactの初心者
  2. Reactを復習したい方
  3. 数学とプログラミングが好きな方

既約ピタゴラス数生成マシン

まずどんなものかお見せしたいと思います。見た目は少々悪いですがご了承下さい🙏
二つの入力欄に任意の数字を打ち込んでCREATEを入力すると、既約ピタゴラス数の組(a,b,c)と直角三角形の外形を出力してくれます。

スクリーンショット 2021-04-06 9.59.45.png

既約ピタゴラス数とは

そもそもピタゴラス数ってなんだっけ?となっている方もいると思います。
ピタゴラス数とは直角三角形の辺となる3つの整数の組です。例えば$(3,4,5)$が該当します。三平方の定理で馴染み深い方が多いのではないでしょうか。

$$ a^2 + b^2 = c^2$$

ピタゴラス数を見つけることはそんなに難しいことはありません。一つの組みを発見しさえすれば3つの数に同じ数をかければいくらでも生成できます。しかしこれは最初に発見したピタゴラス数の直角三角形を拡大したものに過ぎず、面白みがありません。

そこで既約ピタゴラス数の登場です。既約ピタゴラス数とは共通因数をもたないピタゴラス数です。
これを見つけるのは少し難しそうですが、次の式を使って無限に作ることができます。

$$a=st b=\frac{t^2-s^2}{2} c=\frac{t^2+s^2}{2}$$

ここで$s,t$は$1\leq{s}<{t}$を満たす互いに素な奇数です。証明は中学数学でできるので興味のある方は証明してみてください!

実装

1.環境構築

さっそくReactで実装したいと思います。まずはNode.jsがインストールされていることを確認してください。
ご自身のPCでターミナルを開いて以下を実行してください。

$ node -v
v14.16.0

バージョンが出力されればインストールされています。

次に任意のディレクトリに移動して下記を実行します。名前は各自任意で構いません。

$npx create-react-app pythagoras

無事完了するとReactのプロジェクトの環境構築が完了します。
お使いのエディタでプロジェクトを開いてみて、ターミナルで以下を実行してみてください。

$npm start

するとブラウザが起動し、下のようなページが表示されるはずです。
スクリーンショット 2021-04-06 9.04.53.png

また今回はMaterial-UIと呼ばれるUIコンポーネントライブラリとreact-springというアニメーションライブラリを使っていきます。詳しくはMaterial-UIのHPreact-springのHPから。
デザインやアニメーションはどうでもいいという方は省いても結構です。

では以下をターミナル上で実行してインストールしてください。

$npm install --save @material-ui/core
$npm install react-spring

これでMaterial-UIとract-springのコンポーネントが使用できます。

2.コンポーネントの作成

まずは設計。srcディレクトリ配下のApp.jsに以下の3つのコンポーネントを配置するようにします。(これも名前は任意で結構です。)

  • s,tの入力や既約ピタゴラス数生成ボタンを配置するForm.jsxコンポーネント
  • a,b,cの解を表示するLength.jsxコンポーネント
  • 直角三角形の外見を表示するTriangle.jsxコンポーネント

また、states,t,a,b,cを状態管理します。初期値は
$$s=1,\ t=3,\ a=3,\ b=4,\ c=5$$
とします。

ではまずプロジェクトのsrc/App.jsを開いて以下のように変更してください。
stateを設定してForm.jsxLength.jsxTriangle.jsxのコンポーネントをとりあえず読み込んでいます。

src/App.js
import React from 'react'
import Form from './components/Form'
import Length from './components/Length'
import Triangle from './components/Triangle'
import './App.css'

class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      s:1,
      t:3,
      a:3,
      b:4,
      c:5
    }
  }

  render(){
    return(
      <div>
        <h3>互いに素な奇数s,tを入力してください(1≦s<t)</h3>
        <Form />
        <Length />
        <Triangle />
      </div>
    )
  }
}

export default App;

Form.jsx関数コンポーネント

まずForm.jsxからつくっていきます。srcディレクトリ配下にcomponentsディレクトリを作成し、その配下にForm.jsxを以下のように作成してください。後に記述するLength.jsxTriangle.jsxも同様に作成しておいてください。

src/components/Form.jsx
import React from 'react'
import { makeStyles } from '@material-ui/core/styles';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';

const useStyles = makeStyles((theme) => ({
  root: {
    '& > *': {
      margin: theme.spacing(1),
      width: '25ch',
    },
  },
}));


const Form = (props) => {
    const classes = useStyles();

    return(
        <form className={classes.root} noValidate autoComplete="off">
            <TextField name="s" label="sを入力" variant="outlined" onChange={props.handleChange}/>
            <TextField name="t" label="tを入力" variant="outlined" onChange={props.handleChange}/>
            <Button variant="contained" color="primary" href="#contained-buttons" onClick = {props.calculate}>
                CREATE
            </Button>
        </form>
    )
}

export default Form

Form.jsxコンポーネントは関数コンポーネントにして、Material UIのTextFieldButtonコンポーネントを読み込んでいます。また、useStylesTextFieldButtonコンポーネントのスタイルを決定しています。

TextFieldButtonコンポーネントをみてみるとonChange属性にprops.handleChangeonClick属性にprops.calculateが渡されています。これは後にApp.jsでコンポーネントを使用するときに、数字が入力されるとhandleChange()関数が渡されるように属性を設定しています。props.calculateも同様です。これらの関数は後にApp.jsで定義します。

Length.jsx関数コンポーネント

続いてLength.jsxコンポーネントを作成します。以下のように記述してください。

src/components/Length.jsx
import React from 'react'
import {useSpring, animated} from 'react-spring'

const Length = (props) => {
    const lengthValue = useSpring({
        a:props.a,
        b:props.b,
        c:props.c,
        config:{precision:1}
    })
    return(
        <div>
            <p>a=<animated.span>{lengthValue.a}</animated.span></p>
            <p>b=<animated.span>{lengthValue.b}</animated.span></p>
            <p>c=<animated.span>{lengthValue.c}</animated.span></p>
        </div>

    )
}

export default Length

react-springのanimatedコンポーネントでアニメーションを与えています。これにより数字の推移が連続的に変化していることを確認できます。
※react-springを使用しない方はlengthValue.aprops.aに変更すればいいです。

Triangle.jsx関数コンポーネント

引き続きTriangle.jsxコンポーネントを作成します。これもLength.jsxと同様、react-springを使っていきます。

src/components/Triangle.jsx
import React from 'react'
import {useSpring, animated} from 'react-spring'

const Triangle = (props) => {
    const useStyle = useSpring({
        width: 0,
        height: 0,
        borderRight: `${props.a*10}px solid transparent`,
        borderBottom: `${props.b*10}px solid lightblue`,
        marginLeft: '100px'
    })

    return(
        <animated.div style={useStyle}></animated.div>
    )
}

export default Triangle

直角三角形を描くスタイルを作ってanimatedコンポーネントのstyle属性に渡しています。
またborderRight:props.aを入れてthis.state.aを渡せるようにしています。borderBottom:も同様です。

App.jsクラスコンポーネント

子コンポーネントの準備ができたので、親コンポーネントApp.jsを作っていきます。
まずhandleChange関数とcalculate関数を作成します。先に作成したForm.jsxコンポーネントにprops.handleChange属性とprops.calculate属性を設けましたが、ここにその関数を渡してあげます。

src/App.js
import React from 'react'
import Form from './components/Form'
import Length from './components/Length'
import Triangle from './components/Triangle'
import './App.css'


class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      s:1,
      t:3,
      a:3,
      b:4,
      c:5
    }
    this.handleChange = this.handleChange.bind(this)
    this.calculate = this.calculate.bind(this)
  }

  handleChange = (event) => {
    this.setState({
      [event.target.name]:parseInt(event.target.value,10)
    })
  }

  calculate = () => {
    this.setState({
      a:this.state.s*this.state.t,
      b:(this.state.t**2-this.state.s**2)/2,
      c:(this.state.t**2+this.state.s**2)/2
    })
  }

  render(){
    return(
      <div>
        <h3>互いに素な奇数s,tを入力してください(1≦s<t)</h3>
        <Form handleChange={this.handleChange} calculate={this.calculate} />
        <Length a={this.state.a} b={this.state.b} c={this.state.c}/>
        <Triangle a={this.state.a} b={this.state.b}/>
      </div>

    )
  }

}

export default App;

handleChange関数はeventオブジェクトを受け取ってstsetStateします。これにより入力された値に随時変更されます。
calculate関数はstatestから計算しa,b,cを算出します。
これら関数はForm.jsxコンポーネントのhandleChangecalculate属性に渡されます。

ちなみにconstructor(props){...}内に

    this.handleChange = this.handleChange.bind(this)
    this.calculate = this.calculate.bind(this)

が追加されていますが、これがないとエラーが出ます。コンポーネント内にイベントバンドラを渡す場合にはこのようにバインドするようにしましょう。

ここでターミナルで以下を実行し、ローカル環境で動作を確認してみてください。

$npm start

入力してみるとa,b,cが出力され、直角三角形が変形したことを確認できると思います。

3.バリデーションを追加

ここまでで一応a,b,cは出てきます。しかしお気づきの方もいらっしゃると思いますが、このままでは偶数や互いに素ではない数も入力できてしまいます。
そこでバリデーションを追加しましょう。App.jscalculate関数内でswitch(){...}構文で条件分岐します。
するとこうなります。

src/App.js
import React from 'react'
import Form from './components/Form'
import Length from './components/Length'
import Triangle from './components/Triangle'
import './App.css'


class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      s:1,
      t:3,
      a:3,
      b:4,
      c:5
    }

    this.handleChange = this.handleChange.bind(this)
    this.calculate = this.calculate.bind(this)
  }

  handleChange = (event) => {
    this.setState({
      [event.target.name]:parseInt(event.target.value,10)
    })
    event.preventDefault()
  }

  calculate = () => {
    const reg = new RegExp(/^[0-9]*$/)
    const gcd = (p,q) => {
      if (q === 0){
        return p
      }
      return gcd(q, p%q)
    }

    switch(true){
      case(this.state.s<1||this.state.t<1):
        alert('sとtは1以上の整数です!')
        break;
      case(!(reg.test(this.state.s))||!(reg.test(this.state.t))):
        alert('sとtは半角数字で入力してください!')
        break;
      case(this.state.s>=this.state.t):
        alert('sはtより小さくしてください!')
        break;
      case((this.state.s%2) === 0 && (this.state.t%2) === 0):
        alert('sとtが偶数になってます!')
        break;
      case((this.state.s%2) === 0):
        alert('sが偶数になってます!')
        break;
      case((this.state.t%2) === 0):
        alert('tが偶数になってます!')
        break;
      case(gcd(this.state.t,this.state.s)!==1):
        alert('sとtが互いに素ではありません!')
        break;
      default:
        this.setState({
          a:this.state.s*this.state.t,
          b:(this.state.t**2-this.state.s**2)/2,
          c:(this.state.t**2+this.state.s**2)/2
        })
        break;
    }
  }

  render(){
    return(
      <div>
        <h3>互いに素な奇数s,tを入力してください(1≦s<t)</h3>
        <Form handleChange={this.handleChange} calculate={this.calculate} />
        <Length a={this.state.a} b={this.state.b} c={this.state.c}/>
        <Triangle a={this.state.a} b={this.state.b}/>
      </div>
    )
  }

}

export default App;

これにより「$s,t$は$1\leq{s}<{t}$を満たす互いに素な奇数」を満たしていない値を入力するとアラートが出るようになります。
ここでregは半角数字か判定を行うオブジェクトです。
またgcd()関数が追加されていますが、これはs,tの最大公約数を計算する再帰関数で、最大公約数が1でないものは認めないようにしています。

ここで改めてローカル環境で確認してください。不適な値を入力するとアラートが出てくると思います。

$npm start

まとめ

Reactのコンポーネントやstate、さらにはMaterial-UIやreact-springを使って、既約ピタゴラス数を生成する簡単なアプリを実装しました。React初心者の方、復習したい方のお力になれば幸いです🙇‍♂️
今後もReactや数学に関する記事をアップしていきたいと思います!

参考

  1. Material-UIホームページ
  2. react-springホームページ
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0