2007年12月26日水曜日

文字列の分割と結合

ActionScript3(AS3)にあるメソッド、split(文字列の分割)とjoin(文字型配列変数の結合)をHSP標準の文字列操作命令で実装。
splitの方は正規表現で何とかできそうな気がしますが……VBScriptのRegExpにはsplitメソッドが備わっていないようなので、少し難しいのでしょうか。ただいま調査中です。

区切り文字の数が多く、さらに区切り文字で終わる文字列をsplitできなかった不具合を修正。
#module
// 文字列の分割
#deffunc split array arr, str target, str devider
    if devider = "" {
        // 分割する目印は空文字であってはならない
        return 1
    }

    dim part_strlen, 10             // 分割結果の長さ
    part_strlen_max = 0             // 分割結果の長さの最大値
    parts_num = 1                   // 分割結果の数
    _target = target                // instr,strmidを使うために変数に代入する
    target_strlen = strlen(target)  // 分割する文字列の長さ
    devider_strlen = strlen(devider)// 分割する目印の長さ

    // いくつに分割できるか調べる
    repeat
        ins = instr(_target, cnt, devider)
        if ins == -1 {
            part_strlen(parts_num - 1) = target_strlen - cnt
            if part_strlen_max < part_strlen(parts_num - 1) {
                part_strlen_max = part_strlen(parts_num - 1)
            }
            break
        } else {
            part_strlen(parts_num - 1) = ins
            if part_strlen_max < ins {
                part_strlen_max = ins
            }
            parts_num++
            continue cnt + ins + devider_strlen
        }
    loop

    // 分割結果代入用の配列を確保
    sdim arr, part_strlen_max + 1, parts_num

    // 分割の開始
    position = 0
    foreach arr
        arr(cnt) = strmid(_target, position, part_strlen(cnt))
        position += part_strlen(cnt) + devider_strlen
    loop
    return 0

// 文字列の結合
#deffunc join var result, array target, str connecter
    if vartype(target) != 2 {
        // 結合する配列変数は文字列型でなければならない
        return 1
    }

    connecter_strlen = strlen(connecter)
    // 連結結果の長さを調べる
    result_strlen = connecter_strlen * (length(target) - 1)
    repeat length(target)
        target_strlen(cnt) = strlen(target(cnt))
        result_length += target_strlen(cnt)
    loop

    // 連結結果代入用の変数を確保
    sdim result, result_length + 1

    // 連結の開始
    position = 0
    foreach target
        if cnt {
            poke result, position, connecter
            position += connecter_strlen
        }
        poke result, position, target(cnt)
        position += target_strlen(cnt)
    loop
    return 0

// ちょっと遠回りな置換
#deffunc replace var target, str before, str after
    split tmparr, target, before
    if stat : return stat
    join target, tmparr, after
    return stat
#global

// サンプルスクリプト
    font msgothic14
    s = "Hot Soup Processor"
    mes s

    mes "\n半角スペースで分割"
    split arr, s, " "
    foreach arr
        mes "{"+cnt+"}:"+arr(cnt)
    loop

    mes "\nエクスクラメーションマークで結合"
    join result, arr, "!"
    mes result

    mes "\nsplitとjoinを組み合わせで置換"
    replace s, " ""!"
    mes s
    stop

3 件のコメント:

FUJI さんのコメント...

split と join を組み合わせて置換というのは面白いアイデアですね!

split での処理について。

>repeat target_strlen
なるほど、cntをtargetの現在位置に利用しているんですね。

>part_strlen(parts_num - 1) = target_strlen - cnt + 1
+1 しなくてもよさそうです。

s = "x x x x x x x x x x "
とすると #Error 7 in line 41 () になります。

eller さんのコメント...

>splitとjoinで置換
クジラ飛行机さんの著作にあったものです。
ちょっと遠回り過ぎる気もしますが、頻繁に使うのでなければ問題なさそうです。

>repeat target_strlen
peekを利用するときなど、バッファのサイズを超えないようにできるので便利なのです。今回はそれが仇になってしまいましたが…。

>+1 しなくてもよさそうです。
NULL文字分は別のところで確保していましたね。修正しました。

>s = "x x x x x x x x x x "
>とすると #Error 7 in line 41 () になります。
前述のrepeatに問題がありました。修正しました。

eller さんのコメント...

fujiさんのブログにコメントができないようなのでこちらで。

fujiさんの改造スクリプト読みました。
http://www.fujidig.com/2007/12/split.html
確かにそちらの方がスマートな実装ですね(・ー・;
似たような処理が連続していたので違和感はあったのですが、思いつきませんでした。頭硬くなってるんでしょうか。