動的配列、リスト、ハッシュマップのような、データの集まりを表現するデータ構造をコンテナと呼びます。Zenの標準ライブラリではstd.container
で、次のコンテナを提供しています。
コンテナ | 説明 |
---|---|
ArrayList | 動的配列 |
Buffer | 格納する値がu8 の動的配列 |
HashMap | キーの型、データの型、ハッシュ関数を指定可能な連想配列 |
AutoHashMap | キーの型、データの型は指定可能だが、デフォルトのハッシュ関数をもつ連想配列 |
BufMap | キーの型とデータの型とが[]u8 の連想配列 |
BufSet | []u8 キーがデータになるセット |
SinglyLinkedList | 単方向リスト |
TailQueue | 双方向リスト |
Buffer
、BufMap
、BufSet
を除くと、これらのコンテナはジェネリックです。つまり、特定の型に依存しておらず、任意型のコンテナを作り出すことができます。
全てのコンテナ型はメモリアロケータを要求します。要素を追加する操作では、メモリ確保に失敗するとerror.OutOfMemory
が返ります。
よく使うコンテナであるArrayList
、AutoHashMap
、TailQueue
について説明します。
ArrayList(T)
はT
型の値を格納する動的配列です。
ArrayList
の主要なメソッドは次のとおりです。新しく要素を追加するメソッドでは、メモリ確保失敗時に、error.OutOfMemory
が返ります。
deinit() void
: インスタンスの後始末をします。確保したメモリを解放します。len() usize
: 現在保有している要素数を取得します。at(i: usize) *mut T
: i
番目の要素のポインタを取得します。toSlice() Slice
: リストをスライスとして取得します。メモリ領域の所有権はArrayList
側にあります。append(item: T) !void
: リストの一番後ろに要素を1つ追加します。appendSlice(items: SliceConst) !void
: リストの一番後ろに要素を複数個追加します。insert(n: usize, item: T) !void
: n
番目に要素を1つ追加します。insertSlice(n: usize, items: SliceConst) !void
: n
番目に要素を複数個追加します。pop() T
: 要素を1つリストから削除して、取得します。リストが空の場合、安全性保護付き未定義動作を引き起こします。popOrNull() ?T
: pop()
と似ていますが、リストが空の場合、null
が返ります。orderedRemove(i: usize) T
: i
番目の要素を取り除き、その値を返します。取り除く要素以外の順番は保証されます。計算量はO(n)
です。swapRemove(i: usize) T
: i
番目の要素を取り除き、その値を返します。i
番目の要素には、代わりに末尾の要素が入ります。計算量はO(1)
です。ArrayList
の利用例を示します。
examples/ch09-std/container/src/array_list.zen:4:39
const heap = std.heap;
const ArrayList = std.container.ArrayList;
test "ArrayList" {
// アロケータの準備
var bytes: [1024]u8 = undefined;
var allocator = heap.FixedBufferAllocator{ .buffer = bytes[0..] };
// `usize`を格納するArrayListの初期化。`deinit`でメモリを解放
var list = ArrayList(usize){ .allocator = &mut allocator };
defer list.deinit();
// リストの後ろに要素を追加
try list.append(1);
try list.append(5);
// `len`で現在の要素数を取得、`at`で要素アクセス
ok(list.len() == @to(usize, 2));
ok(list.at(0).* == @to(usize, 1));
// `insert`で`1`番目に`10`を挿入
try list.insert(1, 10);
ok(list.len() == @to(usize, 3));
ok(list.at(1).* == @to(usize, 10));
// `pop`で後ろから要素を取り出す
ok(list.pop() == 5);
ok(list.len() == 2);
// `popOrNull`ではコンテナがからの場合、`null`が返る
var sum_of_remained: usize = 0;
while (list.popOrNull()) |value| {
sum_of_remained += value;
} else {
ok(sum_of_remained == 11);
}
}
下記の2つのメソッドは、動的配列の容量に関連するメソッドです。実際に値を保有しているかどうかとは別に、ArrayList
が確保しているメモリ領域の大きさを容量と呼びます。append
ごとに新しくメモリ領域を割り当てるのは、非効率的です。そのため、ArrayList
ではappend
時に容量が不足する場合、ある計算式に基づいて多めにメモリ領域を確保します。
capacity() usize
: メモリの再割当てなしに、T
の値を格納できる個数を返します。ensureCapacity(new_capacity: usize) !void
: new_capacity
以上の容量を確保します。examples/ch09-std/container/src/array_list.zen:41:60
test "ArrayList capacity" {
// アロケータの準備
var bytes: [1024]u8 = undefined;
var allocator = heap.FixedBufferAllocator{ .buffer = bytes[0..] };
// `usize`を格納するArrayListの初期化。`deinit`でメモリを解放
var list = ArrayList(usize){ .allocator = &mut allocator };
defer list.deinit();
// 初期化時点では容量は`0`
ok(list.capacity() == 0);
// 一度メモリを確保すると容量は`8`
try list.append(1);
ok(list.capacity() == 8);
// `16`以上の容量を確保、容量計算により`20`になる
try list.ensureCapacity(16);
ok(list.capacity() == 20);
}
次の2つのメソッドは、確保したメモリの所有権が移動するメソッドです。メモリ解放の責任は、メモリの所有権を受け取る側になります。
fromOwnedSlice(allocator: Allocator, slice: Slice) @This()
: allocator
によって確保されたslice
の所有権を取得し、ArrayList
を作成します。toOwnedSlice() Slice
: 保有しているメモリの所有権を手放し、スライスを返します。examples/ch09-std/container/src/array_list.zen:62:85
test "Ownership" {
// アロケータの準備
var bytes: [1024]u8 = undefined;
var allocator = heap.FixedBufferAllocator{ .buffer = bytes[0..] };
// メモリを動的確保し、使用 (初期化) する
var owned = try heap.alloc(&mut allocator, usize, 10);
for (owned) |*v, i| {
v.* = i;
}
// 動的確保されたメモリから`ArrayList`のインスタンスを作成する
var list = ArrayList(usize).fromOwnedSlice(&mut allocator, owned);
ok(list.len() == 10);
ok(list.at(9).* == 9);
// 所有権を手放して、スライスを返す
var owned_again = list.toOwnedSlice();
ok(list.len() == 0);
for (owned_again) |*v, i| {
ok(v.* == i);
}
heap.free(&mut allocator, owned_again);
}
ハッシュ関数としてwyhash
を使用するハッシュマップです。
AutoHashMap
の主要なメソッドを以下に示します。新しくエントリを作成するメソッドでは、メモリ確保失敗時に、error.OutOfMemory
が返ります。
ここで、K
は任意のキーを表す型、V
は任意のデータを表す型、KV
はstruct { key: K, value: V }
です。
deinit() void
: インスタンスの後始末をします。確保したメモリを解放します。clear() void
: 全てのエントリを削除します。count() usize
: 保有しているエントリ数を取得しますensureCapacity(expected_count: usize) !void
: expected_count
以上の容量を確保します。put(key: K, value: V) !?KV
: 新しいエントリを追加します。キーが同じエントリが既にある場合、古い値を返します。putNoClobber(key: K, value: V) !void
: キーが登録されていなければ、新しいエントリを追加します。キーの登録がないことをアサーションで検査しています。get(key: K) ?*mut KV
: キーが一致するエントリのデータを取得します。contains(key: K) bool
: キーが登録されていればtrue
、そうでなければfalse
が返ります。remove(key: K) ?KV
: 与えたキーを持つエントリを削除します。キーが一致するエントリがあれば削除したエントリが、そうでなければnull
が返ります。AutoHashMap
の利用例を示します。
examples/ch09-std/container/src/hash_map.zen:5:42
const heap = std.heap;
const AutoHashMap = std.container.AutoHashMap;
test "AutoHashMap" {
// アロケータの準備
var bytes: [1024]u8 = undefined;
var allocator = heap.FixedBufferAllocator{ .buffer = bytes[0..] };
// キーが`i32`、データが`[]u8`のハッシュマップ
var map = AutoHashMap(i32, [:0]u8){ .allocator = &mut allocator };
// `deinit`でメモリを解放
defer map.deinit();
ok(map.count() == 0);
var previous = try map.put(1, "one");
// 重複するキーがなければ`null`が返る
ok(previous == null);
ok(map.count() == 1);
// 既にキーが登録されている`1`を登録する
previous = try map.put(1, "1");
// 前回登録したキーとデータのペアが返ってくる
ok(previous.?.key == 1);
equalSlices(u8, "one", previous.?.value);
// キーが登録されていれば`true`が返る
ok(map.contains(1));
// キーに紐づくデータを取得する。キーが登録されていなければ`null`が返る
equalSlices(u8, "1", map.getValue(1).?);
ok(map.getValue(2) == null);
// マップから登録を削除する
var removed = map.remove(1);
// 削除されたキーとデータのペアが返ってくる
ok(removed.?.key == 1);
equalSlices(u8, "1", removed.?.value);
ok(map.count() == 0);
}
双方向リストです。ArrayList
やAutoHashMap
と違い、内部にアロケータを持ちません。これはノードの所有権が常に、TailQueue
側ではなく、呼び出し側にあることを意味します。
TailQueue
のフィールドは下記の通りです。
first
: リストの先頭ノードです。last
: リストの末尾ノードです。TailQueue
の主要なメソッドを以下に示します。ここでT
は格納するデータ型、Node
は双方向リストのノードstruct { prev: ?*mut Node, next: ?*mut Node, data: T }
です。
allocateNode(allocator: heap.Allocator) !*mut Node
: 新しいノードを作成します。destroyNode(node: *mut Node, allocator: heap.Allocator) void
: ノードを解放します。createNode(data: T, allocator: heap.Allocator) !*mut Node
: 新しいノードを作成し、data
で初期化します。append(new_node: *mut Node) void
: リストの末尾にノードを追加します。prepend(new_node: *mut Node) void
: リストの先頭にノードを追加します。insertAfter(node: *mut Node, new_node: *mut Node) void
: node
の後ろにnew_node
を挿入します。insertBefore(node: *mut Node, new_node: *mut Node) void
: node
の前にnew_node
を挿入します。remove(node: *mut Node) void
: node
を削除します。pop() ?*mut Node
: リストの末尾ノードを取り除きます。popFirst() ?*mut Node
: リストの先頭ノードを取り除きます。concatByMoving(list1: *mut Self, list2: *mut Self) void
: list1
の後ろにlist2
を連結します。list2
のノードは空になります。getLen() usize
: リストのノード数を取得します。examples/ch09-std/container/src/linked_list.zen:7:62
test "TailQueue" {
var bytes: [1024]u8 = undefined;
var allocator = heap.FixedBufferAllocator{ .buffer = bytes[0..] };
// `u32`のノードを持つ`TailQueue`を作成
var list = TailQueue(u32){};
// ノード作成
var one = try list.createNode(1, &mut allocator);
var two = try list.createNode(2, &mut allocator);
var three = try list.createNode(3, &mut allocator);
var four = try list.createNode(4, &mut allocator);
var five = try list.createNode(5, &mut allocator);
// ノード解放
defer {
list.destroyNode(one, &mut allocator);
list.destroyNode(two, &mut allocator);
list.destroyNode(three, &mut allocator);
list.destroyNode(four, &mut allocator);
list.destroyNode(five, &mut allocator);
}
list.append(two); // {2}
list.append(five); // {2, 5}
list.prepend(one); // {1, 2, 5}
list.insertBefore(five, four); // {1, 2, 4, 5}
list.insertAfter(two, three); // {1, 2, 3, 4, 5}
// 前から順番に探索
{
var it = list.first; // ?*Node
var index: u32 = 1;
while (it) |node| : (it = node.next) {
ok(node.data == index);
index += 1;
}
}
// 後ろから逆順に探索
{
var it = list.last; // ?*Node
var index: u32 = 1;
while (it) |node| : (it = node.prev) {
ok(node.data == 6 - index);
index += 1;
}
}
var first = list.popFirst(); // {2, 3, 4, 5}
var last = list.pop(); // {2, 3, 4}
list.remove(three); // {2, 4}
ok(list.first.?.data == 2);
ok(list.last.?.data == 4);
ok(list.getLen() == 2);
}
☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.