Разделение строк в clojure при чтении из файла

Я изучаю clojure в школе, и у меня есть экзамен. Я просто работал над несколькими вещами, чтобы убедиться, что я понял это.

Я пытаюсь читать из файла строку за строкой, и, как и я, я хочу разделить строку всякий раз, когда есть ";".

Вот мой код до сих пор

(defn readFile []
  (map (fn [line] (clojure.string/split line #";"))
  (with-open [rdr (reader "C:/Users/Rohil/Documents/work.txt.txt")]
    (doseq [line (line-seq rdr)]
      (clojure.string/split line #";")
        (println line)))))

Когда я делаю это, я все еще получаю вывод:

"I;Am;A;String;"

Я что-то пропустил?

3 ответа

Я не уверен, нужно ли вам это в школе, но так как Гэри уже дал превосходный ответ, считайте это бонусом.

Вы можете выполнять элегантные преобразования в строках текста с помощью преобразователей. Ингредиент, который вам нужен, - это то, что позволяет вам рассматривать строки как сокращаемую коллекцию и закрывает читателя, когда вы закончите сокращать:

(defn lines-reducible [^BufferedReader rdr]
  (reify clojure.lang.IReduceInit
    (reduce [this f init]
      (try
        (loop [state init]
          (if (reduced? state)
            @state
            (if-let [line (.readLine rdr)]
              (recur (f state line))
              state)))
        (finally
          (.close rdr))))))

Теперь вы можете сделать следующее, учитывая ввод work.txt:

I;am;a;string
Next;line;please

Подсчитайте длину каждого "разделения"

(require '[clojure.string :as str])
(require '[clojure.java.io :as io])

(into []
      (comp
       (mapcat #(str/split % #";"))
       (map count))
      (lines-reducible (io/reader "/tmp/work.txt")))
;;=> [1 2 1 6 4 4 6]

Суммируйте длину всех "разбиений"

(transduce
 (comp
  (mapcat #(str/split % #";"))
  (map count))
 +
 (lines-reducible (io/reader "/tmp/work.txt")))
;;=> 24

Суммируйте длину всех слов, пока мы не найдем слово длиннее 5

(transduce
 (comp
  (mapcat #(str/split % #";"))
  (map count))
 (fn
   ([] 0)
   ([sum] sum)
   ([sum l]
    (if (> l 5)
      (reduced sum)
      (+ sum l))))
 (lines-reducible (io/reader "/tmp/work.txt")))

или с take-while:

(transduce
 (comp
  (mapcat #(str/split % #";"))
  (map count)
  (take-while #(> 5 %)))
 +
 (lines-reducible (io/reader "/tmp/work.txt")))

Прочитайте https://tech.grammarly.com/blog/building-etl-pipelines-with-clojure для получения более подробной информации.

TL;DR охватывают REPL и охватывают неизменность

Ваш вопрос был "что я пропускаю?" и на это я бы сказал, что вам не хватает одной из лучших функций Clojure - REPL.

Изменить: вы также можете упустить, что Clojure использует неизменяемые структуры данных, так

рассмотрите этот фрагмент кода:

(doseq [x [1 2 3]]
   (inc x)
   (prn x))

Этот код не печатает "2 3 4"

он печатает "1 2 3", потому что x не является изменяемой переменной.

Во время первой итерации (inc x) вызывается, возвращает 2, и это выбрасывается, потому что оно не было передано ни в чем, затем (prn x) печатает значение x, которое по-прежнему равно 1.

Теперь рассмотрим этот фрагмент кода:

(doseq [x [1 2 3]] (prn (inc x)))

Во время первой итерации inc передает свое возвращаемое значение в prn, так что вы получаете 2

Длинный пример:

Я не хочу лишать вас возможности решить проблему самостоятельно, поэтому я буду использовать другую проблему в качестве примера.

Учитывая файл "birds.txt" с данными "1chicken\n 2duck\n 3Larry"Вы хотите написать функцию, которая принимает файл и возвращает последовательность имен птиц

Давайте разберем эту проблему на более мелкие куски:

сначала давайте прочитаем файл и разделим его на строки

(slurp "birds.txt") даст нам весь файл строку

clojure.string/split-lines даст нам коллекцию с каждой строкой в ​​качестве элемента в коллекции

(clojure.string/split-lines (slurp "birds.txt")) получает нас ["1chicken" "2duck" "3Larry"]

В этот момент мы можем отобразить какую-то функцию над этой коллекцией, чтобы вырезать число как (map #(clojure.string/replace % #"\d" "") birds-collection)

или мы можем просто переместить этот шаг вверх по конвейеру, когда весь файл представляет собой одну строку.

Теперь, когда у нас есть все наши части, мы можем собрать их вместе в функциональный конвейер, где результат одного фрагмента передается в следующий.

В Clojure есть хороший макрос, чтобы сделать его более читабельным, -> макрос

Он берет результат одного вычисления и вводит его в качестве первого аргумента для следующего

так что наш конвейер выглядит так:

(-> "C:/birds.txt"
     slurp
     (clojure.string/replace #"\d" "") 
     clojure.string/split-lines)

Последнее замечание о стиле, для функций Clojure вы хотите придерживаться кебаб случае так readFile должно быть read-file

Я хотел бы сохранить это простым и кодировать это так:

(ns tst.demo.core
  (:use tupelo.test)
  (:require [tupelo.core :as t]
            [clojure.string :as str] ))
(def text
 "I;am;a;line;
  This;is;another;one
  Followed;by;this;")

(def tmp-file-name "/tmp/lines.txt")

(dotest
  (spit tmp-file-name text) ; write it to a tmp file
  (let [lines       (str/split-lines (slurp tmp-file-name))
        result      (for [line lines]
                      (for [word (str/split line #";")]
                        (str/trim word)))
        result-flat (flatten result)]
(is= result
  [["I" "am" "a" "line"]
   ["This" "is" "another" "one"]
   ["Followed" "by" "this"]])

Заметить, что result является двухслойной (2D) матрицей слов. Самый простой способ отменить это flatten функция для производства result-flat:

(is= result-flat
  ["I" "am" "a" "line" "This" "is" "another" "one" "Followed" "by" "this"])))

Вы также можете использовать apply concat как в:

(is= (apply concat result) result-flat)

Если вы хотите избежать создания 2D-матрицы, вы можете использовать generator function (а-ля Питон) через lazy-gen а также yield из библиотеки Тупело:

(dotest
  (spit tmp-file-name text) ; write it to a tmp file
  (let [lines  (str/split-lines (slurp tmp-file-name))
        result (t/lazy-gen
                 (doseq [line lines]
                   (let [words (str/split line #";")]
                     (doseq [word words]
                       (t/yield (str/trim word))))))]

(is= result
  ["I" "am" "a" "line" "This" "is" "another" "one" "Followed" "by" "this"])))

В этом случае, lazy-gen создает функцию генератора. Заметить, что for был заменен на doseqи yield Функция помещает каждое слово в ленивую последовательность вывода.

Другие вопросы по тегам