Shammer's Philosophy

My private adversaria

ClozureCLでマルチバイト文字のバイト数を判定する

以下のようなプログラムを使用してバイト数を判定していたが、これだけではやっぱり不十分だったようだ。

(defun string-byte-size (s)
  (let ((count 0))
    (when (stringp s)
      (let ((strings-list (coerce s 'list)))
       (dolist (i strings-list)
         (if (typep (character i) 'standard-char)
             (setf count (+ count 1))
             (setf count (+ count 3))))))
    count))

単純に、使用するのは日本語か ASCII 文字、文字コードUTF-8 で固定という状態だから、

  • ASCII の場合は 1 バイト
  • それ以外は 3 バイト(日本語は UTF-8 だと 3 バイトでカウント)

という前提で計算すればいいだろう、と思っていたが、この関数で返されるバイト数と ls -l や wc で確認できるバイト数に差が生じることがあると判明。どういう文字が差の原因になるかはわからないが、やっぱり安易すぎたか。
ClozureCL のオンラインドキュメントを確認し、使えそうな関数を確認。名前からは想像しにくいが、encode-string-to-octets で目的を達成できそうだ。以下のようにすることでバイト数が wc や ls と一致するようになった。

(defun string-byte-size (s)
  (if (stringp s)
      (multiple-value-bind (hex-array count)
          (encode-string-to-octets s :external-format :utf-8)
        count)
    0))

渡された引数が文字列じゃなければ強制的に 0 にする実装だが。
真面目に実装するとすれば、

  1. 渡された文字列をバイトに変換
  2. バイトの配列になるはずなので、その配列の要素数をカウント

という感じだろうか。coerce が使えないだろうか。

? (coerce "あいうえお" 'list)
(#\U+3042 #\U+3044 #\U+3046 #\U+3048 #\U+304A)
?
? (coerce "あいうえおabcdefg" 'list)
(#\U+3042 #\U+3044 #\U+3046 #\U+3048 #\U+304A #\a #\b #\c #\d #\e #\f #\g)
? (length (coerce "あいうえおabcdefg" 'list))
12
?

完璧ではない。「あいうえお」は UTF-8 だと 15 になるはず。とりあえず、encode-string-to-octets で目的は達成できているので、真面目にやるにはどうするかはまた今度にする。それにしても、、、わかりにくいネーミングだ。multiple-value-bind を使用しないといけないのも面倒。