C#で文字列の一度置換した語は置換しない置換クラス。

連続して大量の置換処理をしたいとき、前に置換した語をさらに置換したくない時がある。
たとえば、
「私はエヴァンゲリオンが好きです。」
「私はエヴァが好きです。」
このような文章があった時、意味的には、エヴァンゲリオンエヴァは同じなので同じものとして扱いたい。
このとき、エヴァエヴァンゲリオンで置換すればいいのだけど、ただの置換を連発すると、上の方は、
「私はエヴァンゲリオンンゲリオンが好きです。」
となってしまう。これを避けたい。


文字列が長いもの順に置換をしていくと期待した結果になるでしょう。置換の結果はToText()で取得する。

プログラミングの戦略としては、

  1. 連結リストを使い、文字列をリストで扱う。初めは一つの文字列だけがリストに入っている。
  2. 対象の文字列を見つけたら、その対象の前、対象、対象の後の三つの文字列に分け、元の文字列と差し替えてリストに登録。
  3. 置換したものの文字列なら、文字列を探す対象にしない。
  4. 次の文字列へいく。と繰り返す。

わりと、アルゴリズムの授業の課題に相応しいような簡単な例題だ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyLib.Text
{
    public class ReplaceText
    {
        public ReplaceText(string txt)
        {
            list.AddFirst(new TextPart(txt));
        }
        LinkedList<TextPart> list = new LinkedList<TextPart>();

        public void Replace(string txt, string replacedText)
        {
            LinkedListNode<TextPart> part = list.First;

            while (part != null)
            {
                if (part.Value.Replaced == false)
                {
                    int index = part.Value.Text.IndexOf(txt);
                    string tmpText = part.Value.Text;
                    List<TextPart> tmpList = new List<TextPart>();
                    if (index > -1)
                    {
                        if (index > 0)
                        {
                            TextPart t1 = new TextPart(tmpText.Substring(0, index ));
                            tmpList.Add(t1);
                        }
                        TextPart t2 = new TextPart(tmpText.Substring(index, txt.Length));
                        t2.Text = replacedText;
                        t2.Replaced = true;
                        tmpList.Add(t2);
                        if (tmpText.Length > index + txt.Length )
                        {
                            TextPart t3 = new TextPart(tmpText.Substring(index + txt.Length ));
                            tmpList.Add(t3);
                        }
                        LinkedListNode<TextPart> tmp = null;
                        foreach (var item in tmpList)
                        {
                            tmp = list.AddBefore(part, item);
                        }
                        list.Remove(part);
                        part = tmp;
                    }
                    else
                    {
                        part = part.Next;
                    }
                }
                else
                {
                    part = part.Next;
                }
            }
        }

        public string ToText()
        {
            StringBuilder str = new StringBuilder();
            foreach (var item in list)
            {
                str.Append(item.Text);
            }
            return str.ToString();
        }


        class TextPart
        {
            public TextPart(string txt)
            {
                this.Text = txt;
                this.Replaced = false;
            }
            public string Text { get; set; }
            public bool Replaced { get; set; }
        }
    }

}