チューリングマシンは大雑把に言えば、無数の一列に並んだデータを持つテープとそのテープ中のデータを読み書きできて移動もできるヘッダ、から成る抽象的な機械です。それらしいもの(厳密には違う)が後述のデモです。
チューリングマシン – Wikipedia
React は JavaScript で作られた HTML、JavaScript に加えて CSS も載せられる React で web ページ、web サイトのフロントすべてを行える様なライブラリです。様々なことがすぐに整理して出来るため web アプリ的な案件もよく React で実装されます。
React – ユーザインターフェース構築のための JavaScript ライブラリ
時系列データを扱い、あるタイミングにおける状態をピンポイントで画面上に反映するといった要件において、チューリングマシンのモデルはこれのデータ構造とアルゴリズムを整理するために大いに役に立ってくれます。時系列順に並んだデータを持つテープと時刻を指すヘッダ、といった具合です。この手のものを扱うために React でフックに抽象したコードとそれを使ったデモが次です。
デモで使ったフックのソースコードが次です。例では単に数字でしたが、構造体データでもクラスでも問題なく扱えます。
import { useState } from "react";
type useTuringTapeRet<T> = {
current: () => T;
next: () => null | T;
prev: () => null | T;
extendTape: (newData: T) => void;
addNextCellWithOverrideCursorIncrement: (cell: T) => void;
reset: (initItem: T) => void;
tape: T[];
cursor: number;
};
/**
* チューリングマシンテープ的な状態管理をするフックです。
* 時系列準等の順番のある状態管理で特に便利です。
* 例えば Undo, Redo 機能のあるシステムです。
* do された時の状態をテープに保持することで Undo, Redo 時にテープのカーソルを動かすだけで直前、直後の状態に遷移できます。
* @param {T[]} initTape テープの初期状態
*/
export const useTuringTape = <T>(initTape?: T[]): useTuringTapeRet<T> => {
/** 現在表示している状態を指すカーソル */
const [cursor, setCursor] = useState<number>(
initTape ? initTape.length - 1 : -1
);
/** 保持するテープ */
const [tape, setTape] = useState<T[]>(initTape || []);
/** テープとカーソルをまっさらにします。axios 等の後から得た非同期データで後から実質的初期化を行うときに便利です */
const reset = (initItem: T): void => {
setTape([initItem]);
setCursor(0);
};
/** 現在状態を返します */
const current = (): T => tape[cursor];
/** 新しい要素を増やしてテープの先端を伸ばします */
const extendTape = (newData: T): void => setTape([...tape, newData]);
/**
* 現在のカーソルよりも先にテープが余っているならカーソルを先に移動して、移動先要素を返します。
* Undo、Redo用状態管理の例では Redo に対応します
*/
const next = (): null | T => {
const nextCursor = cursor + 1;
if (tape[nextCursor] === undefined) {
// これ以上先がないならば
return null; // no action
}
setCursor((p) => p + 1);
return tape[nextCursor];
};
/**
* 現在のカーソルがもっとも手前でないならばカーソルを手前に移動して、移動先要素を返します
* Undo、Redo用状態管理の例では Undo に対応します
*/
const prev = (): null | T => {
const prevCursor = cursor - 1;
if (tape[prevCursor] === undefined) {
// これ以上手前がないならば
return null; // no action
}
setCursor((p) => p - 1);
return tape[prevCursor];
};
/**
* 現在のカーソルの次の要素を上書きして、次の次から先の既存要素を消して、カーソルを次に動かします
* Undo, Redo 例では、新規に do された時に実行して新たな状態をテープに追加します。
* Undo された後にこれを実行することで過去の Redo と混じって惨事になることを防げます。
*/
const addNextCellWithOverrideCursorIncrement = (cell: T): void => {
setTape([...tape.slice(0, cursor + 1), cell]);
setCursor((preCursor) => preCursor + 1);
};
return {
current,
extendTape,
next,
prev,
addNextCellWithOverrideCursorIncrement,
reset,
tape,
cursor
};
};