Fork me on GitHub
#clojurescript
<
2024-03-31
>
Lidor Cohen12:03:37

Hello everyone 👋 I wanna generate seeded pseudo random value in cljs. I haven't found anything useful in cljs ecosystem. Should I just go for some js library?

p-himik13:03:38

Yes, there's no reason not to.

🙏 1
Lidor Cohen13:03:14

@U050PJ2EU Thanks! 🙏 This is what I've been looking for:

(int (rndm/rand-long (rndm/make-random))) => random int
(int (rndm/rand-long (rndm/make-random 123))) => always the same int

john14:03:56

Well I got carried away making a rule-30 based rng 🙂. Super simple toy impl, wouldn't use in prod:

(require 'clojure.edn)
(defn dec-2-bin
  [d]
  (let [bits (-> d (bit-shift-right 0) (.toString 2) (->> (mapv #(clojure.edn/read-string %))))
        bc (- 32 (count bits))
        pad (->> 0 repeat (take bc) vec)]
    (into pad bits)))

(def rands (atom {}))

(defn mk-rule-30-random-int [& [seed]]
  (let [seed (or seed (inc (last (sort (keys @rands)))) 0)]
    (swap! rands assoc seed 0)
    (fn [n]
      (swap! rands update seed inc)
      (let [current-seed (+ seed (get @rands seed))]
        (->> (dec-2-bin current-seed)
             ((fn [bits] (concat (->> 0 repeat (take 32)) bits (->> 0 repeat (take 32)))))
             (iterate #(mapv {[1 1 1] 0 [1 1 0] 0 [1 0 1] 0 [1 0 0] 1 [0 1 1] 1 [0 1 0] 1 [0 0 1] 1 [0 0 0] 0}
                             (partition 3 1 (repeat 0) (cons 0 %))))
             (take (+ 64 (mod current-seed 16)))
             last
             (drop (+ 32 (mod current-seed 8)))
             (take 32)
             (apply str)
             (#(-> % 
                   (js/parseInt 2)
                   (mod n))))))))

(def random-int-a (mk-rule-30-random-int))
(random-int-a 10) ;=> 6

(def random-int-b (mk-rule-30-random-int))
(random-int-b 10) ;=> 9

(def random-int-8 (mk-rule-30-random-int 8))
(random-int-8 10) ;=> 5

(def random-int-16 (mk-rule-30-random-int 16))
(random-int-16 120) ;=> 98

(def random-int-32 (mk-rule-30-random-int 32))
(random-int-32 3) ;=> 1

john14:03:04

Well, that version right there doesn't always produce the same int, for a given input, but you could easily update it to do that

john14:03:53

Here we go, this one always produces 8:

(defn mk-rule-30-random-int [& [seed]]
  (let [seed (or seed (inc (last (sort (keys @rands)))) 0)]
    (swap! rands assoc seed 0)
    (fn [n & [new-seed]]
      (when-not new-seed
        (swap! rands update seed inc))
      (let [current-seed (or new-seed (+ seed (get @rands seed)))]
        (println :seed seed :current-seed current-seed)
        (->> (dec-2-bin current-seed)
             ((fn [bits] (concat (->> 0 repeat (take 32)) bits (->> 0 repeat (take 32)))))
             (iterate #(mapv {[1 1 1] 0 [1 1 0] 0 [1 0 1] 0 [1 0 0] 1 [0 1 1] 1 [0 1 0] 1 [0 0 1] 1 [0 0 0] 0}
                             (partition 3 1 (repeat 0) (cons 0 %))))
             (take (+ 64 (mod current-seed 16)))
             last
             (drop (+ 32 (mod current-seed 8)))
             (take 32)
             (apply str)
             (#(-> % 
                   (js/parseInt 2)
                   (mod n))))))))

(def random-int-a (mk-rule-30-random-int))
(random-int-a 10 255) ;=> 8

john00:04:44

Okay, my final submission to the cljs prng competition:

(defn hash-r30 [seed]
  (let [generations (-> seed (mod 16) (+ 64))
        index (-> seed (mod 8) (+ 32))
        bits (-> seed (bit-shift-right 0) (.toString 2) (->> (mapv #(js/parseInt %))))
        bc (- 32 (count bits))
        pad (->> 0 repeat (take bc) vec)
        padded-bits (into pad bits)
        initial-conditions (concat (->> 0 repeat (take 32)) padded-bits (->> 0 repeat (take 32)))
        rule-30 #(mapv {[1 1 1] 0 [1 1 0] 0 [1 0 1] 0 [1 0 0] 1 [0 1 1] 1 [0 1 0] 1 [0 0 1] 1 [0 0 0] 0}
                       (partition 3 1 (repeat 0) (cons 0 %)))
        decimalize #(js/parseInt (apply str %) 2)
        extract-int #(->> % (take generations) last (drop index) (take 32) decimalize)
        new-rand (->> initial-conditions (iterate rule-30) extract-int)]
    new-rand))

(defn mk-random-int [& {:keys [seed]}]
  (let [rands (atom {})
        seed (or seed (-> @rands keys sort last inc) 0)]
    (swap! rands assoc seed 0)
    (fn [& [n & {new-seed :seed step :step :as rargs}]]
      (let [[new-seed n] (if (= :seed n)
                           [rargs nil]
                           [(+ seed new-seed) n])
            relative-seed (+ 1 step new-seed)]
        (when-not step
          (swap! rands update relative-seed inc))
        (let [current-seed (if step
                             relative-seed
                             (->> relative-seed (get @rands)))
              current-relative (+ relative-seed current-seed)
              new-rand (hash-r30 current-relative)
              mod-rand (if-not (int? n)
                         new-rand
                         (mod new-rand n))]
          mod-rand)))))

(def random-int (mk-random-int :seed 360)) ; <- global seed changes all values below
(random-int 10 :seed 235 :step 8) ;=> Always 5 ; (+ 360 235 8)
(random-int 10 :seed 235 :step 10) ;=> Always 4 ; (+ 360 235 10)
(random-int 10 :seed 255) ;=> Positive Integer under 10 ; (+ 360 255 1)
(random-int 10 :step 257) ;=> Always 7 ; (+ 360 1 257)
(random-int -10) ;=> Negative Integer over -10 ; (+ 360 1 1)
(random-int :seed 255) ;=> Positive Integer ; (+ 360 255 1)
(random-int 10) ;=> Positive Integer ; <- (+ 360 1 1)

Lidor Cohen07:04:59

:rolling_on_the_floor_laughing: @U050PJ2EU you are 👑

john12:04:07

Thanks man. I was trying to boast at a party last night that I just wrote a prng but nobody seemed to care 🙂 That's public domain if you want to turn it into something you want to support. I'm not sure about the cryptographic integrity of rule-30, but from what I understand it is usable. I see some examples online take a vertical sampling, perhaps that's better. You could also use a wider state (initial conditions) or a longer period (generations) to get more diffusion out of it.

john13:04:19

hmmm, vertical sampling sounds like a bad idea actually

john13:04:29

Because directly vertically oriented cells with be strongly causally correlated with one another