DelFusa Blog 総本山

プログラミングの話題とかです。

NEW | PAGE-SELECT | NEXT

≫ EDIT

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

| スポンサー広告 | --:-- | comments(-) | trackbacks(-) | TOP↑

≫ EDIT

区切り記号の前方と後方で文字列を取得する


     ∧,,∧       今日もコーディングだぜ!
     ミ,, ゚Д゚彡     
     〃つ_つ/  ̄ ̄ ̄/
   ~ミ,,, ,,|\ ./シンクパド/
       '\/.======./
           ̄ ̄ ̄


Delphiコードですよ。

以前から不思議だったのですが
ABCDEFG=XYZ
という文字列を、区切り文字「=」を指定した前方と後方を取得する関数。
これがあったら便利だと思うのですが

.NETでもVCLでも用意されてないみたいっす。


大量に文字列処理のテクが書いてあるここにもそんな関数のってねーし。

VB.NET - 文字列 (String) に関する Tips
http://jeanne.wankuma.com/tips/vb.net/string/




ということで作りました。以前から作ってたんだけどね。



仕様としては
  ABC=DEFG=XYZ
こういう文字列の場合

  「ABC=DEFG」と「XYZ」

と切り分けるか、

  「ABC」と「DEFG=XYZ」

と切り分けるかは、別関数にしています。

  function FirstString(s: String; Delimiter: String): String;
  var
   DeleteIndex: Integer;
  begin
   Result := s;
   DeleteIndex := PosForward( Delimiter, s);
   if DeleteIndex = 0 then
    exit;

  // Result := copy( Result, 1, DeleteIndex - 1);
   Delete(Result, DeleteIndex, MaxInt);
  end;

  function LastString(s: String; Delimiter: String): String;
  var
   DeleteIndex: Integer;
  begin
   Result := s;
   DeleteIndex := PosBackward(Delimiter, s);
   if DeleteIndex = 0 then
    exit;

   DeleteIndex := DeleteIndex + Length(Delimiter) - 1;
   Delete( Result, 1, DeleteIndex);
  end;

  //最も後方に位置する区切り文字(文字列)で指定した物の前方方文字列を取得
  function FirstStringLong(s: String; Delimiter: String): String;
  var
   DeleteIndex: Integer;
  begin
   Result := s;
   DeleteIndex := PosBackward(Delimiter, s);
   if DeleteIndex = 0 then
    exit;

  // Result := copy(Result, 1, DeleteIndex - 1);
   Delete(Result, DeleteIndex, MaxInt);
  end;

  //最も前方に位置する区切り文字(文字列)で指定した物の後方文字列を取得
  function LastStringLong(s: String; Delimiter: String): String;
  var
   DeleteIndex: Integer;
  begin
   Result := s;
   DeleteIndex := PosForward( Delimiter, s);
   if DeleteIndex = 0 then
    exit;

   Delete( Result, 1, DeleteIndex + Length(Delimiter) - 1);
  end;


で、これは、PosForwardとPosBackwordが必要。
(.NETだと、StringのIndexOfか、LastIndexOfで同じ仕組みで文字列の場所が取得できるね)

PosForwardは、通常のPos関数と同一仕様なんだけど、独自実装。
独自関数のPosBackwordと同じようになるようにしてる。

うちのライブラリを見ているとどこかに同じコードがあるような気がするけど
とりあえずコピペ

    function PosForward(const SubStr, S: String; CaseCompare: TCaseCompare = ccCaseSensitive): Integer;
    begin
     Result := RangePosForward(SubStr, S, 1, MaxInt, CaseCompare);
    end;

    function PosBackward(const Substr, S: String; CaseCompare: TCaseCompare = ccCaseSensitive): Integer;
    begin
     Result := RangePosBackward(SubStr, S, 1, MaxInt, CaseCompare);
    end;

これも、汎用関数RangePosをラップしてる。

RangePosは
スタートからエンドの範囲指定で大小文字区別するか否かのオプションをもってる。
ForwardとBackwordで別関数だけど実態は、RangePos_Base関数で実装されている。

  function RangePosForward(const SubStr, S: String;
   Index: Integer = 1; Count: Integer = MaxInt;
   CaseCompare: TCaseCompare = ccCaseSensitive{IgnoreCase=False}): Integer;
  begin
   Result := RangePos_Base(SubStr, S, Index, Count, CaseCompare, sdForward);
  end;

  function RangePosBackward(const SubStr, S: String;
   Index: Integer = 1; Count: Integer = MaxInt;
   CaseCompare: TCaseCompare = ccCaseSensitive{IgnoreCase=False}): Integer;
  begin
   Result := RangePos_Base(SubStr, S, Index, Count, CaseCompare, sdBackward);
  end;

