シンボル型

シンボル型とは、「一言で言うとシンボルとは、文字列をアドレスで管理するデータ型」らしい。

 cf. 12. シンボル型 – もうひとつの Scheme 入門

Ruby のシンボルと同じようなものだと考えてよさそうだ。
シンボルを比較するには、アドレスを比較する eq? が使えるので、高速に比較することができる。なので、連想配列やハッシュ表のキーに使うといいようだ。

今までのエントリに出てきた中では、数値以外のアトムが実はシンボルだった。シンボルを使うにはクォートしてやらないといけない。そうでないと Scheme は変数として評価しようとする。

gosh> foo
*** ERROR: unbound variable: foo
Stack Trace:
_______________________________________
gosh> 'foo
foo

文字列とは違うことに注意。

gosh> (symbol? 'foo)
#t
gosh> (symbol? "foo")
#f

シンボルと文字列は相互に変換できる。文字列からシンボルへは string->symbol、シンボルから文字列へは symbol->string だ。

gosh> (string->symbol "Foo")
Foo
gosh> (symbol->string 'Foo)
"Foo"

処理系にもよるけど、文字列をシンボルに変換するとき小文字にしておかないと、同じつづりでも同じアドレスにならないらしい。

gosh> (eq? (string->symbol "Foo") 'Foo)
#t

同じアドレスでないと、これが #f になるわけだな。Gauche の場合は同じになるようだ。

Pythonで文字列を固定長でスライスする

たまには Python も書かねば。

タイトルのとおりなんだけど、ちょうどいい関数やメソッドが見当たらなかったので書いた。
要するに Ruby の Enumerable#each_slice みたいなのの文字列版。

def string_each_slice(s, n):
    i = 0
    r = []
    while i < len(s):
       r.append(s[i:i+n])
       i += n
    return r

s = "abcdefg"
print string_each_slice(s, 2)
^o^ > python string_each_slice.py
['ab', 'cd', 'ef', 'g']

最後が指定した固定長に足らない場合は、足らないまま返す。まあ、これは Ruby の Enumerable#each_slice と同じ振る舞いにしただけ。

文字と文字列

今日は文字と文字列について。
参考ページ:

 cf. 11. 文字、文字列 – もうひとつの Scheme 入門
 cf. 6.10 文字 – Gauche ユーザリファレンス
 cf. 6.12 文字列 – Gauche ユーザリファレンス

文字

文字のリテラルは #\ に続けて文字を書く。たとえば a という文字を表すには #\a とする。なんか変な感じ。

gosh> #\a
#\a

あと、いくつかの特殊文字などには名前がついている。名前は大文字と小文字を区別しない。

  • #\space
  • #\newline
  • #\return
  • #\tab
  • #\delete
  • #\null

以下、文字にかんする関数をいくつか。

(char? obj) は obj が文字なら真を返す。

gosh> (char? #\a)
#t

(char=? c1 c2) は文字が等しければ真を返す。

gosh> (char=? #\a #\a)
#t

(char->integer c) は文字を整数(ASCIIコード)に変換する。(integer->char n) はその逆。

gosh> (char->integer #\a)
97
gosh> (integer->char 97)
#\a

(char-alphabetic? c)(char-numeric? c)(char-whitespace? c)(char-upper-case? c)(char-lower-case? c) はそれぞれ、c がアルファベット、数字、空白文字、大文字、小文字のときに真を返す。

gosh> (char-alphabetic? #\a)
#t
gosh> (char-numeric? #\1)
#t
gosh> (char-whitespace? #\space)
#t
gosh> (char-upper-case? #\A)
#t
gosh> (char-lower-case? #\a)
#t

(char-upcase c)(char-downcase c) はそれぞれ、大文字、小文字に変換する。

gosh> (char-upcase #\a)
#\A
gosh> (char-downcase #\A)
#\a

文字列

文字列のリテラルは、たとえば “abc” みたいに ” でくくる。こっちは普通な感じ。

(string? s) は s が文字列なら真を返す。

gosh> (string? "foo")
#t

(string-length s) は文字列の長さ。

gosh> (string-length "foo")
3

(string=? s1 s2) は s1 と s2 が等しいとき真を返す。

gosh> (string=? "foo" "foo")
#t

(string-ref s idx) は s の idx番目の文字を返す。idx は0から。

gosh> (string-ref "abcde" 3)
#\d

(substring s start end) は start番目 から end – 1 番目の部分文字列を返す。Python のスライスと同じ要領だな。

gosh> (substring "abcde" 2 4)
"cd"

(string-append s1 s2) は文字列の連結。

gosh> (string-append "foo" "bar")
"foobar"

(string->list s)(list->string ls) は文字列とリストの変換。

gosh> (string->list "abc")
(#\a #\b #\c)
gosh> (list->string '(#\a #\b #\c))
"abc"

こんなところか。もっと詳しくは参考ページで。

練習問題

最後に練習問題をやっておこう。11. 文字、文字列 – もうひとつの Scheme 入門 より。

単語の初めを大文字にする関数 title-style を書いてください。

(define title-style
  (lambda (str)
    (letrec ((u (lambda (l acc)
      (cond
        ((null? l) (list->string (reverse acc)))
        ((char-whitespace? (car l)) (u (cdr l) (cons (car l) acc)))
        (else (d (cdr l) (cons (char-upcase (car l)) acc))))))
          (d (lambda (l acc)
            (cond
              ((null? l) (list->string (reverse acc)))
              ((char-whitespace? (car l)) (u (cdr l) (cons (car l) acc)))
              (else (d (cdr l) (cons (car l) acc)))))))
                (u (string->list str) '()))))

(print (title-style "one size fits all"))

実行結果:

^o^ > gosh title-style.scm
One Size Fits All

練習:ファイルへ出力

ファイルへの出力の練習として、

 cf. 9. 入出力 – もうひとつの Scheme 入門

の練習問題2 と3 をやってみた。

練習問題2

ファイルをコピーする関数 (my-copy-file) を書いてください。

(define my-copy-file
  (lambda (infile outfile)
    (let ((in (open-input-file infile))
          (out (open-output-file outfile)))
      (let loop ((c (read-char in)))
        (if (eof-object? c)
            (begin
              (close-input-port in)
              (close-output-port out))
            (begin
              (display c out)
              (loop (read-char in))))))))

実行例:

^o^ > type sample.txt
Hello world!
Scheme is an elegant programming language.

^o^ > gosh -I.
gosh> (load "my-copy-file.scm")
#t
gosh> (my-copy-file "sample.txt" "sample.out")
#<undef>
gosh> (exit)

^o^ > type sample.out
Hello world!
Scheme is an elegant programming language.

練習問題3

任意個の文字列の引数をとり、それらを標準出力に1行に1つずつ出力する関数 print-lines を書いてください。

(define print-lines
  (lambda args
    (let loop ((ls args))
      (if (pair? ls)
          (begin
            (display (car ls))
            (newline)
            (loop (cdr ls)))))))

実行例:

gosh> (load "print-lines.scm")
#t
gosh> (print-lines "foo" "bar" "baz")
foo
bar
baz
#<undef>

ファイルへ出力

昨日はファイルからの入力を覚えたので、今日はファイルへの出力。

 cf. 9. 入出力 – もうひとつの Scheme 入門

open-output-file、close-output-port

(open-output-file filename) は filename を出力用に開いてポートを返す。使い終わったら (close-output-port port) でポートを閉じる。
出力は display。newline で改行する。

(define write-to-file
  (lambda (file-name message)
    (let ((p (open-output-file file-name)))
      (begin
        (display message p)
        (newline p)
        (close-output-port p)))))

実行例:

gosh> (load "write-to-file1.scm")
#t
gosh> (write-to-file "hello1.txt" "Hello, world.")
#<undef>
gosh> (exit)

^o^ > type hello1.txt
Hello, world.

call-with-output-file

(call-with-output-file filename procedure) は filename を出力用に開いて procedure を評価する。procedure はポートを引数に取る関数。

(define write-to-file
  (lambda (file-name message)
    (call-with-output-file file-name
      (lambda (p)
        (begin
          (display message p)
          (newline p)
          (close-output-port p))))))

実行例:

gosh> (load "write-to-file2.scm")
#t
gosh> (write-to-file "hello2.txt" "Hello, scheme.")
#<undef>
gosh> (exit)

^o^ > type hello2.txt
Hello, scheme.

with-output-to-file

(with-output-to-file filename procedure) は filename を標準入力として開き、procedure を評価する。procedure は引数なしの関数。終わったらポートは勝手に閉じられる。

(define write-to-file
  (lambda (file-name message)
    (with-output-to-file file-name
      (lambda ()
        (begin
          (display message)
          (newline))))))

実行例:

gosh> (load "write-to-file3.scm")
#t
gosh> (write-to-file "hello3.txt" "Hello, world. This is scheme.")
#<undef>
gosh> (exit)

^o^ > type hello3.txt
Hello, world. This is scheme.

練習:ファイルからの入力

ファイルからの入力の練習。

 cf. 9. 入出力 – もうひとつの Scheme 入門

の練習問題1。

ファイルの内容を1行ずつのリストにして返す関数 read-lines を書いてください。 hello.txt に適用すると次のようになるようにして下さい。’\n’ は #\Newline です。 改行文字は残すようにしてください。

(read-lines “hello.txt”) ⇒ (“Hello world!\r\n” “Scheme is an elegant programming language.\r\n”)

(define read-lines
  (lambda (file-name)
    (let ((p (open-input-file file-name)))
      (let loop ((lines '()) (line '()) (c (read-char p)))
        (cond
          ((eof-object? c)
           (begin
             (close-input-port p)
             (reverse lines)))
          ((eq? c #\Newline) (loop (cons (list->string (reverse (cons c line))) lines) '() (read-char p)))
          (else (loop lines (cons c line) (read-char p))))))))

実行例:

^o^ > gosh -I.
gosh> (load "read-lines.scm")
#t
gosh> (read-lines "sample.txt")
("Hello world!\r\n" "Scheme is an elegant programming language.\r\n")

ファイルからの入力

ファイルからの入力の仕方。

 cf. 9. 入出力 – もうひとつの Scheme 入門

より。

open-input-file、read-char、eof-object?

open-input-file はファイルを開いてファイルからの入力ポートを返す。read-char はポートから1文字読み込む。ファイルの終わりに達すると、eof-object を返すので、eof-ofject? でチェックする。

次の例は、sample.txt からファイルの内容を読み込む read-file 関数。

(define read-file
  (lambda (file-name)
    (let ((p (open-input-file file-name)))
      (let loop ((ls '()) (c (read-char p)))
        (if (eof-object? c)
            (begin
              (close-input-port p)
              (list->string (reverse ls)))
            (loop (cons c ls) (read-char p)))))))

3行目で入力ポートを開いて変数 p を束縛している。4行目で p から1文字読み込んで、5行目で eof-object 可動化のチェック。もしファイル終端ならポートを閉じて(7行目)文字列を返し、そうでないなら再帰(9行目)している。
(begin ...) というのは、複数のS式を順に評価して最後のS式の値を返すものらしい。

実行例:

^o^ > type sample.txt
Hello world!
Scheme is an elegant programming language.

^o^ > gosh -I.
gosh> (load "read-file1.scm")
#t
gosh> (read-file "sample.txt")
"Hello world!\r\nScheme is an elegant programming language.\r\n"

call-with-input-file

(call-with-input-file filename procedure) というかたちをしていて、procedure は入力ポートを引数にとる関数。エラー処理をしてくれるのでこちらのほうが便利、と書いてある。
call-with-input-file を使って read-file 関数を書くと次にようになる。

(define read-file
  (lambda (file-name)
    (call-with-input-file file-name
      (lambda (p)
        (let loop ((ls '()) (c (read-char p)))
          (if (eof-object? c)
              (begin
                (close-input-port p)
                (list->string (reverse ls)))
              (loop (cons c ls) (read-char p))))))))

ファイルから文字を読み込んで返すという本質的なところは read-file1.scm とおなじ。違うのは、let で入力ポートを束縛する代わりに、ポートを引数に取る関数を使っているところ。

実行例:

gosh> (load "read-file2.scm")
#t
gosh> (read-file "sample.txt")
"Hello world!\r\nScheme is an elegant programming language.\r\n"

with-input-from-file

with-input-from-file は、ファイルを標準入力として開く。call-with-input-file と同じように (with-input-from-file filename procedure) というかたちをしているけど、procedure は引数をとらない。つまり入力は標準入力に固定されるってわけだな。入力ポートは勝手に閉じられる。

(define read-file
  (lambda (file-name)
    (with-input-from-file file-name
      (lambda ()
        (let loop ((ls '()) (c (read-char)))
          (if (eof-object? c)
              (list->string (reverse ls))
              (loop (cons c ls) (read-char))))))))

実行例:

gosh> (load "read-file3.scm")
#t
gosh> (read-file "sample.txt")
"Hello world!\r\nScheme is an elegant programming language.\r\n"

これが一番使いやすそうだ。

[追記]
最後の例では read-char を引数なしで使っている。read-char 関数は引数がなければ標準入力から読み込むってことでいいのかな。