I often do golf challenges, and thought it might be advantageous to write some code that automatically removes all the unnecessary whitespace from a program.
After a few refactors, this is what I ended up with. In all the tests I've run it through, it's correctly removed only the whitespace that doesn't change the meaning of the program.
Example:
(minimize-code (fn [a] (+ a 1)))
"(fn[a](+ a 1))"
Limitations:
It doesn't handle line-comments well. It doesn't remove comments, so anything after the first
;
is commented out.It only removes unnecessary whitespace. Names still need to be manually shortened (maybe a later project).
What I what advice on:
- Really, I'm looking for any advice. It actually ended up being much simpler than I anticipated, and I'm fairly happy with the end result. I'm sure there's room for improvement though!
(ns bits.golf.helpers.minimizer)
(def safe-neighbors
"The characters that indicate neighboring spaces are safe to remove."
(set "(){}[]\" \t\n"))
(def minimizer-output-path "./minimized-code.clj")
(defn can-remove-cur-char?
"Returns whether or not a character is safe to remove.
A character is considered unsafe to remove if it doesn't change the meaning of the program.
Expects that if prev-char or next-char aren't available, nil will be passed."
[prev-char cur-char next-char]
(cond
(not (Character/isWhitespace ^Character cur-char))
false
(or (nil? prev-char) (nil? next-char))
true
:else
(or (safe-neighbors prev-char) (safe-neighbors next-char))))
(defn safe-remove-whitespace
"Removes any whitespace characters that don't effect the meaning of the program."
[code-str]
(loop [[chr & rest-str] code-str
acc ""]
(if-not chr
acc
(let [prev-chr (if (empty? acc) nil (last acc))
next-chr (if rest-str (first rest-str) nil)
can-remove? (can-remove-cur-char? prev-chr chr next-chr)
replacement-chr (if (Character/isWhitespace ^Character chr)
\space chr)]
(recur rest-str
(if can-remove? acc (str acc replacement-chr)))))))
(defn minimize-code*
"Removes unnecessary whitespace and outputs it to minimizer-output-path, then returns the minimized code."
[code-str]
(let [minimized-code (safe-remove-whitespace code-str)]
(spit minimizer-output-path minimized-code)
minimized-code))
(defmacro minimize-code
"Removes unneccesary whitespace and outputs it to minimizer-output-path, then returns the minimized code."
[& body]
(minimize-code* (apply str body)))
partition 2 1
thenmap
rather than use recursion. I'd be happy to try out or see that approach... \$\endgroup\$ – Chris Murphy Feb 28 '17 at 2:15partition
, but it doesn't give enough context. If many spaces exist in a row, it incorrectly removed all of them regardless of what came before the spaces, since it only sees the immediate previous neighbour instead of the previous neighbor that wasn't removed. If you have a version that usespartition
though, I'd like to see it. \$\endgroup\$ – Carcigenicate Feb 28 '17 at 2:21reduce
to chomp all whitespace down to one space character do the trick, or is it more complex than that? \$\endgroup\$ – Chris Murphy Feb 28 '17 at 2:34