で、こいつの実装は、ループさせて順方向か逆方向かで順次
StringPartsCompare_Base関数を呼んでる。

  type
   TSearchDirection = (sdForward, sdBackward);
   {↑Forward: 前方検索 前から後ろへ検索する[→]
     Backward: 後方検索 後ろから前へ検索する[←]}

  function RangePos_Base(const SubStr, S: String;
   Index, Count: Integer; CaseCompare: TCaseCompare;
   SearchDirection: TSearchDirection): Integer;
  var
   i: Integer;
   EndIndex: Integer;
  begin
   Result := 0;
   if (SubStr='') or (S='') then Exit;
   if not ( (1<=Index) and (Index<=Length(S)) ) then Exit;
   if not ( (Length(SubStr)<=Count) ) then Exit;
   if not (1<=Count) then Exit;

   {↓Index+Count-1を計算してMaxIntを超える場合
     負の値になるので修正}
   EndIndex := Index + Count - 1;
   if (EndIndex < 0) or (Length(S) < EndIndex) then
   begin
    EndIndex := Length(S);
   end;

   { 123456789A
    __________←Sは10Char
      4____9 ←Index=4/End=9の6Char
      ___ ←SubStr=3Char
      ___
       ___
       ___ ←ループは4から7←(9+1-3)
          逆方向は7から4
   }
   case SearchDirection of
    sdForward:
    begin
     for i := Index to EndIndex-Length(SubStr)+1 do
     begin
      if StringPartsCompare_Base(S, SubStr, i, 1, Length(SubStr), CaseCompare) then
      begin
       Result := i;
       Break;
      end;
     end;
    end;

    sdBackward:
    begin
     for i := EndIndex-Length(SubStr)+1 downto Index do
     begin
      if StringPartsCompare_Base(S, SubStr, i, 1, Length(SubStr), CaseCompare) then
      begin
       Result := i;
       Break;
      end;
     end;
    end;
   end; //case SearchMuki
  end;

StringPartsCompare_Baseは、CompareStringW API を使ってもいいんだけど
自分で一致を確認してもいいから、こんな実装。

  function StringPartsCompare_Base(const S1, S2: String;
   S1Index, S2index, CompareLength: Integer;
   CaseCompare: TCaseCompare): Boolean;
  var
   i: Integer;
  begin
   Result := True;

   case CaseCompare of
    ccCaseSensitive:
     for i := 0 to CompareLength-1 do
     begin
      if S1[S1Index + i] <> S2[S2index + i] then
      begin
       Result := False;
       Exit;
      end;
     end;

    ccIgnoreCase:
     for i := 0 to CompareLength-1 do
     begin
      if UpperCase(S1[S1Index + i]) <> UpperCase(S2[S2index + i]) then
      begin
       Result := False;
       Exit;
      end;
     end;
   end;
  end;

基本的なものも、それをラップして応用している
ものは.NETでも簡単に実装できると思う。

こんな内部汎用関数の連続体がベースとなって First/LastStringが出来てます。
StringListとか、Splitで区分して取得してもいいと思うけどね。


そんでもって、これらが
どう便利かというと…

s := 'C:\a\b\c\d.txt'

という文字列があったとして
フォルダ名を取るなら FirstStringLong(s, '\') で取り出せるし
ファイル名なら LastString(s, '\')
拡張子なしのファイル名なら、FirstString(LastString(s, '\'), '.');
と取得できます。

え?Extractナントカでとれるって?

そう。そのExtractナントカを簡単に自作できるってこと。
VCLレベルの処理を自作出来るようになっておくと、
VCLの汎用性を超えた所に処理したい場合に、すぐに応用がきかせられて
より高速な開発ができるようになりますよ。

例えば
こういう応用をかけておくと、

MainForm.xaml.vb

こういうファイルの場合に、ExtractFileExtとかの仕様が xaml.vbを取り出すのか vb をとりだすのか迷う時、仕様を確認するまでもなく、正しいコードを素早く書くことができるようになります。
スポンサーサイト

| 未分類 | 12:05 | comments:5 | trackbacks(-) | TOP↑

COMMENT

つ DelimitedText

| | 2011/12/09 15:11 | URL | ≫ EDIT

.NET は "ABCDEFG=XYZ".Split("=") で概ね用が足りるじゃないですか

| | 2011/12/10 02:21 | URL | ≫ EDIT

iniファイル

iniファイル形式の読み取りではダメでしょうか?

| nobukoshi802 | 2011/12/12 11:42 | URL | ≫ EDIT

コメントありがとうございます。

んー、
動的配列とか、StringListとか、使う時に定義が面倒な場目mもあったり。
時々、皿っと作りたい気がするときもあるんですよね。

もちろん、この手の実装をするときに、内部実装はDelimitedTextやSplitを使ってしまう場合もあります。

上みたいに部分文字列一致系の関数を汎用的汎用的に作りこむ必要もないかも

自分も他に、StringSplitterってクラスも作ってるし、それをラップして、SplitやWordCountやらWordGetやらいろいろ持ってますね。

道具と武器を多めにつくっていると、ま、便利かと。そんな感じで、見守ってくださいまし。

Iniファイル形式の読み取りは…ひとつの例ですよ。
いざというときの為とまでは大層なことではないですが、Iniファイル読み取りクラスを自作する場合などにも応用ききますよ。

| ミ・д・彡 | 2011/12/12 22:16 | URL | ≫ EDIT

そういえば、StringPartsCompareは、私にとっては必須の道具ですね。

これがあれば
複数文字同時置き換え関数を作るときも
最初から最後までループさせて
該当文字列が存在すれば、その文字と置き換え文字を入れ替える。
そんなコードも素早く書けます。

ABCに対して、A→C、C→Aの変換を一度にかけてCBAにすることのできる関数。

$$を$と置き換えて、単独の$は削除するとかって、
複数文字列一括置換がなければ、どうやって実現すればいいんだろうか....

| ミ・д・彡 | 2011/12/16 01:40 | URL |















非公開コメント

PREV | PAGE-SELECT | NEXT

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。