0

I have cards in a database with some paragraph(s) worth of text for each card. I want to replace "Specific Phrase(s)" with HTML symbols. So that when they are displayed, the phrase(s) are symbol(s) instead of text.

My code works pretty flawlessly, except for in the way I am calling the final function.

(I am going to try and not get too involved with information overload but) I am calling the final function like so:

...{:__html                          (add-symbols
                                       (add-symbols
                                         (add-symbols
                                           (add-symbols
                                             (add-symbols
                                               (add-symbols
                                                 (add-symbols
                                                   (add-symbols
                                                     (add-symbols
                                                       (add-symbols
                                                         (add-symbols
                                                           (add-symbols
                                                             (:text card)))))))))))))}

The reason I am calling this function like this, is because the add-symbols function takes the "card-text" and finds the first occurrence and then returns the "card-text" or (:text card) updated in which then it's called again.

Since the first occurrence is now gone and replaced with an html encoded symbol, I can scan again and replace, etc, this number of times will cover the amount of text and frequency of symbols.

But lately, I have been brushing up on Clojure with Clojure for the Brave, and I came across and payed attention to the fn (REDUCE ...)

I knew this wouldn't be necessarily easy. But I was thinking something along the lines of just:

(reduce add-symbols (:text card))

Like I said, I knew it wouldn't be that easy, ;) but, should I just loop? or what?

My previous code works but it's so pathetic, and fully coursing thru ALL and ANY text and replacing ALL phrases no matter what the length of the paragraphs, using some keen reduce, would be ideal!

Thanks for any help!

rezwits
  • 675
  • 6
  • 11

3 Answers3

2

You can use reduce if you combine it with iterate to produce a sequence of new text values, along with a function that stops the reduction when no change is seen. This calculates a fixed point of add-symbols, starting with the value (:text card):

(reduce #(if (= %1 %2) 
          (reduced %1) 
          %2) 
  (iterate add-symbols (:text card)))

As a bonus, with ClojureScript 1.10.238 iterate produces a directly reducible result. No intermediate sequence is produced (much like the low-level loop / recur approach).

Mike Fikes
  • 3,367
  • 11
  • 24
  • 1
    Oh cool, I didn't quite catch this: "No intermediate sequence is produced" at first, I was saying to myself this would be more work because it would do that to make reduce work, then I came back and read that last line! REDUCE for the win! – rezwits May 16 '18 at 20:03
1

As a broad outline, you probably want something like:

{ :__html
  (loop [curr-card (:text card)]
    (if (is-finished curr-card)
      curr-card
      (recur (add-symbols curr-card)))) }

But the is-finished function and add-symbols will depend on your details (please update the question).

Alan Thompson
  • 25,038
  • 5
  • 36
  • 43
  • I will add the code defs for the other functions, it's couple pages, but I'll cut some down and you'll get the gist. Let me get a beer! – rezwits May 12 '18 at 03:47
  • You know what? I am gonna +1 your answer, because after I looked at it, I was like it's exactly the SAME as mine, just slightly different operations. So with no haste this morning I put it in and viola, worked like a charm!! – rezwits May 12 '18 at 18:33
  • Originally when I was trying to use loop, I was trying to use two bindings: old-text & new-text. And I was getting confused, that's when I realized go back and try loop because I am only using the one binding now. Thanks again! – rezwits May 12 '18 at 18:48
0

OK well, I was going with loop, then I read your post, and thought I might be on the right track, but ugh (this language is TOUGH, but when you succeed you get that good ole rewarding feeling, like the "old days.")

I was trying with let and loop even, because I realized all I was doing was "pseudo-recusively" calling my function manually, the 13 times, and saying, that'll be enough. After learning so much, in the end, I decided (with a some help from Redefining a let'd variable in Clojure loop ) to use when because of the "is-finished" clause this would be best.

This code completes the job:

...{:__html
     (let [new-text (atom (add-symbols (:text card)))]
       (when (= @new-text (add-symbols @new-text))
         @new-text
         (swap! new-text #(add-symbols @new-text))))}...

using loop

...{:__html
     (loop [new-text (:text card)]
       (if (= new-text (add-symbols new-text))
         new-text
         (recur (add-symbols new-text))))}...

They (both) work clean and mean. The reason I didn't want to go with "more information" is because I read this one line from "Clojure for the Brave":

"Clojure’s structure is very simple and consistent by comparison. No matter which operator you’re using or what kind of data you’re operating on, the structure is the same."

Which to me meant, "Look no matter what you are doing, flipping pancakes or changing tires, if you have an operator and data, at some "core" the manipulation will be the same, and I like Clojure for this! I know I didn't stick with pure immutable objects, but I figure an atom has to get used eventually...

oh and basically the "is-finished" was checking, run the FN, did the text change?

Thanks!!

other source code:

(defn make-span [text symbol path]
  (.replace text (apply str symbol) (str "<img src='" path "'style=\"width:16px;height:16px;\"></img>")))

(defn add-symbols [card-text]
  (-> (if (nil? card-text) "" card-text)
  (make-span "[b]" "img/dc/me_bl.png")
  (make-span "[c]" "img/dc/me_cs.png")
  (make-span "[d]" "img/dc/me_dd.png")
  (make-span "[f]" "img/dc/me_fd.png")
  (make-span "[j]" "img/dc/me_ju.png")
  (make-span "[s]" "img/dc/me_sl.png")
  (make-span "[w]" "img/dc/me_wi.png")))

(defn- card-text
  "Generate text html representation a card"
  [card cursor]
  [:div
   [:h4 (:title card)]
   [:div.text
    [:p [:span.type (str (:type card))] (if (= (.toLowerCase (:type card)) (:Secondary card))
                                          ""
                                          (str ": " (:Secondary card)))]
    [:pre {:dangerouslySetInnerHTML #js {:__html
                                         (let [new-text (atom (add-symbols (:text card)))]
                                           (when (= @new-text (add-symbols @new-text))
                                             @new-text
                                             (swap! new-text #(add-symbols @new-text))))}}]]])

There is more code but it's just not really what the purpose or application of this question was. I was really excited to get to the EXACT POINT of the question or the "MEAT of the MATTER", thanks again!!

rezwits
  • 675
  • 6
  • 11