Продолжение «Замена шаблона простой строки в Scala и Clojure» - PullRequest
2 голосов
/ 24 мая 2011

В моем предыдущем посте я показал простой (наивный) алгоритм для замены шаблона String.

Одно из решений, предоставленных mikera ,Похоже, гораздо лучший алгоритм.Я реализовал это в Clojure (следует) и сравнил его с моим предыдущим алгоритмом.Новый на 10096 * медленнее (41,475 мсек против 19,128 мсек).Я должен делать что-то глупое в моей новой реализации.

(defn replace-templates
  "Return a String with each occurrence of a substring of the form {key}
   replaced with the corresponding value from a map parameter.
   @param str the String in which to do the replacements
   @param m a map of keyword->value"
  [text m]
  (let [builder (StringBuilder.)
        text-length (.length text)]
    (loop [current-index 0]
      (if (>= current-index text-length)
        (.toString builder)
        (let [open-brace (.indexOf text "{" current-index)]
          (if (< open-brace 0)
            (.toString (.append builder (.substring text current-index)))
            (let [close-brace (.indexOf text "}" open-brace)]
              (if (< close-brace 0)
                (.toString (.append builder (.substring text current-index)))
                (do
                  (.append builder (.substring text current-index open-brace))
                  (.append builder (let [key (keyword (.substring text (inc open-brace) close-brace))
                                         replacement (m key)]
                                     (if (nil? replacement) "" replacement)))
                  (recur (inc close-brace)))))))))))

, хотя он проходит все тестовые случаи:

