0
(defn shuffle-letters 
  [word]
  (let [letters (clojure.string/split word #"")
        shuffled-letters (shuffle letters)]
  (clojure.string/join "" shuffled-letters)))

But if you put in "test" you can get "test" back sometimes.

How to modify the code to be sure that output will never be equal to input.

I feel embarrassing, I can solve it easily in Python, but Clojure is so different to me...

Thank you.

P.S. I thing we can close the topic now... The loop is in fact all I needed...

ivitek
  • 11
  • 2

3 Answers3

0

You can use loop. When the shuffled letters are the same as the original, recur back up to the start of the loop:

(defn shuffle-letters [word]
  (let [letters (clojure.string/split word #"")]
    (loop []  ; Start a loop
      (let [shuffled-letters (shuffle letters)]
        (if (= shuffled-letters letters)  ; Check if they're equal
          (recur)  ; If they're equal, loop and try again
          (clojure.string/join "" shuffled-letters))))))  ; Else, return the joined letters

There's many ways this could be written, but this is I think as plain as it gets. You could also get rid of the loop and make shuffle-letters itself recursive. This would lead to unnecessary work though. You could also use let-fn to create a local recursive function, but at that point, loop would likely be cleaner.


Things to note though:

  • Obviously, if you try to shuffle something like "H" or "HH", it will get stuck and loop forever since no amount of shuffling will cause them to differ. You could do a check ahead of time, or add a parameter to loop that limits how many times it tries.

  • This will actually make your shuffle less random. If you disallow it from returning the original string, you're reducing the amount of possible outputs.

  • The call to split is unnecessary. You can just call vec on the string:

    (defn shuffle-letters [word]
      (let [letters (vec word)]
        (loop []
          (let [shuffled-letters (shuffle letters)]
            (if (= shuffled-letters letters)
              (recur)
              (clojure.string/join "" shuffled-letters))))))
    
Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • The loop, so easy... It's in fact all I needed. Thank you. `(if (= (count (dedupe word)) 1)...` should help solve the "HH" problem – ivitek Nov 10 '19 at 04:50
  • @ivitek `loop` often isn't the *best* solution (functions like `reduce` are often better), but it's the more general purpose looping method. It's good to get practice with it. When in doubt, ` loop` will probably work if you need to loop. – Carcigenicate Nov 10 '19 at 05:17
  • @ivitek And unless there's a corner case that I can't think of, yes, that should help prevent infinite loops. – Carcigenicate Nov 10 '19 at 05:27
0

Here's another solution (using transducers):

 (defn shuffle-strict [s]
  (let [letters (seq s)
        xform (comp (map clojure.string/join)
                 (filter (fn[v] (not= v s))))]
    (when (> (count (into #{} letters)) 1)
      (first (eduction xform (iterate shuffle letters))))))

 (for [_ (range 20)]
         (shuffle-strict "test"))
 ;; => ("etts" "etts" "stte" "etts" "sett" "tste" "tste" "sett" "ttse" "sett" "ttse" "tset" "stte" "ttes" "ttes" "stte" "stte" "etts" "estt" "stet")

(shuffle-strict "t")
;; => nil

(shuffle-strict "ttttt")
;; => nil

We basically create a lazy list of possible shuffles, and then we take the first of them to be different from the input. We also make sure that there are at least 2 different characters in the input, so as not to hang (we return nil here since you don't want to have the input string as a possible result).

mvarela
  • 209
  • 1
  • 3
  • 8
0

If you want your function to return a sequence:

(defn my-shuffle [input]
  (when (-> input set count (> 1))
    (->> input
         (iterate #(apply str (shuffle (seq %))))
         (remove #(= input %)))))
(->> "abc" my-shuffle (take 5))
;; => ("acb" "cba" "bca" "acb" "cab")

(->> "bbb" my-shuffle (take 5))
;; => ()
rmcv
  • 1,956
  • 1
  • 9
  • 8