まず自己紹介
- 元ものづくりエンジニア🔧
- 独学でReact、Railsを使った開発をしてる人
はじめに
Reactに慣れてきたので趣味である数学を使って遊んでみました。
今回は任意の数字を入力すると既約ピタゴラス数を作ってくれるツールをつくってみました。
本記事はこちらの方が対象です。
- Reactの初心者
- Reactを復習したい方
- 数学とプログラミングが好きな方
既約ピタゴラス数生成マシン
まずどんなものかお見せしたいと思います。見た目は少々悪いですがご了承下さい🙏
二つの入力欄に任意の数字を打ち込んでCREATEを入力すると、既約ピタゴラス数の組(a,b,c)と直角三角形の外形を出力してくれます。
既約ピタゴラス数とは
そもそもピタゴラス数ってなんだっけ?となっている方もいると思います。
ピタゴラス数とは直角三角形の辺となる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
するとブラウザが起動し、下のようなページが表示されるはずです。
また今回はMaterial-UIと呼ばれるUIコンポーネントライブラリとreact-springというアニメーションライブラリを使っていきます。詳しくはMaterial-UIのHPとreact-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
コンポーネント
また、state
でs,t,a,b,c
を状態管理します。初期値は
$$s=1,\ t=3,\ a=3,\ b=4,\ c=5$$
とします。
ではまずプロジェクトのsrc/App.js
を開いて以下のように変更してください。
state
を設定してForm.jsx
、Length.jsx
、Triangle.jsx
のコンポーネントをとりあえず読み込んでいます。
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.jsx
とTriangle.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のTextField
とButton
コンポーネントを読み込んでいます。また、useStyles
はTextField
とButton
コンポーネントのスタイルを決定しています。
TextField
とButton
コンポーネントをみてみるとonChange
属性にprops.handleChange
、onClick
属性にprops.calculate
が渡されています。これは後にApp.js
でコンポーネントを使用するときに、数字が入力されるとhandleChange()
関数が渡されるように属性を設定しています。props.calculate
も同様です。これらの関数は後にApp.js
で定義します。
Length.jsx
関数コンポーネント
続いて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.a
をprops.a
に変更すればいいです。
Triangle.jsx
関数コンポーネント
引き続きTriangle.jsx
コンポーネントを作成します。これもLength.jsx
と同様、react-springを使っていきます。
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
属性を設けましたが、ここにその関数を渡してあげます。
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
オブジェクトを受け取ってs
とt
にsetState
します。これにより入力された値に随時変更されます。
calculate
関数はstate
のs
とt
から計算しa,b,c
を算出します。
これら関数はForm.jsx
コンポーネントのhandleChange
とcalculate
属性に渡されます。
ちなみにconstructor(props){...}
内に
this.handleChange = this.handleChange.bind(this)
this.calculate = this.calculate.bind(this)
が追加されていますが、これがないとエラーが出ます。コンポーネント内にイベントバンドラを渡す場合にはこのようにバインドするようにしましょう。
ここでターミナルで以下を実行し、ローカル環境で動作を確認してみてください。
$npm start
入力してみるとa,b,c
が出力され、直角三角形が変形したことを確認できると思います。
3.バリデーションを追加
ここまでで一応a,b,c
は出てきます。しかしお気づきの方もいらっしゃると思いますが、このままでは偶数や互いに素ではない数も入力できてしまいます。
そこでバリデーションを追加しましょう。App.js
のcalculate
関数内でswitch(){...}
構文で条件分岐します。
するとこうなります。
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や数学に関する記事をアップしていきたいと思います!