第5章
List・Dict・Set・Optional
Python の list・dict・set にあたる List・Dict・Set と、「値がないかもしれない」を型で表す Optional の使い方を学びます。範囲外のアクセスや存在しないキーなど、うまくいかないときの動きも実際に確かめます。
この章の内容
Python では、[1, 2, 3] や {"a": 1} と書くだけでリストや辞書が手に入り、何でも入れられました。Mojo にも同じ役割の型があります。ただし、Mojo の型は 要素の型がそろっている ことが前提で、「値がないかもしれない」場合の扱いも、Python とは違います。
この章では、Python プログラマーが毎日使うコレクションを Mojo ではどう書くのかを、動かしながら確かめていきます。うまくいかないとき(範囲の外を読んだとき、存在しないキーを引いたとき)に何が起きるかも、ごまかさずに見ます。
この章を終えると、次のことができるようになります。
List[Int]を作り、要素の追加・読み出し・くり返し・並べ替えができる- リストの要素の型がそろっていなければならない理由と、範囲外アクセスのときの動きを説明できる
Dict[String, Int]で値の登録・検索・列挙ができ、存在しないキーを安全に扱えるSet[Int]で重複のない集まりを作り、和・積・差を取れるOptional[Int]で「値がない」を表し、安全に取り出せる
型の仕組みそのもの(ジェネリクス)は第6章と第15章、値の渡し方と所有権は第11章で学びます。この章では、必要になった場面で最小限だけ触れます。
5.1List を作る
Mojo の List は、Python の list にいちばん近い型です。要素の型を角かっこで指定して List[Int] のように書きます。まず作って、増やして、読んでみましょう。
def main():
var nums: List[Int] = [10, 20, 30]
nums.append(40)
print(len(nums))
print(nums[0], nums[3])
print(nums)~$ mojo run list_basic.mojo 4 10 40 [10, 20, 30, 40]
1行ずつ見ていきます。
var nums: List[Int] = [10, 20, 30]…Intが入るリストを作ります。右辺の[...]は Python と同じ書き方です。nums.append(40)… 末尾に追加します。メソッド名も Python と同じです。len(nums)… 要素数です。1つ追加したので 4 になっています。nums[0]、nums[3]… 添字(インデックス)で読みます。0 から数えるのも同じです。print(nums)… リストをそのまま表示できます。Python と同じ見た目です。
型の注釈を省くと別の型になる
Python の感覚で、型の注釈を付けずに書いたらどうなるでしょうか。
def main():
var nums = [10, 20, 30]
nums.append(40)
print(nums)~$ mojo run array_trap.mojo /home/user/array_trap.mojo:3:9: error: 'Array[Int, Int(3)]' value has no attribute 'append' nums.append(40) ~~~~^ mojo: error: failed to parse the provided Mojo source module
append が見つからないというエラーになりました。メッセージの Array[Int, Int(3)] に注目してください。注釈のない [10, 20, 30] は List ではなく、長さが 3 に固定された Array になります。固定長なので、要素を増やす append はありません。
固定長の Array でも、添字での読み書きや len() は使えます。長さを変えない小さな表なら、Array のままで十分です。この章では、増やせる List を中心に説明します。
空のリストと、他の作り方
空のリストを作って、あとから append で埋めるのは定番の書き方です。決まった長さを同じ値で埋めたり、range から作ったりすることもできます。
def main():
var squares = List[Int]()
for i in range(5):
squares.append(i * i)
print(squares)
var zeros = List[Int](length=3, fill=0)
print(zeros)
var counting = List(range(5))
print(counting)~$ mojo run list_create.mojo [0, 1, 4, 9, 16] [0, 0, 0] [0, 1, 2, 3, 4]
List[Int]()… 空のList[Int]です。型は角かっこの中で指定します。List[Int](length=3, fill=0)… 長さ 3 で、すべて 0 のリストです。Python の[0] * 3にあたります。List(range(5))…rangeから作ります。要素の型Intはrangeから決まるので、書かなくてもかまいません。
5.2要素の型はそろっている
Python の list には、整数も文字列も小数も一緒に入れられました。Mojo でやってみましょう。
def main():
var mixed = [1, "two"]
print(len(mixed))~$ mojo run mixed.mojo /home/user/mixed.mojo:2:18: error: value of type 'IntLiteral[1]' is not compatible with value of type 'StringLiteral["two"]' var mixed = [1, "two"] ^ ~~~~~ /home/user/mixed.mojo:3:15: error: use of unknown declaration 'mixed' print(len(mixed)) ^~~~~ mojo: error: failed to parse the provided Mojo source module
1つめのエラーは「IntLiteral[1](整数)と StringLiteral["two"](文字列)は互換性がない」と言っています。mixed が作れなかったので、2つめのエラーで mixed が宣言されていないと言われています。これは1つの間違いから複数のエラーが出る例です(第1章で見たとおり、一番上のエラーから直します)。
List[Int] のリストに、あとから文字列を追加しようとした場合も同じです。
def main():
var nums: List[Int] = [1, 2]
nums.append("three")
print(nums)~$ mojo run wrong_append.mojo /home/user/wrong_append.mojo:3:17: error: invalid call to 'append': value passed to 'value' cannot be converted from 'StringLiteral["three"]' to 'Int' nums.append("three") ~~~~~~~~~~~ ^~~~~~~ Imported from /home/user/wrong_append.mojo:1: Mojo/stdlib/std/collections/list.mojo:882:1: note: function declared here def append(mut self, var value: T, /) where conforms_to(T, Movable) # note - synthetic signature ^ mojo: error: failed to parse the provided Mojo source module
「append に渡した値を StringLiteral["three"] から Int に変換できない」というエラーです。エラーは実行の前、コンパイルの段階で見つかります。Python のように、動かしてみて初めて型の食い違いに気づく、ということはありません。
どうしても色々な型を混ぜたいときのための道具もありますが、本書では扱いません。まずは「1つのリストには1種類の型」と考えてください。
5.3範囲の外を読むと
存在しない添字を読んだら、何が起きるでしょうか。Python なら IndexError が出て、try で受け止められました。Mojo の動きを見てみましょう。
def main():
var nums: List[Int] = [10, 20, 30]
var i = 7
print("before")
print(nums[i])
print("after")~$ mojo run oob.mojo 2>&1 | grep -v '^#'; echo "終了コード: ${PIPESTATUS[0]}" before At: /home/user/oob.mojo:5:15: Assert Error: index 7 is out of bounds, valid range is 0 to 2 mojo: error: execution crashed To get a symbolicated stack trace, compile your program using `mojo build` with debug info enabled (e.g., `-debug-level=line-tables`) and execute it separately. 終了コード: 1
2>&1 | grep -v '^#' は、長いスタックトレース(#0 0x... で始まる行)を隠すための細工です。${PIPESTATUS[0]} で mojo run 自身の終了コードを見ています。
出力を読みます。before は表示されましたが、after は表示されていません。Assert Error: index 7 is out of bounds, valid range is 0 to 2 は「添字 7 は範囲外です。有効なのは 0 から 2 です」という意味で、続けて mojo: error: execution crashed(実行がクラッシュした)と出て、プログラムが止まりました。
負の添字は使えない
Python では nums[-1] で末尾の要素を取れました。Mojo ではどうなるでしょうか。
def main():
var nums: List[Int] = [10, 20, 30]
print(nums[-1])~$ mojo run negative.mojo /home/user/negative.mojo:1:5: error: function instantiation failed def main(): ^ /home/user/negative.mojo:3:15: note: call expansion failed with parameter value(s): (..., ...) print(nums[-1]) ^ Mojo/stdlib/std/collections/list.mojo:1467:9: note: function instantiation failed Mojo/stdlib/std/collections/list.mojo:1478:9: note: constraint failed: negative indexing is not supported, use e.g. `x[len(x) - 1]` mojo: error: failed to run the pass manager
コンパイルの段階で、「負の添字はサポートされていない。x[len(x) - 1] のように書く」と教えてくれます。末尾の要素は、メッセージのとおりに書きます。
では、添字が変数に入っているときはどうでしょう。コンパイルの時点では値が分からないので、実行してみないと分かりません。
def main():
var nums: List[Int] = [10, 20, 30]
print(nums[len(nums) - 1])
var i = -1
print(nums[i])~$ mojo run negative_var.mojo 2>&1 | grep -v '^#'; echo "終了コード: ${PIPESTATUS[0]}" 30 At: /home/user/negative_var.mojo:5:15: Assert Error: index -1 is out of bounds, valid range is 0 to 2 mojo: error: execution crashed To get a symbolicated stack trace, compile your program using `mojo build` with debug info enabled (e.g., `-debug-level=line-tables`) and execute it separately. 終了コード: 1
len(nums) - 1 で末尾の 30 が取れました。一方、変数 i = -1 で読むと、「添字 -1 は範囲外」というエラーでクラッシュします。Python のように「後ろから数える」意味にはなりません。
5.4くり返し・並べ替え・切り出し
for でくり返す
def main():
var nums: List[Int] = [1, 2, 3, 4]
var total = 0
for n in nums:
total += n
print("total:", total)
for i in range(len(nums)):
nums[i] *= 10
print(nums)~$ mojo run list_iter.mojo total: 10 [10, 20, 30, 40]
for n in nums: は Python と同じ形で、要素を順に取り出します。要素を 書き換えたい ときは、range(len(nums)) で添字を作り、nums[i] *= 10 のように添字を通して書き換えます。
ここで、for n in nums: の中で n を書き換えようとするとどうなるでしょうか。
def main():
var nums: List[Int] = [1, 2, 3]
for n in nums:
n += 1
print(nums)~$ mojo run iter_mutate.mojo /home/user/iter_mutate.mojo:4:9: error: expression must be mutable for in-place operator destination n += 1 ^ mojo: error: failed to parse the provided Mojo source module
「書き換えられない式に += は使えない」というエラーになりました。ループ変数 n は、リストの中身を 読むだけ の参照です。Python のように「値を受け取って好きに書き換える」ものではありません。この「参照」の考え方は、第11章で詳しく学びます。ここでは「書き換えるなら添字を使う」と覚えておいてください。
追加・取り出し・並べ替え
def main():
var nums: List[Int] = [3, 1, 2]
nums.append(5)
nums.insert(1, 9)
print(nums)
var last = nums.pop()
print(last, nums)
sort(nums)
print(nums)
nums.reverse()
print(nums)
print(2 in nums, 7 in nums)~$ mojo run list_ops.mojo [3, 9, 1, 2, 5] 5 [3, 9, 1, 2] [1, 2, 3, 9] [9, 3, 2, 1] True False
insert(1, 9)… 添字 1 の位置に 9 を差し込みます。pop()… 末尾の要素を取り除き、その値を返します。sort(nums)… 関数sortにリストを渡して、その場で昇順に並べ替えます。Python のnums.sort()のようなメソッドではありません。reverse()… 順序を逆にします。2 in nums… 要素が含まれるかを調べます。Python と同じです。
sort は Int だけでなく、String や Float64 のリストにも使えます(比較できる型であれば並べ替えられます)。
スライスで一部を切り出す
def main():
var nums: List[Int] = [1, 2, 3, 4, 5]
print(nums[:2])
print(nums[2:])
print(nums[::2])
var part = List(nums[1:4])
part.append(100)
print(part)
print(nums)~$ mojo run list_slice.mojo [1, 2] [3, 4, 5] [1, 3, 5] [2, 3, 4, 100] [1, 2, 3, 4, 5]
nums[:2] や nums[::2](1つおき)のようなスライスは、Python と同じ書き方で使えます。ただし、スライスの結果は新しいリストではなく、元のリストの一部を指す Span という型です。独立したリストが欲しいときは、List(nums[1:4]) のように List(...) で包みます。part に 100 を足しても、nums が変わっていないことが出力から分かります。
リストのコピー
Python では b = a とすると、a と b は同じリストを指しました。Mojo ではどうでしょうか。
def main():
var a: List[Int] = [1, 2]
var b = a
b.append(3)
print(a, b)~$ mojo run list_copy_trap.mojo /home/user/list_copy_trap.mojo:3:13: error: value of type 'List[Int]' cannot be implicitly copied, it does not conform to 'ImplicitlyCopyable' var b = a ^ /home/user/list_copy_trap.mojo:3:13: note: consider transferring the value with '^' var b = a ^ ^ /home/user/list_copy_trap.mojo:3:13: note: you can copy it explicitly with '.copy()' var b = a ^ .copy() mojo: error: failed to parse the provided Mojo source module
List[Int] は暗黙にコピーできない(ImplicitlyCopyable ではない)というエラーです。メッセージの提案どおり、コピーしたいときは .copy() と 明示 します。
def main():
var a: List[Int] = [1, 2]
var b = a.copy()
b.append(3)
print(a, b)~$ mojo run list_copy.mojo [1, 2] [1, 2, 3]
b に追加しても a は変わりません。大きなリストを、うっかりコピーして遅くなる、という事故を防ぐための仕様です。「コピーか、移動か、参照か」の詳しい話は第11章で学びます。
5.5メモリの中ではどう並ぶか
Mojo が要素の型をそろえたがる理由を、図で確かめましょう。
![Python の list と Mojo の List[Int] のメモリ上の並び方](figures/ch05/list-layout.svg)
Python の list の各スロットは、別の場所にあるオブジェクトへのポインタ です。そのため、整数も文字列も小数も入れられますが、値を読むたびにポインタをたどる必要があります。
一方、Mojo の List[Int] 本体が持っているのは、(1) 要素の並びの先頭の位置、(2) 長さ、(3) 容量の3つだけです。要素の Int 自体は、1つの連続した領域に 値そのまま で並びます。実際に大きさを調べてみましょう。
from std.sys import size_of
def main():
print(size_of[Int]())
print(size_of[List[Int]]())
print(size_of[Optional[Int]]())~$ mojo run sizes.mojo 8 24 16
Int は 8 バイトです。List[Int] の本体は、要素がいくつあっても 24 バイト(3つの項目が8バイトずつ)で、要素は別の連続した領域に置かれます。3つ目の Optional[Int] は、あとで出てきます。
連続して並んでいると、CPU が隣の要素を先読みしやすくなり、第20章で学ぶ SIMD(1命令で複数の値を処理する仕組み)も使いやすくなります。「型がそろっている」ことは、不便な制限であると同時に、速さの源でもあります。
5.6Dict:キーと値の対応表
Dict は Python の dict にあたります。キーと値の型を Dict[String, Int] のように2つ指定します。
def main() raises:
var ages: Dict[String, Int] = {"alice": 30, "bob": 25}
ages["carol"] = 41
ages["bob"] = 26
print(len(ages))
print(ages["alice"])
print("bob" in ages, "dave" in ages)
print(ages)~$ mojo run dict_basic.mojo 3 30 True False {alice: 30, bob: 26, carol: 41}
{"alice": 30, "bob": 25}… Python と同じ辞書リテラルです。Dict[String, Int]と注釈を付けておきます。ages["carol"] = 41… 新しいキーの登録です。すでにあるキー("bob")に代入すると上書きになります。"bob" in ages… キーがあるかどうかを調べます。print(ages)… 登録した順に表示されました。
def main() raises: の raises は「この関数の中でエラーが起こるかもしれない」という印です。ages["alice"] のようなキーでの検索は、キーがなければエラーを起こす可能性があるので、これを使う関数には raises が必要になります。エラー処理の詳しい話は第9章で学びます。
存在しないキーを引くと
Python では KeyError でした。Mojo ではどうなるでしょうか。
def main() raises:
var ages: Dict[String, Int] = {"alice": 30}
print("start")
print(ages["dave"])
print("end")~$ mojo run dict_missing.mojo start stack trace was not collected. Enable stack trace collection with environment variable `MODULAR_DEBUG=stack-trace-on-error` Unhandled exception caught during execution: DictKeyError[String]() mojo: error: execution exited with a non-zero result: 1
今回は、範囲外アクセスと違い、クラッシュではなく エラー(例外) です。Unhandled exception caught during execution: DictKeyError[String]() が「受け止められなかった例外」を表します。start は表示され、end は表示されません。例外なので、try / except で受け止めることもできます。
def main():
var ages: Dict[String, Int] = {"alice": 30}
try:
print(ages["dave"])
except e:
print("error:", e)
print("続けて実行できます")~$ mojo run dict_try.mojo error: DictKeyError[String]() 続けて実行できます
今度は main に raises が要りません。try で受け止めているので、エラーは関数の外に出ないからです。except e: の e はエラーの値で、print でメッセージを表示できます。メッセージはキーそのものを含まず、DictKeyError[String]() という簡素なものです。
get で安全に引く
キーがないかもしれないときは、get が便利です。
def main():
var ages: Dict[String, Int] = {"alice": 30}
print(ages.get("alice"))
print(ages.get("dave"))
print(ages.get("dave", -1))~$ mojo run dict_get.mojo 30 None -1
get("dave") は、例外を起こさずに None を返しました。get("dave", -1) のように2つめの引数を渡すと、キーがないときにその値(既定値)を返します。get が返すものの正体は、この章の最後の Optional の節で説明します。
キー・値・組をくり返す
def main() raises:
var ages: Dict[String, Int] = {"alice": 30, "bob": 25, "carol": 41}
for key in ages:
print("key:", key, "->", ages[key])
for v in ages.values():
print("value:", v)
for item in ages.items():
print(item.key, item.value)
print(ages.pop("alice"))
print(len(ages))~$ mojo run dict_iter.mojo key: alice -> 30 key: bob -> 25 key: carol -> 41 value: 30 value: 25 value: 41 alice 30 bob 25 carol 41 30 2
for key in ages:… キーを順に取り出します。Python と同じです。ages.values()… 値だけを取り出します。キーだけならages.keys()です。ages.items()… キーと値の組です。Python のようにfor k, v in ...と タプルに展開するのではなく、1つの要素itemのitem.keyとitem.valueで読みます。ages.pop("alice")… キーを取り除き、その値を返します。
5.7Set:重複のない集まり
Set は、同じ値を2度は持たない集まりです。使う前に import が必要 です。
from std.collections import Set
def main() raises:
var a: Set[Int] = {1, 2, 3}
a.add(3)
a.add(4)
print(len(a))
print(2 in a, 9 in a)
a.remove(1)
print(a)
var b: Set[Int] = {3, 4, 5}
print(a | b)
print(a & b)
print(a - b)~$ mojo run set_basic.mojo 4 True False {2, 3, 4} {2, 3, 4, 5} {3, 4} {2}
from std.collections import Set…ListやDictと違い、Setは import しないと使えません。忘れると「Setが宣言されていない。from std.collections import Setを追加してください」というエラーになります。a.add(3)… すでにある値を足しても、要素数は増えません。そのあとのa.add(4)で 4 要素になります。2 in a… 含まれるかどうか。集合は検索が速いのが特長です。a.remove(1)… 要素を取り除きます。存在しない値をremoveするとエラー になるので、raisesが必要です。存在しなくてもよいならdiscardを使います。a | b(和)、a & b(積)、a - b(差)… Python と同じ演算子で集合演算ができます。union、intersection、differenceというメソッドもあります。
5.8Optional:値がないかもしれない
Python では、「値がない」を None で表しました。どんな変数にも None を入れられるので、関数が int か None を返す、ということが日常的に起きます。
Mojo の Int には None を入れられません。試してみましょう。
def main():
var x: Int = None
print(x)~$ mojo run none_int.mojo /home/user/none_int.mojo:2:18: error: cannot implicitly convert 'None' value to 'Int' var x: Int = None ^~~~ mojo: error: failed to parse the provided Mojo source module
「None を Int に変換できない」というエラーです。Int は必ず整数が入っている型です。それでは、「見つからなかった」を返したいときはどうするのでしょうか。「あるかもしれないし、ないかもしれない」を表す型が Optional です。Optional[Int] は、Int か None のどちらかです。
def find(xs: List[Int], target: Int) -> Optional[Int]:
for i in range(len(xs)):
if xs[i] == target:
return i
return None
def main():
var xs: List[Int] = [5, 6, 7]
var hit = find(xs, 6)
if hit:
print("found at", hit.value())
else:
print("not found")
var miss = find(xs, 99)
if miss:
print("found at", miss.value())
else:
print("not found")
print(hit)
print(miss)
print(hit.or_else(-1), miss.or_else(-1))~$ mojo run optional_basic.mojo found at 1 not found 1 None 1 -1
-> Optional[Int]… この関数は「IntかNone」を返します。return iとreturn None… 見つかれば添字を、見つからなければNoneを返します。IntからOptional[Int]へは自動で変換されます。if hit:… 値が入っているときだけ真になります。Python のif x is not None:にあたります。hit.value()… 中のIntを取り出します。hit.or_else(-1)… 値があればその値を、なければ引数(-1)を返します。print(hit)とprint(miss)… 値があれば中身の1が、なければNoneが表示されます。
先ほどの Dict.get に話を戻します。ages.get("dave") が返していたのは、実は Optional[Int] です。None が表示されたのはそのためで、if で確かめて .value() で取り出す方法がそのまま使えます。
def main():
var ages: Dict[String, Int] = {"alice": 30}
var found = ages.get("alice")
if found:
print("alice is", found.value() + 1, "next year")
var missing = ages.get("dave")
if not missing:
print("dave is unknown")~$ mojo run dict_get_optional.mojo alice is 31 next year dave is unknown
5.9Python との対応表
| やりたいこと | Python | Mojo |
|---|---|---|
| リストを作る | nums = [1, 2, 3] | var nums: List[Int] = [1, 2, 3] |
| 末尾に追加 | nums.append(4) | nums.append(4) |
| 末尾の要素 | nums[-1] | nums[len(nums) - 1] |
| 範囲外の添字 | IndexError(try で受け止められる) | クラッシュ |
| 並べ替え | nums.sort() | sort(nums) |
| コピー | b = a.copy() | b = a.copy()(暗黙のコピーは不可) |
| 異なる型を混ぜる | できる | できない |
| 辞書を作る | d = {"a": 1} | var d: Dict[String, Int] = {"a": 1} |
| 存在しないキー | KeyError | raises を要求。実行時は DictKeyError |
| キーがあるか | "a" in d | "a" in d |
| 安全に引く | d.get("a") | d.get("a")(Optional を返す) |
| 組を取り出す | for k, v in d.items() | for item in d.items()、item.key、item.value |
| 集合 | {1, 2, 3} | from std.collections import Set のあと var s: Set[Int] = {1, 2, 3} |
| 値がない | None(どんな型にも) | Optional[T](Optional の型だけ) |
5.10まとめ
List[Int]は Python の list にあたる、要素の型がそろった可変長の列です。型を注釈しないリテラルは、長さ固定のArrayになります。- 要素の型が違うものは入れられません。食い違いはコンパイルの段階で分かります。
- 負の添字は使えず、範囲外のアクセスは例外ではなくクラッシュです。自分で範囲を確かめます。
forのループ変数は読むだけです。書き換えるときは添字を使います。コピーは.copy()と明示します。Dict[K, V]はキーでの検索がraisesを要求します。存在しないキーはDictKeyErrorになります。安全に引くにはgetを使います。Setは import が必要です。|・&・-で集合演算ができます。Optional[T]は「TかNone」を表します。if opt:で確かめてから.value()で取り出すか、.or_else()で既定値を与えます。
確認クイズ
var nums = [10, 20, 30]と書いたあとにnums.append(40)を呼ぶと、なぜエラーになりますか?解答を見る
型を注釈しないリテラルは
Listではなく、長さが固定されたArrayになるためです。appendを使いたいときはvar nums: List[Int] = [10, 20, 30]と型を書きます。List[Int]の添字に-1を使うと、末尾の要素を取れますか?解答を見る
取れません。
nums[-1]と直接書くとコンパイルエラーに、変数に入っている-1だと実行時のクラッシュになります。末尾はnums[len(nums) - 1]で読みます。辞書から存在しないキーを
d["x"]で引くと、何が起きますか? 安全に引くにはどうしますか?解答を見る
DictKeyErrorという例外になり、try/exceptで受け止めなければプログラムは止まります。また、この呼び出しを含む関数にはraisesが必要です。安全に引くならd.get("x")を使うと、キーがないときNoneが返ります。Setを使うために必要なことは何ですか?解答を見る
from std.collections import Setの import です。ListやDictと違い、import しないと「Setが宣言されていない」というエラーになります。Mojo に
Optionalがあるのはなぜですか?解答を見る
Intのような普通の型にはNoneを入れられないためです。「値がないかもしれない」ことをOptional[Int]という型で明示することで、Optionalでない値は確認なしで安全に使えます。
練習問題 重複を取り除く
List[Int] の [3, 1, 3, 2, 1] から、重複を取り除いて昇順に並べた List[Int] を作り、表示してください。ヒント:いったん Set に入れてから、List に戻します。
解答例を見る
Set に add で入れ、取り出した要素を新しい List に append して、sort で並べ替えます。
from std.collections import Set
def main():
var data: List[Int] = [3, 1, 3, 2, 1]
var seen = Set[Int]()
for x in data:
seen.add(x)
var result = List[Int]()
for x in seen:
result.append(x)
sort(result)
print(result)実行すると [1, 2, 3] と表示されます。
練習問題 単語を数える
List[String] の ["apple", "fig", "apple", "pear", "fig", "apple"] について、各単語が何回出てくるかを Dict[String, Int] に数えて表示してください。
解答例を見る
get で現在の回数(なければ 0)を調べて 1 を足します。
def main() raises:
var words: List[String] = ["apple", "fig", "apple", "pear", "fig", "apple"]
var counts = Dict[String, Int]()
for w in words:
counts[w] = counts.get(w).or_else(0) + 1
for item in counts.items():
print(item.key, item.value)実行すると、apple 3、fig 2、pear 1 の3行が表示されます。
練習問題 最初の偶数を探す
List[Int] を受け取り、最初の偶数を Optional[Int] で返す関数 first_even を書いてください。偶数がなければ None を返し、呼び出し側で [1, 3, 4, 6] と [1, 3] の両方を試してください。
解答例を見る
x % 2 == 0 で偶数かどうかを調べ、見つかったらすぐ return します。
def first_even(xs: List[Int]) -> Optional[Int]:
for x in xs:
if x % 2 == 0:
return x
return None
def main():
var a: List[Int] = [1, 3, 4, 6]
var b: List[Int] = [1, 3]
print(first_even(a).or_else(-1))
print(first_even(b).or_else(-1))実行すると、4 と -1 が表示されます。