paizaでClojureの勉強 - 【マップの扱い 1】マップの書き換え・1 マス (paizaランク C 相当)
盤面を読み込むためのコード
(defn split-line-by-space [line]
(string/split line #" "))
; 1行読み込んで、スペースで区切られた整数値をリストにして返す
(defn read-int-values-line []
(map #(Integer/parseInt %) (split-line-by-space (read-line))))
; 1行の文字列を、#だったらtrue,そうじゃなければfalseのリストにして返す
(defn line-to-bord [line]
(map #(= % \#) (seq line)))
; 盤面をbooleanの二次元リストとして読み込む
(defn read-input-borad []
(let [[x] (read-int-values-line)]
(map #(line-to-bord %) (readlines x))))
上記のテストコード
(defn input-borad1 []
[[false, false, false]
[false, false, false]
[false, false, false]])
(defn input-borad2 []
[[true, true, true, true]
[true, true, true, true]
[false, false, false, false],
[true, true, false, false]])
(deftest read-input-borad-test
(testing "sample1"
(with-in-str "3 3\n...\n...\n...\n"
(is (= (read-input-borad) (input-borad1)))))
(testing "sample2"
(with-in-str "4 4\n####\n####\n....\n##..\n"
(is (= (read-input-borad) (input-borad2))))))
文字が一致するかどうかを判定する
=関数
2つの引数が等しい場合にtrueを返し、そうでない場合にfalseを返す。
(= \a \a)
; => true
(= \a \b)
; => false
文字列を文字のリストに変換する
seq関数
シーケンスを生成するために、文字列を1文字ずつ分解する。
(def my-string "hello")
; => "hello"
(seq my-string)
; => (\h \e \l \l \o)
map関数について
#は、Clojureにおいて、リテラル値を表現するために使用されるショートカット
#に続く文字や記号によって、異なる種類のリテラル値を表現することができる
例えば、#""は空の文字列を表し、#trueは真のブール値を表す。
また、#(foo %)は、引数を1つ取る無名関数を表す。
#を使用することで、より簡潔なコードを書くことができるが、#を使用する場合は、その表現が明確であることを確認すべき。
%シンボルは、無名関数に渡された引数の省略形。
listとvectorの使い分け
Clojureにおいて、リストとベクターはどちらもシーケンスとして使用される。
- リスト
- 要素の追加や削除が頻繁に行われる場合に適している
- 先頭に要素を追加するcons関数が高速
- 先頭に要素を追加する操作が多い場合に効率的
- 再帰的な処理に適しているため、再帰的なアルゴリズムを実装する場合にも使用
- ベクター
- 要素のアクセスが頻繁に行われる場合に適している
- インデックスを指定して要素にアクセスする操作が高速
- 要素のアクセスが多い場合に効率的
- リストよりもメモリ効率が高く、要素数が多い場合にも効率的
ベクターはタプル的な使い方もできる
(def my-tuple [1 "hello"])
(get my-tuple 0) ; Returns 1
(get my-tuple 1) ; Returns "hello"
(my-tuple 0) ; Returns 1
(my-tuple 1) ; Returns "hello"
(let [[x y] my-tuple]
(println x) ; Prints 1
(println y)) ; Prints "hello"
盤面の更新をするコード
(defn update-borad [borad x y]
(let [borad-x-y (get-in borad [x y])]
(assoc-in borad [x y] (not borad-x-y))))
テストコード
(deftest update-borad-test
(testing "sample1"
(let [borad (input-borad1)]
(is (= (update-borad borad 0 0)
[[true, false, false]
[false, false, false]
[false, false, false]]))))
(testing "sample2"
(let [borad (input-borad2)]
(is (= (update-borad borad 1 1)
[[true, true, true, true]
[true, false, true, true]
[false, false, false, false],
[true, true, false, false]])))))
2次元リスト、ベクターの値の変更方法
Clojureの2次元リストやベクターは不変なデータ構造。
一度作成された後に値を変更することはできないが、新しい2次元リストやベクターを作成し、元の2次元リストやベクターと異なる値を持たせることはできる。
2次元リストやベクターの値を変更するには、assoc-in関数を使用する。
指定された場所に新しい値を設定した新しい2次元リストやベクターを返す。
2つの引数を取り、最初の引数は変更する2次元リストやベクターであり、2番目の引数は変更する場所を示すキーのシーケンスと新しい値のペア。
(def my-list [[1 2] [3 4]])
; => [[1 2] [3 4]]
(def modified-list (assoc-in my-list [1 0] 5))
; => [[1 2] [5 4]]
2次元リストやベクターの値の取得方法
2次元リストやベクターの値を取得するには、get-in関数を使用する。
get-in関数は、ネストされたマップやベクターの値を取得するための関数。
(def my-vector [[1 2 3] [4 5 6] [7 8 9]])
(get-in my-vector [1 2])
; => 6
ボードの行を表示用の1行に変換するコード
(defn board-row-to-line [row]
(apply str (map #(if % \# \.) row)))
テストコード
(deftest board-row-to-line-test
(is (= (board-row-to-line [true, false, false]) "#..")))
リストやベクターの値を連結した文字列を作る
str関数は、引数として渡されたオブジェクトを文字列に変換する
str関数は、可変長引数を取る
(str "hello" "world")
; => "helloworld"
(str 1 2 3)
; => "123"
apply関数は、可変長引数を取る関数に、引数のリストを渡すために使用
apply関数は、第1引数に関数を、第2引数に引数のリストを取る
applyとstrを組み合わせることで、リストやベクターの値を連結した文字列を作ることができる
(defn my-function [& args]
(apply str args))
(my-function "hello" "world")
; => "helloworld"
clojure.string/join関数を使用して文字列を連結することも可能
(defn board-row-to-line [row]
(clojure.string/join "" (map #(if % \# \.) row)))
(ns clojure-practice.paiza.libs
(:require [clojure.string :as string]))
(defn readlines [line-lenght]
(take line-lenght (repeatedly read-line)))
(defn split-line-by-space [line]
(string/split line #" "))
(defn read-int-values-line []
(map #(Integer/parseInt %) (split-line-by-space (read-line))))
(defn line-to-board-row [line]
(map #(= % \#) (seq line)))
(defn read-input-borad [h]
(map #(line-to-board-row %) (readlines h)))
(defn update-borad [borad x y]
(let [borad-x-y (get-in borad [x y])]
(assoc-in borad [x y] (not borad-x-y))))
(defn board-row-to-line [row]
(apply str (map #(if % \# \.) row)))
(defn main []
(let [[h _] (read-int-values-line)
borad (read-input-borad h)
[x y] (read-int-values-line)
new-borad (update-borad borad x y)]
(doseq [line new-borad]
(println (board-row-to-line line)))))
(main)
以下のコードで提出前動作確認をしたら、こんなエラーになる
Syntax error (NumberFormatException) compiling at (Main.clj:34:1).
For input string: "..."
[x y] (read-int-values-line)
のところで"..."を読みこんでいて、落ちていそう
これのせいっぽい
以下、GitHub Copilotに聞いてみた結果
Clojureにおいて、map関数は、遅延評価されたシーケンスを返します。遅延評価されたシーケンスとは、要素が必要になるまで評価されないシーケンスのことです。具体的には、map関数が返すシーケンスは、元のシーケンスの各要素に対して、指定された関数を適用した結果を要素として持つシーケンスです。しかし、このシーケンスの要素は、実際にアクセスされるまで計算されず、必要になった時点で遅延評価されます。
遅延評価されたシーケンスは、メモリを節約するために使用されます。例えば、非常に大きなシーケンスを処理する場合には、遅延評価されたシーケンスを使用することで、必要な要素だけを計算して、メモリ使用量を最小限に抑えることができます。
ただし、遅延評価されたシーケンスは、要素が必要になるまで計算されないため、シーケンスの要素にアクセスするたびに計算が行われるため、パフォーマンスの低下が起こる場合があります。そのため、遅延評価されたシーケンスを使用する場合には、適切なタイミングでシーケンスを評価することが重要です。
こんな感じでmap
返してたところを全部vec
で囲ってあげたらクリアできた。
(ns clojure-practice.paiza.libs
(:require [clojure.string :as string]))
(defn readlines [line-lenght]
(take line-lenght (repeatedly read-line)))
(defn split-line-by-space [line]
(string/split line #" "))
(defn read-int-values-line []
(vec (map #(Integer/parseInt %) (split-line-by-space (read-line)))))
(defn line-to-board-row [line]
(vec (map #(= % \#) (seq line))))
(defn read-input-borad [h]
(vec (map #(line-to-board-row %) (readlines h))))
(defn update-borad [borad x y]
(let [borad-x-y (get-in borad [x y])]
(assoc-in borad [x y] (not borad-x-y))))
(defn board-row-to-line [row]
(apply str (vec (map #(if % \# \.) row))))
(defn main []
(let [[h _] (read-int-values-line)
borad (read-input-borad h)
[x y] (read-int-values-line)
new-borad (update-borad borad x y)]
(doseq [line new-borad]
(println (board-row-to-line line)))))
(main)
この問題やっているときに、update-borad
のバグを見つけた。
xが行、yが列だから、xとyを逆に扱っていた。。
正しくは以下。
(defn update-borad [borad x y]
(let [borad-x-y (get-in borad [y x])]
(assoc-in borad [y x] (not borad-x-y))))
これの提出コードは以下。ほぼコードは前回の問題の使い回しでできた。
(ns clojure-practice.paiza.libs
(:require [clojure.string :as string]))
(defn readlines [line-lenght]
(take line-lenght (repeatedly read-line)))
(defn split-line-by-space [line]
(string/split line #" "))
(defn read-int-values-line []
(vec (map #(Integer/parseInt %) (split-line-by-space (read-line)))))
(defn line-to-board-row [line]
(vec (map #(= % \#) (seq line))))
(defn read-input-board [h]
(vec (map #(line-to-board-row %) (readlines h))))
(defn should-update-board [board x y]
(and (>= x 0) (>= y 0) (< x (count (first board))) (< y (count board))))
(defn update-board [board x y]
(if (should-update-board board x y)
(let [board-x-y (get-in board [y x])]
(assoc-in board [y x] (not board-x-y)))
board))
(defn board-row-to-line [row]
(apply str (vec (map #(if % \# \.) row))))
(defn update-board-all-point [board x y]
(let [new-board (update-board board x y)
new-board (update-board new-board (dec x) y)
new-board (update-board new-board x (dec y))
new-board (update-board new-board (inc x) y)
new-board (update-board new-board x (inc y))]
new-board))
(defn main []
(let [[h] (read-int-values-line)
board (read-input-board h)
[y x] (read-int-values-line)
new-board (update-board-all-point board x y)]
(doseq [line new-board]
(println (board-row-to-line line)))))
(main)