[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]
『VBAでLEFTB的なものを再現する高速な手法は?』(白茶)
ほぼ趣味の世界のお話なので、おヒマでしたらお付き合いください。
ワークシート関数のLEFTBに似たFunctionで、 条件1「全角をちょん切ってしまった場合は半角スペースに補正して桁を揃える」 条件2「LEFTBとは違って、ChrW(12958)の様な文字は2桁として計算する」 というのをVBA上で再現しようと、いろいろ試しています。
●ボツ案1
Application.Evaluateで再現する方法。
Evaluateの引数文字列が512桁を超えるとエラーになる。 条件2をクリアできない。
●ボツ案2
予め末尾に半角スペースを1つ付けた上で、 WorksheetFunction.ReplaceBで指定桁数超過部分を空白に置き換える。
長い文字列でもイケるが、条件2をクリアできない。
●ボツ案3
1文字ずつ順番に取り出して検査しながら 指定桁数に達するまでLENB相当のバイト数を足算。 (StrConvでvbFromUnicodeに変換した文字とvbUnicodeに戻した文字とが違ったら2を それ以外はvbFromUnicodeに変換した文字のLenBを足算) ループを抜けた時点での文字数分をLeft$で取り出した後、 足算結果と指定桁数に差があったら尻に差分のスペースを追加。
とにかく遅い。 10万回ループでの速度比較では、 10桁の文字でもReplaceBに負ける。 100桁くらいで既に差を体感する様になり、 1000桁くらいになってくると、もう目も当てられない・・・
●ボツ案4
ボツ案3の検査方法をAscW関数に変更
StrConvしない分、目も当てられない事は無くなったけど、 10万回ループでの速度比較では、 100桁くらいでも1秒以上かかるし、 1000桁だと10秒もかかってしまう。
●現行案
ボツ案3の検査方法をバイト配列に変更 バイト配列に変換して、2バイトずつ取り出しては、前バイトと後バイトを検査しながら 指定桁数に達するまでLENB相当のバイト数を足算。 ループを抜けた時点での文字数分をUnicodeのままLeftB$で取り出した後、 足算結果と指定桁数に差があったら尻に差分のスペースを追加。
10万回ループでの速度比較では、 100桁の文字列だとReplaceBと引き分けくらい(どちらも1秒未満で勝ったり負けたり) 1000桁だと敗けました(1秒超の差)。
ちなみに1万桁の文字列だと、差は顕著で、 StrConv(LeftB$(StrConv(文字列, vbFromUnicode), 長さ), vbUnicode) が7.5秒程度で処理するのに対し、 ReplaceBが20秒、現行案が30秒でした。
ここでネタが尽きました。 というか、これくらいが限界なのかなぁ・・・と思いつつ、
そもそも皆さんならどんなアプローチで処理するのでしょうか?
100桁台の文字列ならコレでもReplaceBより速いですが、 LeftB$くらい高速に動作する事までは望まないとしても、 せめて1000桁でもReplaceBより早く処理する方法は無いでしょうか?
まったりとアドバイスお願いします。
早いかどうかは全く分からないし、正しいかどうかも分からない無責任レス。
VBAのLEFTBで取り出した文字列の最後の文字 Right(その文字列,1) が、なんとなく ASCコードで 63 になっているみたい??(Win7のxl2010) であれば、63 なら最後の文字を半角スペースで置き換えるとか。
まったく、いいかげんな発言かな? 大きく外している公算大・・・・
(ぶらっと)
ぶらっとさんレスありがとうございます。
>VBAのLEFTBで取り出した文字列の最後の文字 確かに。これボツ案2と3の間で遭遇しましたね。
ただ、LeftB$でS-JIS換算のバイト指定取り出しを vbFromUnicode変換せずに行う方法が分らなかったので 「条件2」をクリアできず、ボツ案3以降の流れになってました。
そこら辺って上手い処理方法あるんでしょうか?
(白茶)
>そこら辺って上手い処理方法あるんでしょうか?
いやぁ。。。。いろいろ試してみると 63 も、何か別のもののようだし・・・ お役に立てそうにないです。
(ぶらっと)
いえいえ、お付き合いいただきありがとうございます。
どうやら63は「?」のキャラコードみたいですね。 S-JISにマッピングされていない文字をvbFromUnicode変換した時に「?」になるから、か・・・
私もさっきのレスは勘違いしてたみたいで、 私の遭遇したのは全角の前半バイトにAsc関数かけると「0」を返す。 というやつだったみたいです。
またなんかひらめいたら、ぜひお願いします。
(白茶)
すみません。2箇所ほど記述を訂正します。ウソ書いてました。
>条件2「LEFTBとは違って、ChrW(12958)の様な文字は2桁として計算する」
誤:LEFTBとは違って 正:LENBとは違って
>●ボツ案3 >(StrConvでvbFromUnicodeに変換した文字とvbUnicodeに戻した文字とが違ったら2を
誤:vbFromUnicodeに変換した文字 正:vbFromUnicodeに変換する前の文字
(白茶)
もうちょっと経緯を説明した方が良いかしら・・・
実はボツ案2は、非常に惜しいのです。
末尾が全角文字で、指定桁数がその末尾文字を分断する場所だった場合、 ReplaceB関数自体が末尾の全角文字を分断出来ないので、 (REPLACEB("あ",2,1,"") => "あ" が返る) 予め末尾に半角スペースを付けてReplaceBで後ろを全部消しちゃうのですが、 (REPLACEB("あ"&" ",2,1+LEN(" "),"") => 半角スペース1個 が返る)
出てくるのがS-JISの全角のみであれば、これでイケます。 ところがChrW(12958)の様な文字が混在した場合は、このやり方が通用しません。
というのが、 そもそも桁数の指定が対象文字列の桁数より大きかった場合に、 本来無いはずの半角スペースが末尾に付いたままの答えが返ってくるので、 ReplaceBの処理以前に、対象文字列の桁数と指定桁数の比較で処理を分岐させるのですが、 当然桁数の算出にLenB関数を使います。 当然StrConvでvbFromUnicodeに変換します。 ChrW(12958)は「?」に変換されて1桁扱いになってしまいます。 結果、桁数の比較に失敗してReplaceBで後ろを消す処理に流れないパターンが発生してしまいます。
LenBだけ自作する? いや、それやっちゃうならReplaceBを使う意味がない。
じゃあ、 末尾に付ける文字を半角スペースじゃなく、もっと特殊な文字にして あえて対象文字列の桁数と指定桁数の事前比較を行わないでReplaceBし、 結果として残ってたらソイツだけ消す。
という流れも考えたのですが、 「特殊な文字」って何使えばいいんだろ? vbNullCharで試したら何も付けてないのと同じ扱いになってしまいました。 改行文字もタブも対象文字列内で使用する可能性はあるし・・・
で、ボツ案3以降へと突入したのです。
ReplaceBの上記問題をクリアできれば、 短い文字列は現行案で、長い文字列はReplaceBで処理 というちょっとセコい解決策もあるんですけどね・・・
(白茶)
なんで今まで気付かんかったんだろ。 のめり込んだら当たり前のことが見えなくなったの典型でした。
別に「特殊な文字」なんぞ使わなくたって、 Lenで文字数超過を確認すれば良いだけじゃないの。
ボツ案2は、ボツ撤回。 現行案に取り入れました。
Function xlLeftB(String1 As String, Length As Long) As String Rem ▼▼長さ指定ゼロなら即終了 If Length < 1 Then Exit Function Rem ▼▼100文字超の文字列に対して200桁超の切り出しを行う際はReplaceB関数を使って速度を稼ぐ Dim i As Long If Len(String1) > 100 And Length > 200 Then i = Len(String1) Rem ▼REPLACEBは末尾の全角削除が苦手なので、末尾に一文字付けてから処理 xlLeftB = Application.WorksheetFunction.ReplaceB(String1 & Space$(1), Length + 1, i * 2 + 1, "") Rem ▼String1より長い文字列だったらString1を返す If Len(xlLeftB) > i Then xlLeftB = String1 Exit Function End If Rem ▼▼それ以下の場合は関数の呼び出しオーバーヘッドで逆に遅くなると見越して自前処理 Dim c() As Byte, StrLenB As Long, j As Long Rem ▼バイト配列にキャストしてLENB算出及び加算 c = String1 For i = LBound(c) + 1 To UBound(c) Step 2 'j = LenBfromWByte(c(i - 1), c(i)) '←廃止 Select Case c(i) Case 0& Select Case c(i - 1) Case 0& To 128&, 160&, 253& To 255&: j = 1 Case Else: j = 2 End Select Case 255& Select Case c(i - 1) Case 97& To 159&: j = 1 Case Else: j = 2 End Select Case Else: j = 2 End Select If StrLenB + j > Length Then Exit For '長さ指定に達する直前でループ抜け StrLenB = StrLenB + j Next Rem ▼算出バイト位置でLEFT xlLeftB = LeftB$(String1, (i - 1)) If xlLeftB = String1 Then Exit Function Rem ▼桁足らずだった場合はスペースを補う If Length - StrLenB > 0 Then xlLeftB = xlLeftB & Space$(Length - StrLenB) End Function
実際には短い文字列で処理速度なんて気にならないだろうし、 せっかくExcelなんだから、ExcelらしくReplaceB一択処理でも構わない気がしてきました・・・。
(白茶)
[ 一覧(最新更新順) ]
YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki.
Modified by kazu.