I'm trying to find pangrams ie sentences that contain all the letters of the alphabet, for instance "The quick brown fox jumps over the lazy dog".

I'm an absolute beginner in elisp, and it ended up being a really difficult exercice for me. My idea what to put all the letters in a set check if all the letters of the alphabet are in this set.

I don't think it was a very good idea because elisp doesn't seem to really have sets, so I ended up using a hashmap, which seems very clunky.

Another things that really bothers me is the two if in is-pangram. I feel that they make the function really difficult to reason about, although it's doing something very basic.

(defun add-to-hashmap (hashmap item)
  "Insert ITEM into HASHMAP and return HASHMAP."
  (puthash item t hashmap)

(defun iter-hashmap-keys (hashmap)
  "Return a sequence of the keys in HASHMAP"
  (let ((keys '()))
     (lambda (key _) (setq keys (cons key keys)))

(defun get-letters (s)
  "Return the set of letters in S.
The set is a hashmap where keys are ascii characters and values are `t`."
  (setq letters (make-hash-table :test 'equal))
   (lambda (hashmap letter) (add-to-hashmap hashmap (downcase letter)))
   (split-string s "")

(defvar ALPHABET
  (let* ((letters (get-letters "abcdefghijklmnopqrstuvwxyz"))
         (_ (remhash "" letters)))

(defun is-pangram (s)
  "Return `t` is S is a pangram, `nil` otherwise."
  (setq letters (get-letters s))
  (if (seq-some
       ;; this function returns returns `nil` if the given letter is
       ;; *present* in the set, and `t` otherwise. That way, seq-some
       ;; returns a non-nil value when there's a letter that is *not* in
       ;; the set.
       (lambda (letter) (if (gethash letter letters) nil t))
       (iter-hashmap-keys ALPHABET))

(provide 'pangram)
(setq letters (get-letters s))

You are setting the same letters variable from different functions; in Emacs Lisp, that sets a global variable which is bad style: (1) that pollutes the global namespace, (2) that could overwrite an existing variable, and (3) the functions are not reentrant and could lead to bad suprises with side-effects. Better introduce variables with let.

Instead of doing (remhash "" letters), you can pass T for OMIT-NULLS in split-string.

Another approach is to transform the input text so as to downcase all letters, remove spaces, remove duplicate letters and check if it corresponds to the expected alphabet. For example:

(equalp (delete-duplicates
         (sort (coerce
                 (remove ?\s "The quick brown fox jumps over the lazy dog"))
        (coerce "abcdefghijklmnopqrstuvwxyz" 'list))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.