(use 'clojure.test)

(deftest test-replace-templates
  (is (= (replace-templates "this is a test" {:foo "FOO"})
        "this is a test"))
  (is (= (replace-templates "this is a {foo} test" {:foo "FOO"})
        "this is a FOO test"))
  (is (= (replace-templates "this is a {foo} test {bar}" {:foo "FOO" :bar "BAR"})
        "this is a FOO test BAR"))
  (is (= (replace-templates "this is a {foo} test {bar} 42" {:foo "FOO" :bar "BAR"})
        "this is a FOO test BAR 42"))
  (is (= (replace-templates "this is a {foo} test {bar" {:foo "FOO" :bar "BAR"})
        "this is a FOO test {bar")))

; user=> Ran 1 tests containing 5 assertions.
; user=> 0 failures, 0 errors.
; user=> {:type :summary, :test 1, :pass 5, :fail 0, :error 0}

Вот код теста:

(time (dotimes [n 100] (replace-templates
  "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque
elit nisi, egestas et tincidunt eget, {foo} mattis non erat. Aenean ut
elit in odio vehicula facilisis. Vestibulum quis elit vel nulla
interdum facilisis ut eu sapien. Nullam cursus fermentum
sollicitudin. Donec non congue augue. {bar} Vestibulum et magna quis
arcu ultricies consectetur auctor vitae urna. Fusce hendrerit
facilisis volutpat. Ut lectus augue, mattis {baz} venenatis {foo}
lobortis sed, varius eu massa. Ut sit amet nunc quis velit hendrerit
bibendum in eget nibh. Cras blandit nibh in odio suscipit eget aliquet
tortor placerat. In tempor ullamcorper mi. Quisque egestas, metus eu
venenatis pulvinar, sem urna blandit mi, in lobortis augue sem ut
dolor. Sed in {bar} neque sapien, vitae lacinia arcu. Phasellus mollis
blandit commodo." {:foo "HELLO" :bar "GOODBYE" :baz "FORTY-TWO"})))

; user=> "Elapsed time: 41.475 msecs"
; user=> nil

Интересно, проблема в непрерывном перераспределении StringBuilder.

Ответы [ 3 ]

4 голосов
/ 25 мая 2011

Я думаю, что вы поражены отражением. *warn-on-reflection* твой друг. Вот некоторые тесты с критерием .

replace-templates-original:         56.4us
replace-templates-original-hinted:   9.4us
replace-templates-new:             131.4us
replace-templates-new-hinted:        6.3us
replace-templates-very-new:          7.3us

Вот replace-templates-very-new, версия, которую я сделал для гольфа. :)

(defn replace-templates-very-new
  [^String text m]
  (let [builder (StringBuilder.)]
    (loop [text text]
      (cond
        (zero? (count text))
        (.toString builder)

        (.startsWith text "{")
        (let [brace (.indexOf text "}")]
          (if (neg? brace)
            (.toString (.append builder text))
            (do
              (.append builder (get m (keyword (subs text 1 brace))))
              (recur (subs text (inc brace))))))

        :else
        (let [brace (.indexOf text "{")]
          (if (neg? brace)
            (.toString (.append builder text))
            (do
              (.append builder (subs text 0 brace))
              (recur (subs text brace)))))))))

Он проходит все тесты, поэтому должен работать.

ОБНОВЛЕНИЕ : Поддержка значений без ключа, заключенных в фигурные скобки ("this is a {not-a-key-{foo}-in-the-map} test" => "this is a {not-a-key-FOO-in-the-map} test"), что позволяет использовать их в генераторе кода Java, где важны объекты, не заключенные в ключевые скобки: -).

(defn replace-templates-even-newer
  "Return a String with each occurrence of a substring of the form {key}
   replaced with the corresponding value from a map parameter.
   @param str the String in which to do the replacements
   @param m a map of keyword->value
   @thanks kotarak /4623355/prodolzhenie-zamena-shablona-prostoi-stroki-v-scala-i-clojure
     follow-up-to-simple-string-template-replacement-in-scala-and-clojure"
  [^String text m]
  (let [builder (StringBuilder.)]
    (loop [text text]
      (cond
        (zero? (count text))
        (.toString builder)

        (.startsWith text "{")
        (let [brace (.indexOf text "}")]
          (if (neg? brace)
            (.toString (.append builder text))
            (if-let [[_ replacement] (find m (keyword (subs text 1 brace)))]
              (do
                (.append builder replacement)
                (recur (subs text (inc brace))))
              (do
                (.append builder "{")
                (recur (subs text 1))))))

        :else
        (let [brace (.indexOf text "{")]
          (if (neg? brace)
            (.toString (.append builder text))
            (do
              (.append builder (subs text 0 brace))
              (recur (subs text brace)))))))))
2 голосов
/ 23 сентября 2012

Я написал некоторый код Clojure (https://gist.github.com/3729307), который позволяет интерполировать любое значение карты в шаблон, возможно, самым быстрым способом (см. Ниже) ЕСЛИ шаблон известенво время компиляции.

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

С этим решением,код должен быть переписан как ...

; renderer-fn is defined in https://gist.github.com/3729307
(time (dotimes [n 100] ((renderer-fn
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque
elit nisi, egestas et tincidunt eget, " (:foo %) " mattis non erat. Aenean ut
elit in odio vehicula facilisis. Vestibulum quis elit vel nulla
interdum facilisis ut eu sapien. Nullam cursus fermentum
sollicitudin. Donec non congue augue. " (:bar %) " Vestibulum et magna quis
arcu ultricies consectetur auctor vitae urna. Fusce hendrerit
facilisis volutpat. Ut lectus augue, mattis " (:baz %) " venenatis " (:foo %)
"lobortis sed, varius eu massa. Ut sit amet nunc quis velit hendrerit
bibendum in eget nibh. Cras blandit nibh in odio suscipit eget aliquet
tortor placerat. In tempor ullamcorper mi. Quisque egestas, metus eu
venenatis pulvinar, sem urna blandit mi, in lobortis augue sem ut
dolor. Sed in " (:bar %) " neque sapien, vitae lacinia arcu. Phasellus mollis
blandit commodo.") {:foo "HELLO" :bar "GOODBYE" :baz "FORTY-TWO"})))

; => "Elapsed time: 1.371 msecs"
0 голосов
/ 02 мая 2019

Если честно, ваше решение больше похоже на Java в одежде Clojure. Clojure уже обладает довольно гибкой функцией clojure.string/replace, которая способна сделать то, что вам нужно. Кроме того, ваша строка документации не соответствует соглашениям Clojure. Я бы предложил что-то вроде этого:

(defn replace-templates
  "Returns a string with each occurrence of the form `{key}` in a
  `template` replaced with the corresponding value from a map
  `m`. Keys of `m` are expected to be keywords."
  [template m]
  (clojure.string/replace template #"\{([^{]+?)\}"
    (fn [[orig key]] (or (get m (keyword key)) orig))))

Как можно себе представить, replace уже довольно оптимизирован, поэтому нет никакой реальной причины для развертывания собственной реализации. Он использует StringBuffer для внутреннего использования, в то время как вы используете StringBuilder, поэтому ваша реализация может сэкономить несколько микросекунд - об этом не стоит говорить.

Если вы действительно заботитесь о каждой микросекунде, вы, вероятно, должны рассмотреть макроподход. Если это невозможно, например, потому что вы загружаете шаблон из файла, тогда ввод / вывод в любом случае будет более серьезным. Также в этом случае я бы предложил рассмотреть систему шаблонов Selmer , которая имеет немного другой синтаксис (с двойными вместо одиночных фигурных скобок для замен), но также гораздо более гибкая в том, что она может делать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...