2024/02/15(木) [n年前の日記]
#1 [basic] FreeBASICでsplit()を使いたい。その2
_昨日、
FreeBASIC で split() を ―― 文字列を区切り文字で分割して配列にする関数の実装を試してみたけれど。ダブルクオーティション(")が入ってくる場合に望んだ形にならなかったので、そのあたりをどうにかできないかと試していた。
環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
環境は Windows10 x64 22H2 + FreeBASIC 1.10.1 32bit。
◎ ソース :
一応処理は書けたような気がする。もはや split() の動作ではなくなっているけれど…。
実処理は以下。
_splitdq.bi
テストサンプルは以下。
_test_splitdq.bas
fbc test_splitdq.bas でコンパイル。実行結果は以下。
ダブルクオーティションで挟まれた部分については区切り文字を無視するようにできた。
ただ、バグがありそうな気がする…。こういう文字列を渡すとおかしくなる、という場面がありそうな…。
処理の仕方も無駄がありそう。少なくとも処理速度は期待できない予感。
でもまあ、今回やりたいことはできそうだから、これでもいいか…。
実処理は以下。
_splitdq.bi
' License: CC0 / Public Domain #ifndef __SPLITDQ__ #define __SPLITDQ__ Sub Splitdq(byref text As String, byref delim As String, result() as String) If text = "" Then Return Dim As ubyte ptr cp = strptr(text) Dim As ubyte ptr dp = strptr(delim) Dim As Integer slen = Len(text) Dim As Integer pi = 0 Dim As Integer i = 0 Dim As Boolean dqfg = False Dim As String s = "" Do If dqfg Then ' check double quotation pair If (*cp = 34) Then dqfg = False ElseIf (*cp = 34) Then ' found double quotation dqfg = True ElseIf (*cp = *dp) Then ' found delimiter s = Mid(text, pi + 1, (i - pi)) pi = i + 1 End If cp += 1 i += 1 If i >= slen Then s = Mid(text, pi + 1, (i - pi)) pi = i + 1 End If If s <> "" Then ReDim preserve result(UBound(result) + 1) result(UBound(result)) = s s = "" End If Loop While (i < slen) End Sub #endif
テストサンプルは以下。
_test_splitdq.bas
#include "splitdq.bi" Dim As String text 'text = "char id=32 x=250 y=22 width=5 height=5 xoffset=-2 yoffset=21 xadvance=7 page=0 chnl=15" text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2" Dim words() As String splitdq(text, " ", words()) Print text For i As Integer = 0 To UBound(words) Print "[" & words(i) & "]" Next i Print " ...success."
fbc test_splitdq.bas でコンパイル。実行結果は以下。
> test_splitdq.exe info face="DejaVu Sans" size=24 bold=1 italic=0 charset="ANSI" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2 [info] [face="DejaVu Sans"] [size=24] [bold=1] [italic=0] [charset="ANSI"] [unicode=0] [stretchH=100] [smooth=1] [aa=4] [padding=0,0,0,0] [spacing=1,1] [outline=2] ...success.
ダブルクオーティションで挟まれた部分については区切り文字を無視するようにできた。
ただ、バグがありそうな気がする…。こういう文字列を渡すとおかしくなる、という場面がありそうな…。
処理の仕方も無駄がありそう。少なくとも処理速度は期待できない予感。
でもまあ、今回やりたいことはできそうだから、これでもいいか…。
◎ split()の実装が気になってきた :
一般的に split() ってどんな感じに実装されたりするのか気になってググってみた。
以下の事例を見て目ウロコ。元文字列の最後に区切り文字を追加してから処理を始めることで、ループがスッキリしてる…。その手があったか…。
_Split the string into substrings using delimiter - GeeksforGeeks
FreeBASIC版を書いてみた。ダブルクオーティションに対応させようとして、記述が長くなってしまったけれど…。
_splitstringa.bi
使用サンプルは以下。fbc test_splitstringa.bas でコンパイル。
_test_splitstringa.bas
test_splitstringa.exe を実行。たしかに分割できている。
それとは別の実装事例。FreeBASICのWikiを眺めてたら、そもそも split() の実装サンプルが紹介されてた。まさかそんなページで紹介されていたとは…。
_Passing Arrays to Procedures - FreeBASIC Wiki Manual | FBWiki
内容的には、instr() で区切り文字を探して位置を求めて、頭からそこまでの文字列を配列に記録したら、その文字列分を元文字列から削除して処理を続けていた。サブルーチンに引数として元文字列を渡す際、値渡しで渡してるから、元文字列を破壊しても大丈夫ということなのだろう…。もしかすると strtok() を使った事例と考え方は似ているのかもしれない。
これも手元で動作確認してみた。
_splitstringb.bi
使用サンプルは以下。fbc test_splitstringb.bas でコンパイル。
_test_splitstringb.bas
ただ、この版は、区切り文字が連続して並んでる際、それぞれを配列変数に入れてしまう。
もしかすると文字列を切り出した後、文字列が空かどうかを調べて、空じゃなければ配列に格納するようにすればいいのだろうか…?
以下の事例を見て目ウロコ。元文字列の最後に区切り文字を追加してから処理を始めることで、ループがスッキリしてる…。その手があったか…。
_Split the string into substrings using delimiter - GeeksforGeeks
FreeBASIC版を書いてみた。ダブルクオーティションに対応させようとして、記述が長くなってしまったけれど…。
_splitstringa.bi
#ifndef __SPLITSTRINGA__ #define __SPLITSTRINGA__ Sub splitStringA(ByVal text As String, ByVal delim As String, result() as String, ByVal chk_dbqt As Boolean = False) If text = "" Then Return Dim As String src = text & delim Dim As String word = "" Dim As Boolean founded_dbqt = False For i As Integer = 0 To Len(src) - 1 If founded_dbqt Then word &= Chr(src[i]) If src[i] = 34 Then founded_dbqt = False ' found double quote Else If src[i] <> delim[0] Then ' not delimiter word &= Chr(src[i]) If chk_dbqt And src[i] = 34 Then founded_dbqt = True ' found double quote Else ' found delimiter If word <> "" Then ' word is not empty. save word to array ReDim preserve result(UBound(result) + 1) result(UBound(result)) = word word = "" End If End If End If Next i If word <> "" Then ' word is not empty. save word to array ReDim preserve result(UBound(result) + 1) result(UBound(result)) = word End If End Sub #endif
使用サンプルは以下。fbc test_splitstringa.bas でコンパイル。
_test_splitstringa.bas
#include "splitstringa.bi" Dim As String text text = "char id=32 x=250 y=22 width=5 height=5 xoffset=-2 yoffset=21 xadvance=7 page=0 chnl=15" 'text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2" Dim words() As String splitStringA(text, " ", words()) 'splitStringA(text, " ", words(), True) ' check double quote Print "[" & text & "]" For i As Integer = 0 To UBound(words) Print i & ": [" & words(i) & "]" Next i Print "end."
test_splitstringa.exe を実行。たしかに分割できている。
> test_splitstringa.exe [char id=32 x=250 y=22 width=5 height=5 xoffset=-2 yoffset=21 xadvance=7 page=0 chnl=15] 0: [char] 1: [id=32] 2: [x=250] 3: [y=22] 4: [width=5] 5: [height=5] 6: [xoffset=-2] 7: [yoffset=21] 8: [xadvance=7] 9: [page=0] 10: [chnl=15] end.
それとは別の実装事例。FreeBASICのWikiを眺めてたら、そもそも split() の実装サンプルが紹介されてた。まさかそんなページで紹介されていたとは…。
_Passing Arrays to Procedures - FreeBASIC Wiki Manual | FBWiki
内容的には、instr() で区切り文字を探して位置を求めて、頭からそこまでの文字列を配列に記録したら、その文字列分を元文字列から削除して処理を続けていた。サブルーチンに引数として元文字列を渡す際、値渡しで渡してるから、元文字列を破壊しても大丈夫ということなのだろう…。もしかすると strtok() を使った事例と考え方は似ているのかもしれない。
これも手元で動作確認してみた。
_splitstringb.bi
#ifndef __SPLITSTRINGB__ #define __SPLITSTRINGB__ Sub splitStringB(ByVal src As String, ByVal delim As String, result(Any) As String) Do Dim As Integer i = InStr(1, src, Chr(delim[0])) ' search delimiter ReDim Preserve result(UBound(result) + 1) If i = 0 Then ' not found delimiter. exit loop result(UBound(result)) = src Exit Do End If result(UBound(result)) = Left(src, i - 1) ' save word to array src = Mid(src, i + 1) ' delete word Loop End Sub #endif
使用サンプルは以下。fbc test_splitstringb.bas でコンパイル。
_test_splitstringb.bas
#include "splitstringb.bi" Dim As String text text = "char id=32 x=250 y=22 width=5 height=5 xoffset=-2 yoffset=21 xadvance=7 page=0 chnl=15" 'text = "info face=""DejaVu Sans"" size=24 bold=1 italic=0 charset=""ANSI"" unicode=0 stretchH=100 smooth=1 aa=4 padding=0,0,0,0 spacing=1,1 outline=2" Dim words() As String splitStringB(text, " ", words()) Print "[" & text & "]" For i As Integer = 0 To UBound(words) Print i & ": [" & words(i) & "]" Next i Print "end."
ただ、この版は、区切り文字が連続して並んでる際、それぞれを配列変数に入れてしまう。
> test_splitstringb.exe [char id=32 x=250 y=22 width=5 height=5 xoffset=-2 yoffset=21 xadvance=7 page=0 chnl=15] 0: [char] 1: [id=32] 2: [] 3: [] 4: [x=250] 5: [] 6: [] ... 34: [page=0] 35: [] 36: [chnl=15] end.
もしかすると文字列を切り出した後、文字列が空かどうかを調べて、空じゃなければ配列に格納するようにすればいいのだろうか…?
◎ マルチバイト文字列対応は大変そう :
1文字=1byteの文字列なら処理が書けたので、せっかくだからマルチバイト文字列にも対応させられないものかと思い立ったのだけど。String を、マルチバイト文字列を格納する WString に書き換えれば済むかなと思ったらコンパイルすら通らず。
WString や ZString は、扱いが面倒らしい…。
_FreeBASICのワイド文字列が使いにくい件 - 飴屋ぷろじぇくと
_FreeBASIC Compiler - 飴屋ぷろじぇくと
_Problem passing and copying fixed length WSTRING array - freebasic.net
あらかじめ領域を固定で確保しておかないといけないとか、サブルーチンや関数に WString の配列変数を渡せないとか…。これはなかなか大変…。
WString や ZString は、扱いが面倒らしい…。
_FreeBASICのワイド文字列が使いにくい件 - 飴屋ぷろじぇくと
_FreeBASIC Compiler - 飴屋ぷろじぇくと
_Problem passing and copying fixed length WSTRING array - freebasic.net
あらかじめ領域を固定で確保しておかないといけないとか、サブルーチンや関数に WString の配列変数を渡せないとか…。これはなかなか大変…。
◎ 区切り"文字列"にすると大変 :
区切り"文字"で分割する分にはまだこうして書けるけど、区切り"文字列"で分割できるように対応しようとするとちょっと大変そう。文字列検索/文字列探索アルゴリズムの話になって、KMP法とかBM法とかが出てくる…。
_文字列探索アルゴリズムとは?KMP法やBM法について解説
_文字列検索アルゴリズムについて #アルゴリズム - Qiita
_文字列探索アルゴリズムとは?KMP法やBM法について解説
_文字列検索アルゴリズムについて #アルゴリズム - Qiita
[ ツッコむ ]
以上です。