Lisp 風のシンプルな連結リストライブラリ slist を作成します。Lisp のリストは複数の「コンスセル (cons cell)」を連結したものです。ひとつのコンスセルには、データを格納する CAR (カー) という場所と、次のセルを連結する CDR (クダー) という場所からなっています。次の図を見てください。
CAR CDR CAR CDR
┌─┬─┐ ┌─┬─┐
│・│・┼─→│・│・┼→終端 (nil)
└┼┴─┘ └┼┴─┘
↓ ↓
1 2
図 : リストの構造
上図では、コンスセルを箱で表しています。左側の CAR がデータを格納する場所で、CDR が次のコンスセルと連結しています。上図の例では、先頭のコンスセルの CAR には 1 が格納され、CDR は次のコンスセルと連結しています。2 番目のコンスセルには CAR に 2 というデータが格納されています。このあとに接続されるコンスセルはもうないので、CDR にはリストの終わりを示す特別なデータ (nil) が格納されます。このようなリストを Lisp では (1 2) と表記します。
Lisp のリストは CAR にリストを格納して、リストを入れ子にすることができます。次の図を見てください。
┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│・│・┼→│・│・┼→│・│/│ / : nil
└┼┴─┘ └┼┴─┘ └┼┴─┘
↓ │ │
1 │ │
│ ↓
│ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│ │・│・┼→│・│・┼→│・│/│
│ └┼┴─┘ └┼┴─┘ └┼┴─┘
│ ↓ ↓ ↓
│ 3 12 13
↓
┌─┬─┐ ┌─┬─┐ ┌─┬─┐
│・│・┼→│・│・┼→│・│/│
└┼┴─┘ └┼┴─┘ └┼┴─┘
↓ ↓ ↓
2 10 11
図 : リストの階層構造
上図のリストを Lisp で記述すると (1 (2 10 11) (3 12 13)) になります。なお、Lisp のリストは CDR 部にコンスセルと nil だけではなく他の値を格納することができますが、今回はプログラムを簡単にするため、CDR 部にはコンスセルと nil だけしか格納できないものとします。
今回はコンスセルを次のように定義します。
リスト : セルの定義
type Any interface{}
type Cell struct {
item Any
next *Cell
}
セルを生成する関数 Cons, リストの先頭要素を取り出す関数 Car, リストの先頭要素を取り除く関数 Cdr を定義してください。なお、リストの終端は nil で表し、Car と Cdr は引数が nil であれば nil を返すものとします。
func Cons(x Any, xs *Cell) *Cell func Car(xs *Cell) Any func Cdr(xs *Cell) *Cell
Cons(1, nil) => (1) Cons(2, Cons(1, nil)) => (2 1) Car(Cons(1, nil)) => 1 Cdr(Cons(1, nil)) => () Car(Cons(2, Cons(1, nil)) => 2 Cdr(Cons(2, Cons(1, nil)) => (1)
引数 x がセル以外であれば真を返す述語 Atom, x がリスト (nil も含む) であれば真を返す述語 Listp, x がセルであれば真を返す述語 Consp, x が空リスト (nil) であれば真を返す述語 Null を定義してください。
func Atom(x Any) bool func Listp(x Any) bool func Consp(x Any) bool func Null(x *Cell) bool
Atom(1) => true Atom(Cons(1, nil)) => false Listp(1) => false Listp(Cons(1, nil)) => true Listp(Cdr(Cons(1, nil))) => true Consp(1) => false Consp(Cons(1, nil)) => true Consp(Cdr(Cons(1, nil))) => false Null(Cdr(Cons(1, nil))) => true Null(Cdr(Cons(2, Cons(1, nil)))) => false
リスト xs を Lisp 風に表示する関数 Print とリストを文字列に変換して返す関数 Sprint を定義してください。
func Print(xs Any) func Sprint(xs Any) string
Sprint(Cons(1, nil)) => "(1)" Sprint(Cons(3, Cons(2, Cons(1, nil)))) => "(3 2 1)" Sprint(Cons(Cons(3, nil), Cons(Cons(2, nil), Cons(Cons(1, nil), nil)))) => "((3) (2) (1))"
可変個の引数をリストに格納して返す関数 List, リスト xs の n 番目の要素を参照する関数 Nth, 2 つのリスト xs, ys を連結する関数 Append, リスト xs を反転する関数 Reverse, リスト xs の長さ (要素の個数) を求める関数 Length を定義してください。
func List(xs ... Any) *Cell func Nth(xs *Cell, n int) (Any, bool) func Append(xs, ys *Cell) *Cell func Reverse(xs *Cell) *Cell func Length(xs *Cell) int
List(1, 2, 3, 4) => (1 2 3 4) Nth(List(1, 2, 3, 4), 0) => 1 true Nth(List(1, 2, 3, 4), 3) => 4 true Nth(List(1, 2, 3, 4), 4) => <nil> true Append(List(1, 2, 3), List(4, 5, 6)) => (1 2 3 4 5 6) Reverse(List(1, 2, 3, 4, 5)) => (5 4 3 2 1) Length(List(1, 2, 3, 4, 5)) => 5
リスト xs の先頭から n 個の要素を取り出す関数 Take, xs の先頭から n 個の要素を取り除く関数 Drop, xs の後ろから n 個の要素を取り出す関数 Last, xs の後ろから n 個の要素を取り除く関数 ButLast を定義してください。
func Take(xs *Cell, n int) *Cell func Drop(xs *Cell, n int) *Cell func Last(xs *Cell, n int) *Cell func ButLast(xs *Cell, n int) *Cell
Take(List(1, 2, 3, 4), 0) => () Take(List(1, 2, 3, 4), 2) => (1 2) Take(List(1, 2, 3, 4), 4) => (1 2 3 4) Drop(List(1, 2, 3, 4), 0) => (1 2 3 4) Drop(List(1, 2, 3, 4), 2) => (3 4) Drop(List(1, 2, 3, 4), 4) => () |
Last(List(1, 2, 3, 4, 5), 1) => (5) Last(List(1, 2, 3, 4, 5), 2) => (4 5) Last(List(1, 2, 3, 4, 5), 4) => (2 3 4 5) ButLast(List(1, 2, 3, 4, 5), 1) => (1 2 3 4) ButLast(List(1, 2, 3, 4, 5), 2) => (1 2 3) ButLast(List(1, 2, 3, 4, 5), 4) => (1) |
引数 xs, ys の等値を判定する述語 Equal を定義してください。
func Equal(xs, ys Any) bool
引数 xs, ys の型がインターフェースの場合、実際の値が演算子 == で比較できる型であれば、== で等値を判定することができます。xs と ys の型が異なるときは false になります。xs と ys がリストの場合、型はポインタになるので、== はアドレスを比較して等値を判定します。リストの内容を比較するわけではないので、== で等値判定することはできません。
この場合、リストの要素同士が等しいかチェックしていきます。リストは入れ子にすることができるので、要素のチェックは Equal を再帰呼び出しすると簡単です。すべての要素が Equal を満たしていれば、2 つのリストは等しいことになります。
Equal(1, 1) => true Equal(1, 1.0) => false Equal(List(1, 2, 3), List(1, 2, 3)) => true Equal(List(Cons(1, nil), 2, 3), List(Cons(1, nil), 2, 3)) => true Equal(List(Cons(1, nil), 2, 3), List(Cons(5, nil), 2, 3)) => false
高階関数 MapCar, Filter, Foldl, Foldr, ForEach を定義してください。
func MapCar(f func(x Any) Any, xs *Cell) *Cell func Filter(f func(x Any) bool, xs *Cell) *Cell func Foldl(f func(a, x Any) Any, g Any, xs *Cell) Any func Foldr(f func(x, a Any) Any, g Any, xs *Cell) Any func ForEach(f func(x Any), xs *Cell)
times := func(x Any) Any { return x.(int) * 2 }
MapCar(times, List(1, 2, 3, 4, 5)) => (2 4 6 8 10)
isOdd := func(x Any) bool { return x.(int) % 2 != 0 }
Filter(isOdd, List(1, 2, 3, 4, 5)) => (1 3 5)
add := func(x, y Any) Any { return x.(int) + y.(int) }
Foldl(add, 0, List(1, 2, 3, 4, 5)) => 15
Foldr(add, 0, List(1, 2, 3, 4, 5)) => 15
var Nil *Cell
xcons := func(x, y Any) Any { return Cons(y, x.(*Cell)) }
Foldl(xcons, Nil, List(1, 2, 3, 4, 5)) => (5 4 3 2 1)
kons := func(x, y Any) Any { return Cons(x, y.(*Cell)) }
Foldr(kons, Nil, List(1, 2, 3, 4, 5)) => (1 2 3 4 5)
ForEach(func(x Any){ fmt.Println(x) }, List(1,2,3,4,5)) => (画面に出力)
1
2
3
4
5
リスト xs から x を探索する関数 Member, Find, Position, x と等しい要素をカウントする関数 Count, 高階関数 MemberIf, FindIf, PositionIf, CountIf を定義してください。
func Member(x Any, xs *Cell) *Cell func MemberIf(f func(x Any) bool, xs *Cell) *Cell func Find(x Any, xs *Cell) bool func FindIf(f func(x Any) bool, xs *Cell) (Any, bool) func Position(x Any, xs *Cell) int func PositionIf(f func(x Any) bool, xs *Cell) int func Count(x Any, xs *Cell) int func CountIf(f func(x Any) bool, xs *Cell) int
Member は xs の中に x が含まれているか調べます。もし見つからなければ nil を返します。見つけた場合は、それ以降のリストの残りを返します。Find は x を見つけたら true を返します。Position は x を見つけたらその位置を、見つからない場合は -1 を返します。
xs := List(1, 2, 3, 4, 5)
Member(1, xs) => (1 2 3 4 5)
Member(3, xs) => (3 4 5)
Member(5, xs) => (5)
Member(6, xs) => ()
isEven := func(x Any) bool { return x.(int) % 2 == 0 }
MemberIf(isEven, xs) => (2 3 4 5)
Find(3, xs) => true
Find(6, xs) => false
FindIf(isEven, xs) => 2 true
Position(3, xs) => 2
PositionIf(isEven, xs) => 1
Count(3, xs) => 1
CountIf(isEven, xs) => 2
リスト xs から x と等しい要素を削除する関数 Remove と、述語 f が真を返す要素を削除する高階関数 RemoveIf, x と等しい要素を y と置換する関数 Substitute, 述語 f が真を返す要素を引数 y と置換する関数 SubstituteIf を定義してください。
func Remove(x Any, xs *Cell) *Cell func RemoveIf(f func(x Any) bool, xs *Cell) *Cell func Substitute(x, y Any, xs *Cell) *Cell func SubstituteIf(f func(x Any) bool, y Any, xs *Cell) *Cell
xs := List(1, 2, 3, 4, 5)
Remove(3, xs) => (1 2 4 5)
isEven := func(x Any) bool { return x.(int) % 2 == 0 }
RemoveIf(isEven, xs) => (1 3 5)
Substitute(3, 30, xs) => (1 2 30 4 5)
SubstituteIf(isEven, 100, xs) => (1 100 3 100 5)
2 つのリスト xs, ys を受け取り、同じ位置にある要素をリストにまとめ、それをリストに格納して返す関数 Zip, Zip したリストを元に戻す関数 UnZip, 連想リスト xs からキー x とその値を探索する関数 Assoc, 連想リスト xs から述語 f が真を返すキーと値を求める関数 AssocIf を定義してください。
func Zip(xs, ys *Cell) *Cell func UnZip(xs *Cell) (*Cell, *Cell) func Assoc(x Any, xs *Cell) *Cell func AssocIf(f func(x Any) bool, xs *Cell) *Cell
xs := Zip(List("a", "b", "c"), List(4, 5, 6))
xs => ((a 4) (b 5) (c 6))
UnZip(xs) => (a b c) (4 5 6)
Assoc("a", xs) => (a 4)
Assoc("d", xs) => ()
AssocIf(func(x Any) bool { return x.(string) == "c" }, xs) => (c 6)
リスト : リストの基本操作
// セルの生成
func Cons(x Any, xs *Cell) *Cell {
return &Cell{x, xs}
}
// 要素を取り出す
func Car(xs *Cell) Any {
if xs == nil { return nil }
return xs.item
}
// 先頭要素を取り除く
func Cdr(xs *Cell) *Cell {
if xs == nil { return nil }
return xs.next
}
Cons は引数 x をセルの item に、xs をセルの next にセットして返すだけです。Car はセルの item を返し、Cdr はセルの next を返します。
リスト : 基本的な述語
// アトムか?
func Atom(x Any) bool {
_, ok := x.(*Cell)
return !ok
}
// セルか?
func Consp(x Any) bool {
xs, ok := x.(*Cell)
return ok && xs != nil
}
// リストか?
func Listp(x Any) bool {
_, ok := x.(*Cell)
return ok
}
// 空リストか?
func Null(xs *Cell) bool {
return xs == nil
}
Atom は型アサーションで引数 x がセルかチェックし、そうでなければ true を返します。Consp は型アサーションで引数 x がセルかチェックします。セルでかつ nil でなければ true を返します。Listp は引数 x がセルか型アサーションで調べるだけです。Null は引数 xs と nil を演算子 == で比較するだけです。
リスト : リストの表示
func sprintList(xs *Cell) string {
s := "("
for ; !Null(xs); xs = Cdr(xs) {
s += Sprint(Car(xs))
if !Null(Cdr(xs)) {
s += " "
}
}
s += ")"
return s
}
func Sprint(x Any) string {
if Listp(x) {
return sprintList(x.(*Cell))
} else {
return fmt.Sprint(x)
}
}
func Print(x Any) {
fmt.Print(Sprint(x))
}
func Println(x Any) {
fmt.Println(Sprint(x))
}
Sprint は引数 x がリストか Listp でチェックし、そうであれば関数 sprintList を呼び出します。そうでなければ、fmt.Sprint で x を文字列に変換します。sprintList は最初に変数 s を "(" に初期化します。そして、for ループでリスト xs を順番にたどり、Sprint を再帰呼び出しして要素 Car(xs) を文字列に変換します。これで Car(xs) がリストの場合でも文字列に変換することができます。次に、Cdr(xs) が空リストか Null でチェックして、そうでなければ次の要素があるので s に空白文字を連結します。最後に s と ")" を連結して返すだけです。
Print と Println は Sprint を呼び出して引数 x を文字列に変換し、fmt.Print または fmt.Println で画面に表示します。
リスト : リスト操作関数
// リストの生成
func List(xs... Any) *Cell {
var zs *Cell
for i := len(xs) - 1; i >= 0; i-- {
zs = Cons(xs[i], zs)
}
return zs
}
// リストの参照
func Nth(xs *Cell, n int) (Any, bool) {
for ; !Null(xs); xs = Cdr(xs) {
if n == 0 {
return Car(xs), true
}
n--
}
return nil, false
}
// 反転
func Reverse(xs *Cell) *Cell {
var zs *Cell
for ; !Null(xs); xs = Cdr(xs) {
zs = Cons(Car(xs), zs)
}
return zs
}
// 長さ
func Length(xs *Cell) int {
c := 0
for ; !Null(xs); xs = Cdr(xs) {
c++
}
return c
}
// 連結
func Append(xs, ys *Cell) *Cell {
if Null(xs) {
return ys
}
return Cons(Car(xs), Append(Cdr(xs), ys))
}
List は可変長引数 xs (スライス) から要素を取り出してリストに格納していきます。リストの先頭にデータを追加するのは Cons で簡単にできますが、最後尾に追加していくのはちょっと面倒です。そこで、xs の末尾から要素を取り出して、リスト zs の先頭に Cons で追加します。
Nth は for ループでリスト xs をたどり、n 番目の要素を求めるだけです。途中で xs が nil になったならば、for ループを終了して nil と false を返します。Reverse は for ループでリスト xs をたどり、その要素を順番にリスト zs の先頭に追加します。これで xs を反転したリストを作ることができます。Lengh は簡単です。for ループでリストをたどり、変数 c の値を +1 していくだけです。
Append は再帰呼び出しを使うと簡単です。xs が nil ならば ys を返します。これは空リスト (nil) と ys を連結すると ys になることを表していて、再帰呼び出しの停止条件になります。そうでなければ、xs を Car と Cdr で分割して、Cdr(xs) と ys を連結したリストの先頭に Cons で Car(xs) を追加します。これで xs と ys を連結したリストを生成することができます。このとき、xs のリストはコピーされることに注意してください。
リスト : リスト操作関数 (2)
// 先頭から n 個の要素を取り出す
func Take(xs *Cell, n int) *Cell {
if Null(xs) || n <= 0 {
return nil
}
return Cons(Car(xs), Take(Cdr(xs), n - 1))
}
// 先頭から n 個の要素を取り除く
func Drop(xs *Cell, n int) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
if n <= 0 {
break
}
n--
}
return xs
}
// 後ろから n 個の要素を取り出す
func Last(xs *Cell, n int) *Cell {
return Drop(xs, Length(xs) - n)
}
// 後から n 個の要素を取り除く
func ButLast(xs *Cell, n int) *Cell {
return Take(xs, Length(xs) - n)
}
Take は引数 n が 0 以下または引数 xs が空リストの場合は nil を返します。そうでなければ Take を再帰呼び出しして、その返り値にリストの先頭要素 Car(xs) を追加します。Drop は簡単です。引数 n が 0 以下または引数 xs が空リストになるまで Drop を再帰呼び出しするだけです。
Last は xs の先頭から (Length(xs) - n) 個の要素を Drop で取り除くだけです。逆に、ButLast は Take で (Length(xs) - n) 個の要素を取り出すだけです。
リスト : 等値の判定
func Eq(x Any, y Any) bool {
return x == y
}
func Equal(x Any, y Any) bool {
if Atom(x) && Atom(y) {
return Eq(x, y)
}
xs, ox := x.(*Cell)
ys, oy := y.(*Cell)
if ox && oy {
for !Null(xs) && !Null(ys) {
if !Equal(Car(xs), Car(ys)) {
return false
}
xs = Cdr(xs)
ys = Cdr(ys)
}
return Null(xs) && Null(ys)
}
return false
}
Equal の引数 x, y がセルでなければ関数 Eq で比較します。Eq は演算子 == で引数 x, y を比較するだけです。そうでなければ、x, y を型アサーションでリスト xs, ys に変換します。どちらかがリストに変換できない場合は false を返します。
x, y がリストの場合、Equal を再帰呼び出しして要素 Car(xs), Car(ys) を比較します。これでリストが入れ子の場合でも比較することができます。Equal の返り値が false であれば return で false を返します。そうでなければ、要素の比較を続けます。for ループが終了したら、xs, ys がともに nil であることを確認します。そうでなければ、リストの長さが異なるので結果は false になります。
リスト : 高階関数
// マッピング
func MapCar(f func(x Any) Any, xs *Cell) *Cell {
if Null(xs) {
return nil
}
return Cons(f(Car(xs)), MapCar(f, Cdr(xs)))
}
// フィルター
func Filter(f func(x Any) bool, xs *Cell) *Cell {
if Null(xs) {
return nil
} else if f(Car(xs)) {
return Cons(Car(xs), Filter(f, Cdr(xs)))
}
return Filter(f, Cdr(xs))
}
// 畳み込み
func Foldl(f func(a, x Any) Any, a Any, xs *Cell) Any {
for ; !Null(xs); xs = Cdr(xs) {
a = f(a, Car(xs))
}
return a
}
func Foldr(f func(x, a Any) Any, a Any, xs *Cell) Any {
if Null(xs) {
return a
}
return f(Car(xs), Foldr(f, a, Cdr(xs)))
}
// 巡回
func ForEach(f func(x Any), xs *Cell) {
for ; !Null(xs); xs = Cdr(xs) {
f(Car(xs))
}
}
MapCar は簡単です。引数 xs が空リストであれば nil を返します。これが再帰呼び出しの停止条件になります。そうでなければ、xs を Car と Cdr で分解し、Car(xs) に関数 f を適用した結果と MapCar(f, Cdr(xs)) の結果を Cons で結合します。Filter も簡単です。述語 f が真を返すとき要素 Car(xs) をリストに追加し、偽を返すときはリストに加えません。
Foldl は for ループで簡単にプログラムできます。累積変数 a と Car(xs) を関数 f に渡して評価し、その返り値で変数 a の値を書き換えます。Foldr は再帰呼び出しでプログラムします。引数 xs が空リストであれば a を返します。そうでなければ、Foldr を再帰呼び出しして、その返り値と要素 Car(xs) を関数 f に渡して評価します。
ForEach も簡単です。for ループでリストを先頭から順番にたどり、要素 Car(xs) を関数 f に渡して評価するだけです。
リスト : リストの探索
func Member(x Any, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) { break }
}
return xs
}
func MemberIf(f func(x Any) bool, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) { break }
}
return xs
}
func Find(x Any, xs *Cell) bool {
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) {
return true
}
}
return false
}
func FindIf(f func (x Any) bool, xs *Cell) (Any, bool) {
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) {
return Car(xs), true
}
}
return nil, false
}
func Position(x Any, xs *Cell) int {
n := 0
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) {
return n
}
n++
}
return -1
}
func PositionIf(f func(x Any) bool, xs *Cell) int {
n := 0
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) {
return n
}
n++
}
return -1
}
func Count(x Any, xs *Cell) int {
c := 0
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) {
c++
}
}
return c
}
func CountIf(f func(x Any) bool, xs *Cell) int {
c := 0
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) { c++ }
}
return c
}
これらの関数は線形探索なので難しいところはないと思います。詳細はプログラムリストをお読みください。
リスト : リストの修正
func Remove(x Any, xs *Cell) *Cell {
return Filter(func(y Any) bool { return !Eq(x, y) }, xs)
}
func RemoveIf(f func(x Any) bool, xs *Cell) *Cell {
return Filter(func(y Any) bool { return !f(y) }, xs)
}
func SubstituteIf(f func(x Any) bool, y Any, xs *Cell) *Cell {
if Null(xs) {
return nil
} else if f(Car(xs)) {
return Cons(y, SubstituteIf(f, y, Cdr(xs)))
}
return Cons(Car(xs), SubstituteIf(f, y, Cdr(xs)))
}
func Substitute(x, y Any, xs *Cell) *Cell {
return SubstituteIf(func(z Any) bool { return Eq(z, x) }, y, xs)
}
Remove と RemoveIf は Filter を呼び出すだけなので説明は割愛します。SubStituteIf は再帰呼び出しを使うと簡単です。引数 xs が空リストならば nil を返します。f(Car(xs)) の返り値が真ならば、SubstituteIf の返り値のリストに y を追加します。そうでなければ Car(xs) を追加します。Subustitute は SubstituteIf を呼び出すだけです。
リスト : 連想リスト
func Zip(xs, ys *Cell) *Cell {
if Null(xs) || Null(ys) {
return nil
}
return Cons(List(Car(xs), Car(ys)), Zip(Cdr(xs), Cdr(ys)))
}
func UnZip(xs *Cell) (*Cell, *Cell) {
if Null(xs) {
return nil, nil
}
ys, zs := UnZip(Cdr(xs))
x := Car(xs).(*Cell)
return Cons(Car(x), ys), Cons(Car(Cdr(x)), zs)
}
func Assoc(key Any, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
ys, ok := Car(xs).(*Cell)
if !ok { break }
if Eq(key, Car(ys)) {
return ys
}
}
return nil
}
func AssocIf(f func (x Any) bool, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
ys, ok := Car(xs).(*Cell)
if !ok { break }
if f(Car(ys)) {
return ys
}
}
return nil
}
Zip は引数 xs または ys が空リストならば nil を返します。そうでなければ、Zip を再帰呼び出しして、その返り値に List(Car(xs), Car(ys)) を追加します。Unzip は再帰呼び出しでプログラムすると簡単です。xs が空リストの場合、2 つの空リストを多値で返します。そうでなければ、Unzip を再帰呼び出しして、返り値 (多値) を多重代入で受け取ります。そして、受け取ったリストに要素を追加して、それを多値で返すだけです。
Assoc, AsscoIf はリスト xs を線形探索するだけですが、要素がリストなので型アサーションを使っていることに注意してください。*Cell に変換できない場合は、break で for ループを脱出して nil を返します。panic でエラーを送出してもよいでしょう。
//
// slist.go : Lisp 風の連結リスト
//
// Copyright (C) 2014-2021 Makoto Hiroi
//
package slist
import "fmt"
// 型の定義
type Any interface{}
// セルの定義
type Cell struct {
item Any
next *Cell
}
// 基本操作
// セルの生成
func Cons(x Any, xs *Cell) *Cell {
return &Cell{x, xs}
}
// 要素を取り出す
func Car(xs *Cell) Any {
if xs == nil {
return nil
}
return xs.item
}
// 先頭要素を取り除く
func Cdr(xs *Cell) *Cell {
if xs == nil {
return nil
}
return xs.next
}
// 述語
// アトムか?
func Atom(x Any) bool {
_, ok := x.(*Cell)
return !ok
}
// セルか?
func Consp(x Any) bool {
xs, ok := x.(*Cell)
return ok && xs != nil
}
// リストか?
func Listp(x Any) bool {
_, ok := x.(*Cell)
return ok
}
// 空リストか?
func Null(xs *Cell) bool {
return xs == nil
}
// 等値
func Eq(x Any, y Any) bool {
return x == y
}
func Equal(x Any, y Any) bool {
if Atom(x) && Atom(y) {
return Eq(x, y)
}
xs, ox := x.(*Cell)
ys, oy := y.(*Cell)
if ox && oy {
for !Null(xs) && !Null(ys) {
if !Equal(Car(xs), Car(ys)) {
return false
}
xs = Cdr(xs)
ys = Cdr(ys)
}
return Null(xs) && Null(ys)
}
return false
}
// リスト操作
// リストの生成
func List(xs... Any) *Cell {
var zs *Cell
for i := len(xs) - 1; i >= 0; i-- {
zs = Cons(xs[i], zs)
}
return zs
}
// リストの参照
func Nth(xs *Cell, n int) (Any, bool) {
for ; !Null(xs); xs = Cdr(xs) {
if n == 0 {
return Car(xs), true
}
n--
}
return nil, false
}
// 反転
func Reverse(xs *Cell) *Cell {
var zs *Cell
for ; !Null(xs); xs = Cdr(xs) {
zs = Cons(Car(xs), zs)
}
return zs
}
// 連結
func Append(xs, ys *Cell) *Cell {
if Null(xs) {
return ys
}
return Cons(Car(xs), Append(Cdr(xs), ys))
}
// 長さ
func Length(xs *Cell) int {
c := 0
for ; !Null(xs); xs = Cdr(xs) {
c++
}
return c
}
// 先頭から n 個の要素を取り除く
func Drop(xs *Cell, n int) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
if n <= 0 {
break
}
n--
}
return xs
}
// 先頭から n 個の要素を取り出す
func Take(xs *Cell, n int) *Cell {
if Null(xs) || n <= 0 {
return nil
}
return Cons(Car(xs), Take(Cdr(xs), n - 1))
}
// 後ろから n 個の要素を取り出す
func Last(xs *Cell, n int) *Cell {
return Drop(xs, Length(xs) - n)
}
// 後から n 個の要素を取り除く
func ButLast(xs *Cell, n int) *Cell {
return Take(xs, Length(xs) - n)
}
// 高階関数
func MapCar(f func(x Any) Any, xs *Cell) *Cell {
if Null(xs) {
return nil
}
return Cons(f(Car(xs)), MapCar(f, Cdr(xs)))
}
func Filter(f func(x Any) bool, xs *Cell) *Cell {
if Null(xs) {
return nil
} else if f(Car(xs)) {
return Cons(Car(xs), Filter(f, Cdr(xs)))
}
return Filter(f, Cdr(xs))
}
func Foldl(f func(a, x Any) Any, a Any, xs *Cell) Any {
for ; !Null(xs); xs = Cdr(xs) {
a = f(a, Car(xs))
}
return a
}
func Foldr(f func(x, a Any) Any, a Any, xs *Cell) Any {
if Null(xs) {
return a
}
return f(Car(xs), Foldr(f, a, Cdr(xs)))
}
func ForEach(f func(x Any), xs *Cell) {
for ; !Null(xs); xs = Cdr(xs) {
f(Car(xs))
}
}
// 探索
func Member(x Any, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) { break }
}
return xs
}
func MemberIf(f func(x Any) bool, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) { break }
}
return xs
}
func Find(x Any, xs *Cell) bool {
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) {
return true
}
}
return false
}
func FindIf(f func (x Any) bool, xs *Cell) (Any, bool) {
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) {
return Car(xs), true
}
}
return nil, false
}
func Position(x Any, xs *Cell) int {
n := 0
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) {
return n
}
n++
}
return -1
}
func PositionIf(f func(x Any) bool, xs *Cell) int {
n := 0
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) {
return n
}
n++
}
return -1
}
func Count(x Any, xs *Cell) int {
c := 0
for ; !Null(xs); xs = Cdr(xs) {
if Eq(x, Car(xs)) {
c++
}
}
return c
}
func CountIf(f func(x Any) bool, xs *Cell) int {
c := 0
for ; !Null(xs); xs = Cdr(xs) {
if f(Car(xs)) { c++ }
}
return c
}
// リストの修正
func Remove(x Any, xs *Cell) *Cell {
return Filter(func(y Any) bool { return !Eq(x, y) }, xs)
}
func RemoveIf(f func(x Any) bool, xs *Cell) *Cell {
return Filter(func(y Any) bool { return !f(y) }, xs)
}
func SubstituteIf(f func(x Any) bool, y Any, xs *Cell) *Cell {
if Null(xs) {
return nil
} else if f(Car(xs)) {
return Cons(y, SubstituteIf(f, y, Cdr(xs)))
}
return Cons(Car(xs), SubstituteIf(f, y, Cdr(xs)))
}
func Substitute(x, y Any, xs *Cell) *Cell {
return SubstituteIf(func(z Any) bool { return Eq(z, x) }, y, xs)
}
// 連想リスト
func Zip(xs, ys *Cell) *Cell {
if Null(xs) || Null(ys) {
return nil
}
return Cons(List(Car(xs), Car(ys)), Zip(Cdr(xs), Cdr(ys)))
}
func UnZip(xs *Cell) (*Cell, *Cell) {
if Null(xs) {
return nil, nil
}
ys, zs := UnZip(Cdr(xs))
x := Car(xs).(*Cell)
return Cons(Car(x), ys), Cons(Car(Cdr(x)), zs)
}
func Assoc(key Any, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
ys, ok := Car(xs).(*Cell)
if !ok { break }
if Eq(key, Car(ys)) {
return ys
}
}
return nil
}
func AssocIf(f func (x Any) bool, xs *Cell) *Cell {
for ; !Null(xs); xs = Cdr(xs) {
ys, ok := Car(xs).(*Cell)
if !ok { break }
if f(Car(ys)) {
return ys
}
}
return nil
}
// 表示
func sprintList(xs *Cell) string {
s := "("
for ; !Null(xs); xs = Cdr(xs) {
s += Sprint(Car(xs))
if !Null(Cdr(xs)) {
s += " "
}
}
s += ")"
return s
}
func Sprint(x Any) string {
if Listp(x) {
return sprintList(x.(*Cell))
} else {
return fmt.Sprint(x)
}
}
func Print(x Any) {
fmt.Print(Sprint(x))
}
func Println(x Any) {
fmt.Println(Sprint(x))
}
//
// yagp05.go : Yet Another Golang Problems
//
// Copyright (C) 2014-2021 Makoto Hiroi
//
package main
import (
"fmt"
. "yagp/slist"
)
func main() {
fmt.Println("----- P41 -----")
Println(Cons(1, nil))
Println(Cons(2, Cons(1, nil)))
Println(Car(Cons(1, nil)))
Println(Cdr(Cons(1, nil)))
Println(Car(Cons(2, Cons(1, nil))))
Println(Cdr(Cons(2, Cons(1, nil))))
fmt.Println("----- P42 -----")
Println(Atom(1))
Println(Atom(Cons(1, nil)))
Println(Listp(1))
Println(Listp(Cons(1, nil)))
Println(Listp(Cdr(Cons(1, nil))))
Println(Consp(1))
Println(Consp(Cons(1, nil)))
Println(Consp(Cdr(Cons(1, nil))))
Println(Null(Cdr(Cons(1, nil))))
Println(Null(Cdr(Cons(2, Cons(1, nil)))))
fmt.Println("----- P43 -----")
fmt.Println(Sprint(Cons(1, nil)))
fmt.Println(Sprint(Cons(3, Cons(2, Cons(1, nil)))))
fmt.Println(Sprint(Cons(Cons(3, nil), Cons(Cons(2, nil), Cons(Cons(1, nil), nil)))))
fmt.Println("----- P44 -----")
Println(List(1, 2, 3, 4))
fmt.Println(Nth(List(1, 2, 3, 4), 0))
fmt.Println(Nth(List(1, 2, 3, 4), 3))
fmt.Println(Nth(List(1, 2, 3, 4), 4))
Println(Append(List(1, 2, 3), List(4, 5, 6)))
Println(Reverse(List(1, 2, 3, 4, 5)))
Println(Length(List(1, 2, 3, 4, 5)))
fmt.Println("----- P45 -----")
Println(Take(List(1, 2, 3, 4), 0))
Println(Take(List(1, 2, 3, 4), 2))
Println(Take(List(1, 2, 3, 4), 4))
Println(Drop(List(1, 2, 3, 4), 0))
Println(Drop(List(1, 2, 3, 4), 2))
Println(Drop(List(1, 2, 3, 4), 4))
Println(Last(List(1, 2, 3, 4, 5), 1))
Println(Last(List(1, 2, 3, 4, 5), 2))
Println(Last(List(1, 2, 3, 4, 5), 4))
Println(ButLast(List(1, 2, 3, 4, 5), 1))
Println(ButLast(List(1, 2, 3, 4, 5), 2))
Println(ButLast(List(1, 2, 3, 4, 5), 4))
fmt.Println("----- P46 -----")
fmt.Println(Equal(1, 1))
fmt.Println(Equal(1, 1.0))
fmt.Println(Equal(List(1, 2, 3), List(1, 2, 3)))
fmt.Println(Equal(List(Cons(1, nil), 2, 3), List(Cons(1, nil), 2, 3)))
fmt.Println(Equal(List(Cons(1, nil), 2, 3), List(Cons(5, nil), 2, 3)))
fmt.Println("----- P47 -----")
times := func(x Any) Any { return x.(int) * 2 }
Println(MapCar(times, List(1, 2, 3, 4, 5)))
isOdd := func(x Any) bool { return x.(int) % 2 != 0 }
Println(Filter(isOdd, List(1, 2, 3, 4, 5)))
add := func(x, y Any) Any { return x.(int) + y.(int) }
fmt.Println(Foldl(add, 0, List(1, 2, 3, 4, 5)))
fmt.Println(Foldr(add, 0, List(1, 2, 3, 4, 5)))
var Nil *Cell
xcons := func(x, y Any) Any { return Cons(y, x.(*Cell)) }
Println(Foldl(xcons, Nil, List(1, 2, 3, 4, 5)))
kons := func(x, y Any) Any { return Cons(x, y.(*Cell)) }
Println(Foldr(kons, Nil, List(1, 2, 3, 4, 5)))
ForEach(func(x Any){ fmt.Println(x) }, List(1,2,3,4,5))
fmt.Println("----- P48 -----")
xs := List(1, 2, 3, 4, 5)
Println(Member(1, xs))
Println(Member(3, xs))
Println(Member(5, xs))
Println(Member(6, xs))
isEven := func(x Any) bool { return x.(int) % 2 == 0 }
Println(MemberIf(isEven, xs))
fmt.Println(Find(3, xs))
fmt.Println(Find(6, xs))
fmt.Println(FindIf(isEven, xs))
fmt.Println(Position(3, xs))
fmt.Println(PositionIf(isEven, xs))
fmt.Println(Count(3, xs))
fmt.Println(CountIf(isEven, xs))
fmt.Println("----- P49 -----")
Println(Remove(3, xs))
Println(RemoveIf(isEven, xs))
Println(Substitute(3, 30, xs))
Println(SubstituteIf(isEven, 100, xs))
fmt.Println("----- P50 -----")
xs = Zip(List("a", "b", "c"), List(4, 5, 6))
Println(xs)
ys, zs := UnZip(xs)
Println(ys)
Println(zs)
Println(Assoc("a", xs))
Println(Assoc("d", xs))
Println(AssocIf(func(x Any) bool { return x.(string) == "c" }, xs))
}
$ cd yagp $ ls go.mod slist yagp01.go yagp02.go yagp03.go yagp04.go yagp05.go $ cat go.mod module yagp go 1.23.2 $ ls slist slist.go $ go run yagp05.go ----- P41 ----- (1) (2 1) 1 () 2 (1) ----- P42 ----- true false false true true false true false true false ----- P43 ----- (1) (3 2 1) ((3) (2) (1)) ----- P44 ----- (1 2 3 4) 1 true 4 truefalse (1 2 3 4 5 6) (5 4 3 2 1) 5 ----- P45 ----- () (1 2) (1 2 3 4) (1 2 3 4) (3 4) () (5) (4 5) (2 3 4 5) (1 2 3 4) (1 2 3) (1) ----- P46 ----- true false true true false ----- P47 ----- (2 4 6 8 10) (1 3 5) 15 15 (5 4 3 2 1) (1 2 3 4 5) 1 2 3 4 5 ----- P48 ----- (1 2 3 4 5) (3 4 5) (5) () (2 3 4 5) true false 2 true 2 1 1 2 ----- P49 ----- (1 2 4 5) (1 3 5) (1 2 30 4 5) (1 100 3 100 5) ----- P50 ----- ((a 4) (b 5) (c 6)) (a b c) (4 5 6) (a 4) () (c 6)