Closed16

paizaでClojureの勉強 - 【マップの扱い 1】マップの書き換え・1 マス (paizaランク C 相当)

むらむーむらむー

https://paiza.jp/works/mondai/b_rank_new_level_up_problems/b_rank_new_level_up_problems__get_one

盤面を読み込むためのコード

(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)

むらむーむらむー

https://paiza.jp/works/mondai/b_rank_new_level_up_problems/b_rank_new_level_up_problems__get_upsidedown

これの提出コードは以下。ほぼコードは前回の問題の使い回しでできた。

https://github.com/eno314/clojure-practice/pull/4

(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)
このスクラップは2023/06/30にクローズされました