テキスト エディタにとって、純粋に機能的なデータ構造とはどのようなものでしょうか? 1 文字をテキストに挿入したり、テキストから 1 文字を削除したりできる効率を許容できる範囲で維持したいと考えています。また、変更を簡単に元に戻すことができるように、古いバージョンを保持できるようにしたいと考えています。
文字列のリストを使用して、バージョンごとに変更されていない行を再利用する必要がありますか?
テキスト エディタにとって、純粋に機能的なデータ構造とはどのようなものでしょうか? 1 文字をテキストに挿入したり、テキストから 1 文字を削除したりできる効率を許容できる範囲で維持したいと考えています。また、変更を簡単に元に戻すことができるように、古いバージョンを保持できるようにしたいと考えています。
文字列のリストを使用して、バージョンごとに変更されていない行を再利用する必要がありますか?
「良い」の洗練された定義に対して、この提案が「良い」かどうかはわかりませんが、簡単で楽しいものです。テキストエディタのコア部分を Haskell で記述し、提供するレンダリング コードとリンクさせる演習をよく行います。データモデルは次のとおりです。
最初に、要素のリスト内のカーソルとは何かを定義しますx
。ここで、カーソルで利用可能な情報には何らかの type がありますm
。(はまたはx
になります。)Char
String
type Cursor x m = (Bwd x, m, [x])
これBwd
は単なる後方の「snoc-lists」です。私は強い空間的直感を保ちたいので、頭ではなくコードで物事を好転させます。アイデアは、カーソルに最も近いものに最も簡単にアクセスできるということです。それがThe Zipperの精神です。
data Bwd x = B0 | Bwd x :< x deriving (Show, Eq)
カーソルの読み取り可能なマーカーとして機能する無償のシングルトン型を提供します...
data Here = Here deriving Show
…だから私は、ある世界のどこかにいるとはどういうことかを言うことができます。String
type StringCursor = Cursor Char Here
ここで、複数行のバッファーを表すにはString
、カーソルのある行の上下に が必要StringCursor
で、現在編集中の行の中央に が必要です。
type TextCursor = Cursor String StringCursor
TextCursor
編集バッファーの状態を表すために使用するのは、この型だけです。二層ジッパーです。ANSI エスケープ対応のシェル ウィンドウ内のテキストにビューポートをレンダリングするコードを学生に提供し、ビューポートにカーソルが確実に含まれるようにします。TextCursor
キーストロークに応答してを更新するコードを実装するだけです。
handleKey :: Key -> TextCursor -> Maybe (Damage, TextCursor)
キーストロークが無意味な場合はどこにhandleKey
戻りますが、それ以外の場合は更新された「損傷レポート」を配信します。後者は次のいずれかですNothing
Just
TextCursor
data Damage
= NoChange -- use this if nothing at all happened
| PointChanged -- use this if you moved the cursor but kept the text
| LineChanged -- use this if you changed text only on the current line
| LotsChanged -- use this if you changed text off the current line
deriving (Show, Eq, Ord)
Nothing
( returnとreturn の違いが何であるか疑問に思っている場合はJust (NoChange, ...)
、エディターもビープ音を鳴らしたいかどうかを検討してください。) 損傷レポートは、表示された画像を最新のものにするためにどれだけの作業が必要かをレンダラーに伝えます。
このKey
型は、生の ANSI エスケープ シーケンスから抽象化して、可能なキーストロークに読み取り可能なデータ型表現を与えるだけです。それは目立たないです。
これらのキットを提供することで、このデータ モデルを使用して上下する大きな手がかりを学生に提供します。
deactivate :: Cursor x Here -> (Int, [x])
deactivate c = outward 0 c where
outward i (B0, Here, xs) = (i, xs)
outward i (xz :< x, Here, xs) = outward (i + 1) (xz, Here, x : xs)
このdeactivate
関数は、 からフォーカスを移動するために使用されCursor
、通常のリストが表示されますが、カーソルがどこにあったかがわかります。対応するactivate
関数は、カーソルをリスト内の特定の位置に配置しようとします。
activate :: (Int, [x]) -> Cursor x Here
activate (i, xs) = inward i (B0, Here, xs) where
inward _ c@(_, Here, []) = c -- we can go no further
inward 0 c = c -- we should go no further
inward i (xz, Here, x : xs) = inward (i - 1) (xz :< x, Here, xs) -- and on!
私は生徒たちに、意図的に不正確で不完全な定義を提供します。handleKey
handleKey :: Key -> TextCursor -> Maybe (Damage, TextCursor)
handleKey (CharKey c) (sz,
(cz, Here, cs),
ss)
= Just (LineChanged, (sz,
(cz, Here, c : cs),
ss))
handleKey _ _ = Nothing
通常の文字キーストロークを処理するだけで、テキストが後方に出てきます。c
文字がの右側に表示されていることが簡単にわかりHere
ます。バグを修正し、矢印キー、バックスペース、削除、戻るなどの機能を追加するように依頼します。
これまでで最も効率的な表現ではないかもしれませんが、純粋に機能的であり、編集中のテキストに関する空間的直感にコードを具体的に適合させることができます。
AVector[Vector[Char]]
はおそらく良い賭けでしょう。あなたが言及したのとは異なり、IndexedSeq
それはまともな更新/プリペンド/更新パフォーマンスを持っています。パフォーマンス特性List
を見ると、効果的な一定時間の更新があるのは、言及されている唯一の不変コレクションです。
私たちはYiでテキストジッパーを使用しています。これはHaskellでの本格的なテキストエディターの実装です。
不変の状態タイプの実装について、以下で説明します。
http://publications.lib.chalmers.se/records/fulltext/local_94979.pdf
http://publications.lib.chalmers.se/records/fulltext/local_72549.pdf
および他の論文。
フィンガーツリーに基づくData.Sequence.Seqと組み合わせてジッパーを使用することをお勧めします。したがって、現在の状態を次のように表すことができます
data Cursor = Cursor { upLines :: Seq Line
, curLine :: CurLine
, downLines :: Seq Line }
これにより、カーソルを1行上下に移動するためのO(1)splitAt
の複雑さが得られます。また、(><)
(union)には両方のO(log(min(n1、n2)))の複雑さがあるため、O(log(L) )L行を上下にスキップするための複雑さ。
CurLine
カーソルの前、前、後の文字のシーケンスを保持するために、同様のジッパー構造を使用できます。
Line
ByteStringなど、スペース効率の高いものにすることができます。
vty-ui
この目的のために、ライブラリにジッパーを実装しました。ここで見ることができます:
https://github.com/jtdaugherty/vty-ui/blob/master/src/Graphics/Vty/Widgets/TextZipper.hs
Clojure コミュニティは、RRB ツリー(Relaxed Radix Balanced) を、効率的に連結/スライス/挿入できるデータ ベクトルの永続的なデータ構造として検討しています。
連結、インデックスへの挿入、および分割操作を O(log N) 時間で実行できます。
文字データに特化した RRB ツリーは、大きな「編集可能な」テキスト データ構造に完全に適していると思います。
思いつく可能性は次のとおりです。
数値インデックスを持つ「テキスト」タイプ。バッファの連結リスト (内部表現は UTF16) にテキストを保持するため、理論的には計算の複雑さは通常連結リストの複雑さ (たとえば、インデックス付けは O(n)) ですが、実際には従来の連結リストよりもはるかに高速です。ウィキペディア全体をバッファに保存していない限り、 n の影響をおそらく忘れてしまう可能性があることをリストしてください。私が正しいかどうかを確認するために、100 万文字のテキストでいくつかの実験を試みてください (実際に行ったことはありません)。
テキスト ジッパー: カーソルの後のテキストを 1 つのテキスト要素に格納し、カーソルの前のテキストを別のテキスト要素に格納します。カーソル転送テキストを一方の側から他方の側に移動するには。