逆引きxyzzy lisp(文字列) - circleratio/xyzzy GitHub Wiki

目次

作成

文字列を作る

文字の集合から作る場合。

(coerce '(#\あ #\い #\う #\え #\お #\a #\b #\c) 'string)
=> "あいうえおabc"

(concatenate 'string '(#\a #\b #\c) '(#\d #\e #\f))
=> "abcdef"

(let ((clist '(#\a #\b #\c)))
  (string (make-vector (length clist) :initial-contents clist)))
=> "abc"

1文字だけからなる文字列を作る場合は

(string #\a)
=> "a"

でよい。 ただし、elispのように

(string ?a ?b ?c)
=> "abc"

とはできないので注意。

ひとつの文字が連続する場合。

(make-sequence 'string 15 :initial-element #\SPC)
=>"               "

文字列を結合する

(concat "abc" "def" "ghi")
=> "abcdefghi"

(concatenate 'string "abc" "def" "ghi")
=>"abcdefghi"

繰り返し文字列を生成する

"abc" を 5回つなげる. dotimes は x を 0, 1, 2, 3, 4 と変化させるので,5個繋げるなら引数は4.

次の関数のほうが綺麗.

(defun repeat-string (str n)
  (if (= n 0)
      ""
    (concat str (repeat-string str (1- n)))))

(repeat-string "a" 10)
=> "aaaaaaaaaa"

コマンドの実行結果を文字列に設定する

テンポラリのバッファを作るのが定石.

(defun runcmd (cmd)
  (let ((buf (create-new-buffer "*command*"))
	proc)
    (save-window-excursion
      (with-set-buffer
	(set-buffer buf)
	(setq proc (make-process cmd))
	(while (eql :run (process-status proc))
	  (do-events))
	(prog1
	    (buffer-substring (point-min) (point-max))
	  (delete-buffer buf))))))

(setq result (runcmd "cmd /c dir c:"))

複数行のコマンドの実行結果を文字列に設定する

言語による支援があるわけではないので省略. 「コマンドの実行結果を文字列に設定する」の結果を複数個つなげること!

複数行の文字列を作成する

行単位でものを考えるのでなく,カッコや引用符の対応で構文解析するので,ヒアドキュメントのような考え方は不要.

(setq s "This is test.

Ruby, the Object Oriented Script Language.")
=> "This is test.

   Ruby, the Object Oriented Script Language."

部分文字列操作

部分文字列を取り出す

2文字目から4文字目までを取り出す.

(subseq "ABcDE" 1 4)
=> "BcD"

3文字目以降をすべて取り出す.

(subseq "ABcDE" 2)
=> "cDE"

xyzzy lisp には substring というほぼ同機能の関数もあるが,Common Lisp 標準の関数は subseq だけなので、こちらを使うほうがベター.

部分文字列を置き換える

(concatenate 'string "Vine" (subseq s 5))
=> "Vine Banana Orange"

(concatenate 'string (subseq s 0 6) "Lemon" (subseq s 12))
=> "Apple Lemon Orange"

文字列比較

等しいかどうかを調べる

(string-equal "あいう" "あいう")
=> t

(string= "あいう" "あいう")
=> t

(equal "あいう" "あいう")
=> t

(eq "あいう" "あいう")
=> nil

(= "あいう" "あいう")
=> 不正なデータ型です: "あいう": number

辞書順で比較する

(string-lessp "abc" "abz")
=>2

(string-greaterp "abc" "abz")
=>nil

(string< "abc" "abz")
=>2

(string> "abc" "ab0")
=>2

"<" (string-lessp, string<)または ">" (string-greaterp, string>)を満たせば,一致しない文字のインデックスを返す. 満たさなければ nil を返す.

逐次処理

文字列を1文字ずつ処理する

(let ((s "Lisp") (sum 0))
  (dotimes (idx (length s) sum)
    (setq sum (+ sum (char-code (elt s idx))))))
=> 408

文字コードはそれぞれ L(76), i(105), s(115), p(112) で合計408.

文字列を1行ずつ処理する

文字列からストリームを作る.

(setq text "This is test.

Ruby, the Object Oriented Script Language.")

(let ((stream (make-string-input-stream text)) line (linenum 0))
  (while (setq line (read-line stream nil))
    (setq linenum (+ linenum 1))
    (print (format nil "~D: ~A" linenum line))))
=> "1: This is test." 
   "2: " 
   "3: Ruby, the Object Oriented Script Language." 
   nil

"次"の文字列を取得する

「"A001" の次は "A002"」といった形でシーケンシャルな文字列を作っていく処理は自前で作るしかない.

(defun next-string (str)
  (labels
      ((fill-with-zero (str)
	 (format nil (concat "~"
			     (format nil "~D" (length str))
			     ",'0D")
		 (1+ (read-from-string str))))
       (next-num-string (str)
	 (cond ((string-match "^0" str) (fill-with-zero str))
	       (t (format nil "~D" (1+ (read-from-string str)))))))
    (if (string-match "\\([^0-9]*\\)\\([0-9]+\\)\\([^0-9]*\\)" str)
	(let ((m1 (match-string 1))
	      (m2 (match-string 2))
	      (m3 (match-string 3)))
	  (concat m1 (next-num-string m2) m3))
      str)))

(next-string "A001")
=> "A002"

(next-string "A002")
=> "A003"

検索・置換

文字列中で指定したパターンにマッチする部分を置換する

(substitute-string "あいうえお/かきくけこ/さしすせそ" "/" "*")
=>"あいうえお*かきくけこ*さしすせそ"

文字列中から指定したパターンを取り出す

(let ((str "1234:5678"))
  (when (string-match "^\\([0-9]+\\):\\([0-9]+\\)" str)
    (setq str (string-replace-match str "[\\1][\\2]"))))

文字列中に含まれている任意文字列の位置を求める

先頭の位置はstring-matchが返す(先頭の文字が0なので注意). 終了位置は match-end で取得.

(string-match "cd" "abcde")
=> 2

(match-end 0)
=> 4

文字列自身を取得することはできないなので substring を使う.

(let* ((str "abcde12345fghij")
       (beg (string-match "[0-9]+" str))
       (end (match-end 0)))
  (substring str beg end))
=> "12345"

任意のパターンにマッチするものを全て抜き出す

(let ((str "hoge:045-111-2222 boke:045-222-2222 ")
      (result))
  (while (string-match "\\([a-z]+:[0-9-]+\\)\\(.*\\)$" str)
    (push (match-string 1) result)
    (setq str (string-replace-match str "\\2")))
  result)

文字列の長さを数える

マルチバイトを考慮する必要は特になし.

(length "あいうえお")
=> 5

文字列の最後の文字を取り出す

(let ((s "あいうえお"))
  (subseq s (- (length s) 1)))
=>"お"

分割

文字列を区切り文字で分割する

(split-string "/usr/work/xyzzy" #\/)
=> ("usr" "work" "xyzzy")

文字列をl, n, m, ...文字目で分割する

(defun string-cut (str &rest args)
  (let ((f (lambda (str offset &rest args)
	     (if (eql args nil)
		 nil
	       (let ((pos (car args)))
		 (cons (subseq str offset pos)
		       (apply 'string-cut-recur (cons str (cons pos (cdr args))))))))))
    (apply f (cons str (cons 0 args)))))

(string-cut "abcdefghijklmn" 2 4 6 9)
=> ("ab" "cd" "ef" "ghi")

カンマ区切りの文字列を扱う

(split-string "121,,12321" #\,)
=> ("121" "12321")

(split-string "121,,12321" #\, t)
=> ("121" "" "12321")

split-string は IGNORE-EMPTY がデフォルトでは nil なのが perl, ruby 利用者には直感的でないかも.

変換

文字列を整数に変換する (to_i)

(read-from-string "999")
=> 999
   3

戻り値は多値. ひとつめは変換結果,ふたつめは読み込まなかった最初の位置(全部読めた場合は文字列の長さと等しくなる).

整数の場合は parse-integer でもよい.ただし,浮動小数点の場合はこれに相当する関数はない(read-from-string を使うこと).

(parse-integer "123")
=> 123

(parse-integer "123.4")
=> 不正な数値の形式です: "123.4"

文字列を浮動小数点に変換する (to_f)

(read-from-string "10.5")
=> 10.5
   4

read-from-string は何でも読めます.

8進文字列を整数に変換する (oct)

(read-from-string "#o17")
=> 15
   4

read-from-string は何でも読めます.

16進文字列を整数に変換する (hex)

(read-from-string "#xff")
=> 255
   4

read-from-string は何でも読めます.

数値を文字列に変換する

format を使う.

(format nil "~A" 0.1)
=> "0.1"

(format nil "~A" 100)
=> "100"

(format nil "~B" 7)
=> "111"

(format nil "~O" 16)
=> "20"

(format nil "~X" 16)
=> "10"

(format nil "~R" 1010)
=> "one thousand, ten"

(format nil "~F" 10100000)
=> "1.01e7"

シンボルを文字列に変換する

(symbolp 'abcde)
=> t

(string 'abcde)
=> "abcde"

文字列を文字のリストに変換する

(coerce "あいうえおabc" 'list)
=> (#\あ #\い #\う #\え #\お #\a #\b #\c)

(map 'list #'string "あいうえおabc")
=> ("あ" "い" "う" "え" "お" "a" "b" "c")

ASCII文字をコード値に(コード値をASCII文字に)変換する

(char-code (elt "A" 0))
=> 65

(format nil "~C" (code-char 65))
=> "A"

適当な型の値を文字列に変換する

(let ((v 256))
  (cond
   ((stringp v) v)
   ((integerp v) (format nil "~D" v))
   ((floatp v) (format nil "~F" v))
   ((symbolp v) (format nil "~A" v))
   ((characterp v) (format nil "~C" v))
   ((rationalp v) (format nil "~F" v))))
=> "256"

漢字コードを変換する

xyzzy ではバッファと文字コードがセットで管理されており,文字列ごとの文字コードを意識するようにすることは困難.

変換後の漢字コードに別のバッファを割り当てるなら簡単. lisp の処理では漢字コードを意識せず,最後に change-fileio-encoding でバッファの漢字コードを設定してやればよい.

文字列を暗号化する

ちゃんとした暗号化を実装するのは大変なので rot13/47 にする(ネタばれ防止程度にしか使っちゃダメ!)。

(defun rot13 (str)
  (coerce (mapcar (lambda (c)
		    (cond ((and (char>= c #\a) (char<= c #\m)) (code-char (+ (char-code c) 13)))
			  ((and (char>= c #\n) (char<= c #\z)) (code-char (- (char-code c) 13)))
			  ((and (char>= c #\A) (char<= c #\M)) (code-char (+ (char-code c) 13)))
			  ((and (char>= c #\N) (char<= c #\Z)) (code-char (- (char-code c) 13)))))
		  (coerce str 'list))
	  'string))

(defun rot47 (str)
  (coerce (mapcar (lambda (c)
		    (cond ((and (char>= c #\!) (char<= c #\O)) (code-char (+ (char-code c) 47)))
			  ((and (char>= c #\P) (char<= c #\~)) (code-char (- (char-code c) 47)))))
		  (remove #\ESC (coerce str 'list)))
	  'string))

(defun rot13/47 (str)
  (apply 'concat 
	 (mapcar (lambda (s)
		   (cond ((string-match "^\\$B\\(.*\\)" s)
			  (map-jis-to-internal (concat (string #\ESC) "$B" (rot47 (match-string 1)))))
			 ((string-match "^(B\\(.*\\)" s) (rot13 (match-string 1)))
			 (t (rot13 s))))
		 (split-string (map-internal-to-jis str) (string #\ESC)))))

(rot13/47  "abcあいうえおdef")
=> "nop嗔嗷嗾嘛噎qrs"

(rot13/47 "nop嗔嗷嗾嘛噎qrs")
=> "abcあいうえおdef"

(rot13 "abcABC")
=> "nopNOP"

(rot13 "nopNOP")
=> "abcABC"

(rot47 "abcABC")
=> "234pqr"

(rot47 "234pqr")
=> "abcABC"

URLエンコード

(si:www-url-encode (map-internal-to-utf-8 "あいうえお"))
=> "%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A"

整形

printf 整形

(format nil "~5,'0D" 123)
=> "00123"

大文字・小文字に揃える

(string-upcase "lisp")
=> "LISP"

(string-downcase "LISP")
=> "lisp"

nstring-upcase/downcase もある. こちらの場合は引数が破壊されます.

(setq s "abcde")
=> "abcde"

(nstring-upcase s)
=> "ABCDE"

s
=> "ABCDE"

また,文字列の先頭だけを大文字にするなら

(string-capitalize "abc")
=> "Abc"

大文字と小文字の入れ替え

(map 'string
     (lambda (ch)
       (cond
	((lower-case-p ch) (char-upcase ch))
	((upper-case-p ch) (char-downcase ch))))
     "abCde")

文字列の先頭と末尾の空白文字を削除する

(substitute-string "   abcbbbbdc   " "^[ \t]*\\([^ \t]*\\)[ \t]*$" "\\1")
=> "abcbbbbdc"

文字列の末端の改行を削除する

(let ((str "abcde\n"))
  (when (string-match "^\\(.*\\)\n$" str)
    (string-replace-match str "\\1")))
=> "abcde"

マルチバイト文字列の最後の1文字を削除する

(let ((str "あいうえお"))
  (when (string-match "^\\(.*\\).$" str)
    (setq str (string-replace-match str "\\1"))))
=> "あいうえ"

文字列を中央寄せ・左詰・右詰する

(defun ljust (num str)
  (let ((l (length str)))
    (if (< l num)
	(concat str (make-sequence 'string (- num l) :initial-element #\SPC))
      str)))

(defun rjust (num str)
  (let ((l (length str)))
    (if (< l num)
	(concat (make-sequence 'string (- num l) :initial-element #\SPC) str)
      str)))

(defun center (num str)
  (let ((l (length str)))
    (if (< l num)
	(let ((padding (make-sequence 'string (truncate (/ (- num l) 2)) :initial-element #\SPC)))
	  (concat padding
		  (if (oddp l) " " "")
		  str
		  padding))
      str)))


(ljust 15 "xyzzy")
=> "xyzzy          "

(rjust 15 "xyzzy")
=> "          xyzzy"

(center 15 "xyzzy")
=> "     xyzzy      "

ヒアドキュメントの終端文字列をインデントする

ヒアドキュメントがない.

文字列中の式を評価し値を展開する

Perl, Ruby のように文字列内に変数を埋め込む機能はないので、自前で書く必要がある。

(defun eval-string (str)
  (if (string-match "#{\\([^}]+\\)}" str)
      (concat (substring str 0 (match-beginning 0))
	      (format nil "~A" (eval (read-from-string (match-string 1))))
	      (eval-string (substring str (match-end 0))))
    str))

使用例。

(setq str "test" val 99)

(eval-string "String is #{str} and value is #{cde}.")
=> "String is test and value is 99."

(defun hello (str)
  (concat "Hello, " str "."))

(eval-string "#{(hello \"Taro\")}")
=> "Hello, Taro."

(eval-string "sum of 1, 2 and 3 is #{(+ 1 2 3)}")
=> "Sum of 1, 2 and 3 is 6"

文字列を決められた幅に収める

(abbreviate-display-string "AbcdeFghijKlmno" 10)
=> "Abc...mno"

(abbreviate-string-column "AbcdeFghijKlmno" 10)
=> "AbcdeFghij"
⚠️ **GitHub.com Fallback** ⚠️