Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating through a map with doseq

Tags:

clojure

I'm new to Clojure and I'm doing some basic stuff from labrepl, now I want to write a function that will replace certain letters with other letters, for example: elosska → elößkä.

I wrote this:

(ns student.dialect (:require [clojure.string :as str]))
(defn germanize
  [sentence]
  (def german-letters {"a" "ä" "u" "ü" "o" "ö" "ss" "ß"})
  (doseq [[original-letter new-letter] german-letters]
    (str/replace sentence original-letter new-letter)))

but it doesn't work as I expect. Could you help me, please?

like image 249
Rafał Cieślak Avatar asked Jul 31 '11 15:07

Rafał Cieślak


3 Answers

Here is my take,


(def german-letters {"a" "ä" "u" "ü" "o" "ö" "ss" "ß"})

(defn germanize [s]
  (reduce (fn[sentence [match replacement]]
            (str/replace sentence match replacement)) s german-letters))


(germanize "elosska")
like image 193
Hamza Yerlikaya Avatar answered Sep 28 '22 06:09

Hamza Yerlikaya


There are 2 problems here:

  1. doseq doesn't preserve head of list that created by its evaluation, so you won't get any results
  2. str/replace works on separate copies of text, producing 4 different results - you can check this by replacing doseq with for and you'll get list with 4 entries.

You code could be rewritten following way:

(def german-letters {"a" "ä" "u" "ü" "o" "ö" "ss" "ß"})
(defn germanize [sentence]
  (loop [text sentence
         letters german-letters]
    (if (empty? letters)
      text
      (let [[original-letter new-letter] (first letters)]
        (recur (str/replace text original-letter new-letter)
               (rest letters))))))

In this case, intermediate results are collected, so all replacements are applied to same string, producing correct string:

user> (germanize "elosska")
"elößkä"

P.S. it's also not recommended to use def in the function - it's better to use it for top-level forms

like image 41
Alex Ott Avatar answered Sep 28 '22 07:09

Alex Ott


Alex has of course already correctly answered the question with respect to the original issue using doseq... but I found the question interesting and wanted to see what a more "functional" solution would look like. And by that I mean without using a loop.

I came up with this:

(ns student.dialect (:require [clojure.string :as str]))

(defn germanize [sentence]
  (let [letters {"a" "ä" "u" "ü" "o" "ö" "ss" "ß"}
        regex (re-pattern (apply str (interpose \| (keys letters))))]
    (str/replace sentence regex letters)))

Which yields the same result:

student.dialect=> (germanize "elosska")
"elößkä"

The regex (re-pattern... line simply evaluates to #"ss|a|o|u", which would have been cleaner, and simpler to read, if entered as an explicit string, but I thought it best to have only one definition of the german letters.

like image 23
Scott Avatar answered Sep 28 '22 08:09

Scott