4 ms·
The implementation of "sentence" highlights that Clojure really needs an "interate" function with built in termination. Because no-one uses iterate...
by moomin 11y ago
The implementation of "sentence" highlights that Clojure really needs an "interate" function with built in termination. Because no-one uses iterate...
- kazinator 11y agoI cannot quite agree. The problem here is that termination is indicated by a word which ends in a period, and that word must be included in the word list. This is a poor form of "in-band signaling". Kludgy sequence operators which take or produce one extra item which doesn't satisfy the inclusion predicate can be avoided by proper design of the data representation which avoids this type of framing. TXR Lisp version fixed to use the t symbol for start, nil for end, and straight giterate while true. (I.e. while the sequence element isn't nil): (defun wordlist-sentence (sentence) [(opip `@1.` (split-str @1 #/\s+/) ^(t ,*@1 nil) ;; list starts with t, ends with nil (remqual "")) ;; blanks not permitted sentence]) (defun make-markov-mapping (sentence) (let ((wordlist (wordlist-sentence sentence))) (hash-from-pairs (mapcar (ret ^(,@1 (,@2))) wordlist (rest wordlist)) :equal-based))) (defun markov-data (text) [(opip (split-str @1 #/\./) (mapcar make-markov-mapping) (reduce-left (op hash-uni @1 @2 append) @1 (hash :equal-based))) text]) (defun pick-next-word (map word) (let ((choices [map word])) [choices (rand (length choices))])) (defun sentence (map) (let ((words (cdr [giterate true (op pick-next-word map) t]))) (cat-str words " "))) In this representation, the words which end with a period are still there, but they have a next-word entry in the map: they all point to nil. So we have a nice "spindle": t | +---------+-------+----+ | | | | various start ..... words | | | | . : . : | | | sentence. end. words. | | | +---------+------+ | nil
- moomin 11y agoIt's not ideal, but there's plenty of stuff that looks like this in reality. Personally, I favour Haskell's unfoldr http://hackage.haskell.org/package/base-4.8.1.0/docs/Data-List.html#v:unfoldr http://hackage.haskell.org/package/base-4.8.1.0/docs/Data-Li... which both doesn't require the output to be the same as the intermediate values and cleanly supports termination. (There's actually a very good reason Haskell cares about termination: it means you can be precise about side-effects. With a classic lazy list, any side effects caused by it are determined by how many items you actually evaluate.)
- kazinator 11y agoI've looked at this before; I decided to implement it finally, calling it expand-right. (I use the "reduce" terminology rather than "fold", so the antonym has to be consistent). With explicit lambdas, decrement from 5 to zero, not including it in list: 1> (expand-right (lambda (item) (if (zerop item) nil (cons item (pred item)))) 5) (5 4 3 2 1) Using functional combinators: 2> [expand-right [iff zerop false [callf cons identity pred]] 5] (5 4 3 2 1) Include zero; bit of a mouthful: 1> [expand-right [iff null nilf [callf cons identity [iff zerop nilf pred]]] 5] (5 4 3 2 1 0) Though it can do it, it doesn't look like a great interface when inclusion of the last element is required. Comparison with the inclusive iterate (which I did implement and called "ginterate"--thanks for the nice name: 1> [ginterate [chain zerop not] pred 5] (5 4 3 2 1 0)