Нумерация строк внутри вектора в clojure

Учитывая следующую строку:

(def text "this is the first sentence . And this is the second sentence")

Я хотел посчитать количество раз, когда в тексте появляется слово « this », добавив счетчик после каждого появления этого слова. Как это:

["this: 1", "is" "the" "first" "sentence" "." "and" "this: 2" ...]

В качестве первого шага я обозначил строку:

 (def words (split text #" "))

Затем я создал вспомогательную функцию, чтобы получить количество раз « this » в тексте:

 (defn count-this [x] (count(re-seq #"this" text)))

Наконец, я попытался использовать результат функции count-this внутри этого цикла:

(for [x words]
(if (= x "this")
(str "this: "(apply str (take (count-this)(iterate inc 0))))
x))

Вот что я получаю:

("this: 01" "is" "the" "first" "sentence" "." "And" "this: 01" "is" ...)
1
поскольку в ответе не используется clojure.core/frequency - здесь doc
добавлено автор birdspider, источник

5 ответы

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

(def text "this is the first sentence. And this is the second sentence.")

(defn notate-occurences [word string]
  (->
    (reduce 
        (fn [[count string'] member] 
            (if (= member word) 
              (let [count' (inc count)]
                [count' (conj string' (str member ": " count'))])
              [count (conj string' member)]))
          [0 []]
          (clojure.string/split string #" "))
    second))

(notate-occurences "this" text) 
;; ["this: 1" "is" "the" "first" "sentence." "And" "this: 2" "is" "the" "second""sentence."]
1
добавлено

Вы должны сохранить какое-то состояние, когда идете вперед. reduce , loop / recur и итерация все это делают. iterate просто переходит из одного состояния в другое. Вот функция перехода:

(defn transition [word]
  (fn [[[head & tail] counted out]]
    (let [[next-counted to-append] (if (= word head)
                                    [(inc counted) (str head ": " (inc counted))]
                                    [counted head])]
      [tail next-counted (conj out to-append)])))

Затем вы можете использовать iterate , чтобы использовать эту функцию до тех пор, пока не останется входной сигнал:

(let [in (s/split "this is the first sentence . And this is the second sentence" #" ")
      step (transition "this")]
    (->> (iterate step [in 0 []])
         (drop-while (fn [[[head & _] _ _]]
                       head))
         (map #(nth % 2))
         first))

;; => ["this: 1" "is" "the" "first" "sentence" "." "And" "this: 2" "is" "the" "second" "sentence"]
1
добавлено
(defn split-by-word [word text]
    (remove empty?
        (flatten
            (map #(if (number? %) (str word ": " (+ 1 %)) (clojure.string/split (clojure.string/trim %) #" "))
                 (butlast (interleave
                      (clojure.string/split (str text " ") (java.util.regex.Pattern/compile (str "\\b" word "\\b")))
                      (range)))))))
1
добавлено

Проблема с этим подходом заключается в том, что (применить str (take (count-this) (iterate inc 0))) будет оценивать одно и то же каждый раз.

Чтобы полностью контролировать переменные, вы, как правило, хотите использовать форму цикла.

например

(defn add-indexes [word phrase]
  (let [words (str/split phrase #"\s+")]
    (loop [src words
           dest []
           counter 1]
      (if (seq src)
        (if (= word (first src))
          (recur (rest src) (conj dest (str word " " counter)) (inc counter))
          (recur (rest src) (conj dest (first src)) counter))
        dest))))

user=> (add-indexes "this" "this is the first sentence . And this is the second sentence")
["this 1" "is" "the" "first" "sentence" "." "And" "this 2" "is" "the" "second" "sentence"]

loop allows you to specify the value of every of the loop variables on each pass. So you can decide to change them or not according to your own logic.

Если вы готовы погрузиться в Java и, возможно, сделать что-то, что может показаться обманом, это тоже сработает.

(defn add-indexes2 [word phrase]
  (let [count (java.util.concurrent.atomic.AtomicInteger. 1)]
    (map #(if (= word %) (str % " " (.getAndIncrement count)) %)
         (str/split phrase #"\s+"))))

user=> (add-indexes2 "this" "this is the first sentence . And this is the second sentence")
("this 1" "is" "the" "first" "sentence" "." "And" "this 2" "is" "the" "second" "sentence")

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

0
добавлено
Как правило, я думаю, что цикл является хорошим выбором для чего-то вроде этого, потому что есть явная поддержка управления значениями над проходами итерации, и это немного более прямолинейно, чем попытка поместить его в сокращение.
добавлено автор Bill, источник
Нет необходимости в Java interop здесь, вот что Clojure использует atom s для.
добавлено автор Svante, источник
@Bill Спасибо за ваш ответ. Я выбрал ваш ответ, потому что именно здесь я сейчас изучаю Clojure (циклы, recur).
добавлено автор omar, источник

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

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

(require '(clojure.string))

(def text "this is the first sentence . And this is the second sentence")

(defn replace-token [ca token]
  (swap! ca inc)
  (str token ": " @ca))

(defn count-this [text]
  (let [counter     (atom 0)
        replacer-fn (partial replace-token counter)]
    (clojure.string/replace text #"this" replacer-fn)))

(count-this text)
; => "this: 1 is the first sentence . And this: 2 is the second sentence"

В приведенном выше решении используется тот факт, что функция может быть передана в clojure.string/replace .

Во-вторых, если вам нужен результат как последовательность, есть некоторые накладные расходы от токенизации:

(defn count-seq [text]
  (let [counter      (atom 0)
        replacer-fn  (partial replace-token counter)
        converter    (fn [tokens] (map #(if (not= % "this")
                                            % 
                                            (replacer-fn %))
                                       tokens))]
    (-> text
        (clojure.string/split #" ")
        (converter))))

(count-seq text)

; => ("this: 1" "is" "the" "first" "sentence" "." "And" "this: 2" "is" "the" "second" "sentence")

Шаблон loop-recur очень распространен для начинающих клуурцев, которые происходят из нефункциональных языков. В большинстве случаев существует более чистое и более идиоматическое решение, использующее функциональную обработку с map , reduce и друзьями.

Как и другие ответы, основной проблемой в вашей первоначальной попытке является привязка вашего счетчика. На самом деле (iterate inc 0) не привязан ни к чему. Посмотрите на мои примеры выше, чтобы продумать объем связанного атома counter . В качестве ссылки здесь приведен пример использования закрытий , который также может быть использован в этом случае с большим успехом!

В качестве примечания для приведенных выше примеров: для более чистого кода вы должны сделать более общее решение, извлекая и повторно используя общие части функций count-seq и count-this . Кроме того, локальная функция converter может быть извлечена из count-seq . replace-token уже является общим для всех токенов, но рассмотрим, как можно было бы расширить все решение за пределами соответствующего текста, кроме «этого». Они оставлены в качестве упражнений для читателя.

0
добавлено
pro.jvm
pro.jvm
3 503 участник(ов)

Сообщество разработчиков Java Scala Kotlin Groovy Clojure Чат для нач-их: @javastart Наш сайт: projvm.com projvm.ru Наш канал: @proJVM Вакансии: @jvmjobs Конфы: @jvmconf

Clojure — русскоговорящее сообщество
Clojure — русскоговорящее сообщество
433 участник(ов)

Общаемся на темы, посвященный Clojure. Решаем проблемы, обмениваемся опытом и делимся новостями. Вакансии и поиск работы: @clojure_jobs Вам могут быть интересны: @javascript_ru, @nodejs_ru, @ruby_ru, @devops_ru, @devops_jobs