下面是用Scala和Clojure編寫的用於在Strings中簡單替換模板的函數。每個函數的輸入是String
,其中包含{key}
形式的模板以及從符號/關鍵字到替換值的映射。Scala和Clojure中的簡單字符串模板替換
例如:
斯卡拉:
replaceTemplates("This is a {test}", Map('test -> "game"))
的Clojure:
(replace-templates "This is a {test}" {:test "game"})
將返回"This is a game"
。
輸入地圖使用符號/關鍵字,因此我不必處理字符串中的模板包含大括號的情況。
不幸的是,算法效率不高。
這裏是Scala代碼:
def replaceTemplates(text: String,
templates: Map[Symbol, String]): String = {
val builder = new StringBuilder(text)
@tailrec
def loop(key: String,
keyLength: Int,
value: String): StringBuilder = {
val index = builder.lastIndexOf(key)
if (index < 0) builder
else {
builder.replace(index, index + keyLength, value)
loop(key, keyLength, value)
}
}
templates.foreach {
case (key, value) =>
val template = "{" + key.name + "}"
loop(template, template.length, value)
}
builder.toString
}
和這裏是Clojure的代碼:
(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 [sb (StringBuilder. text)]
(letfn [(replace-all [key key-length value]
(let [index (.lastIndexOf sb key)]
(if (< index 0)
sb
(do
(.replace sb index (+ index key-length) value)
(recur key key-length value)))))]
(doseq [[key value] m]
(let [template (str "{" (name key) "}")]
(replace-all template (count template) value))))
(.toString sb)))
這裏是一個測試用例(Scala代碼):
replaceTemplates("""
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.
""", Map('foo -> "HELLO", 'bar -> "GOODBYE", 'baz -> "FORTY-TWO"))
和輸出:
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque
elit nisi, egestas et tincidunt eget, HELLO 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. GOODBYE Vestibulum et magna quis
arcu ultricies consectetur auctor vitae urna. Fusce hendrerit
facilisis volutpat. Ut lectus augue, mattis FORTY-TWO venenatis HELLO
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 GOODBYE neque sapien, vitae lacinia arcu. Phasellus mollis
blandit commodo.
該算法遍歷輸入映射,並且對於每個對,在輸入String
中進行替換,臨時保存在StringBuilder
中。對於每個鍵/值對,我們搜索鍵的最後一次出現(用大括號括起來),並將其替換爲該值,直到不再出現爲止。
如果我們在StringBuilder中使用.lastIndexOf
與.indexOf
是否會產生性能差異?
算法如何改進?有沒有一種更習慣的方式來編寫Scala和/或Clojure代碼?
UPDATE:請參閱我的follow-up。
更新2:這是一個更好的Scala實現; O(n)中的字符串的長度。請注意,我根據幾個人的建議將Map
修改爲[String, String]
而不是[Symbol, String]
。(感謝mikera,kotarak):
/**
* Replace templates of the form {key} in the input String with values from the Map.
*
* @param text the String in which to do the replacements
* @param templates a Map from Symbol (key) to value
* @returns the String with all occurrences of the templates replaced by their values
*/
def replaceTemplates(text: String,
templates: Map[String, String]): String = {
val builder = new StringBuilder
val textLength = text.length
@tailrec
def loop(text: String): String = {
if (text.length == 0) builder.toString
else if (text.startsWith("{")) {
val brace = text.indexOf("}")
if (brace < 0) builder.append(text).toString
else {
val replacement = templates.get(text.substring(1, brace)).orNull
if (replacement != null) {
builder.append(replacement)
loop(text.substring(brace + 1))
} else {
builder.append("{")
loop(text.substring(1))
}
}
} else {
val brace = text.indexOf("{")
if (brace < 0) builder.append(text).toString
else {
builder.append(text.substring(0, brace))
loop(text.substring(brace))
}
}
}
loop(text)
}
更新3:這裏有一組Clojure的測試用例(Scala的版本作爲一個練習:-)):
(use 'clojure.test)
(deftest test-replace-templates
(is (= ; No templates
(replace-templates "this is a test" {:foo "FOO"})
"this is a test"))
(is (= ; One simple template
(replace-templates "this is a {foo} test" {:foo "FOO"})
"this is a FOO test"))
(is (= ; Two templates, second at end of input string
(replace-templates "this is a {foo} test {bar}" {:foo "FOO" :bar "BAR"})
"this is a FOO test BAR"))
(is (= ; Two templates
(replace-templates "this is a {foo} test {bar} 42" {:foo "FOO" :bar "BAR"})
"this is a FOO test BAR 42"))
(is (= ; Second brace-enclosed item is NOT a template
(replace-templates "this is a {foo} test {baz} 42" {:foo "FOO" :bar "BAR"})
"this is a FOO test {baz} 42"))
(is (= ; Second item is not a template (no closing brace)
(replace-templates "this is a {foo} test {bar" {:foo "FOO" :bar "BAR"})
"this is a FOO test {bar"))
(is (= ; First item is enclosed in a non-template brace-pair
(replace-templates "this is {a {foo} test} {bar" {:foo "FOO" :bar "BAR"})
"this is {a FOO test} {bar")))
(run-tests)
的關鍵是在編譯時已知?如果是這樣,這太複雜了 – 2011-05-24 12:26:27
@Kim Stebel:這是怎麼回事?我如何改進它? – Ralph 2011-05-24 12:32:33
@ralph從clojure.contrib.strint看['<<'](http://clojure.github.com/clojure-contrib/strint-api.html#clojure.contrib.strint/%3C%3C) '。我認爲它也轉移到了新的貢獻。不過,這只是編譯時間。 – kotarak 2011-05-24 12:41:40