Skip to content

Instantly share code, notes, and snippets.

What would you like to do?

Remove last vowel from words in a sentence

Write a function that removes the last vowel from every word in a sentence. You can split words by spaces. Vowels are a, e, i, o, and u.


(remove-last-vowels "Hi, there!") ;=> "H, ther!"
(remove-last-vowels "This is not a test.") ;=> "Ths s nt tst."
(remove-last-vowels "Hippopotamus") ;=> "Hippopotams"

Thanks to this site for the idea. This is kind of a silly one, but I thought it would be good for testing our Clojure skills.

You can leave comments on these submissions in the gist itself. Please leave comments! You can also hit the Subscribe button to keep abreast of the comments. We’re all here to learn.

(defn remove-last-vowels [s]
(let [sb (StringBuilder.)]
(loop [[f & rst] s wv [] wov []]
(nil? f)
(doseq [c wov] (.append sb c))
(contains? vowels f)
(doseq [c wv] (.append sb c))
(recur rst [f] []))
(re-matches #"\W" (str f))
(doseq [c wov] (.append sb c))
(.append sb f)
(recur rst [] []))
:else ;; must be a consonant
(recur rst (conj wv f) (conj wov f))))
(.toString sb)))
(ns remove-last-vowels
(:require [clojure.string :as str]
[clojure.test :refer :all]))
(def vowels (into #{} (seq "aeiou")))
(defn- chars->word
(apply str (reverse chars)))
(defn- remove-y-value
"Filters the 'y' value when no other regular vowel exists in a word. The technical rules for 'y'
as a vowel are more complex and rely on phonetics. We ignore those rules for simplicity. This also
assumes :chars represent a monosyllabic word, which means only one 'y' is present in the coll."
{:pre [(= 1 (count (filter #(= \y %) chars)))]}
(->> chars
(filter #(not= \y %))
(defn- remove-last-vowel
(loop [remaining (reverse (seq word))
seen []]
(if (empty? remaining)
(remove-y-value seen)
(let [next (first remaining)]
(if (contains? vowels next)
(->> (rest remaining)
(concat seen)
(recur (rest remaining) (conj seen next)))))))
(defn remove-last-vowels
"Removes the last vowel from all words in a given string. Words are delimited by spaces. Extra
spaces between words are removed from the result."
(if (not (empty? s))
(->> (str/split s #"\s+")
(map remove-last-vowel)
(filter #(not (empty? %)))
(str/join " "))
(deftest remove-last-vowels-test
(is (= "H, ther!" (remove-last-vowels "Hi, there!")))
(is (= "Ths s nt tst." (remove-last-vowels "This is not a test.")))
(is (= "Hippopotams" (remove-last-vowels "Hippopotamus")))
(is (= "m gm" (remove-last-vowels "my gym")))
(is (thrown? AssertionError (remove-last-vowel "myy")))
(is (= "To mny spacs!" (remove-last-vowels "Too many spaces!")))
(is (nil? (remove-last-vowels nil)))
(is (empty? (remove-last-vowels ""))))
(ns jvw.wordplay
(:require [clojure.string :refer [upper-case last-index-of split]]
[clojure.set :refer [union]]
[clojure.test :refer [deftest testing is]]))
(def vowels-lower-case #{\a \e \i \o \u})
(def vowels-upper-case (->> (map upper-case vowels-lower-case)
(map first) ; to convert from string to character
(def vowels (union vowels-lower-case vowels-upper-case))
(defn index-last-vowel
"Returns the index of the last vowel in `s` or nil if `s` contains no vowels."
(let [indices (->> vowels
(map (partial last-index-of s))
(remove nil?))]
(when (seq indices)
(apply max indices))))
(defn remove-index
"Removes the character at `idx`. Returns `s` if `idx` is nil."
[s idx]
(if idx
(str (subs s 0 idx)
(subs s (inc idx)))
(defn- remove-last-vowel
"Removes the last vowel in `s`."
(remove-index s (index-last-vowel s)))
(defn remove-last-vowels
"Removes the last vowel of each space-separatated word in `s`."
(->> (split s #" ")
(map remove-last-vowel)
(clojure.string/join " ")))
;; Tests
(deftest index-last-vowel-test
(testing "no vowels"
(is (= nil (index-last-vowel "b"))))
(testing "one vowel"
(is (= 2 (index-last-vowel "cdef"))))
(testing "multiple vowels"
(is (= 4 (index-last-vowel "abcde"))))
(testing "capitals"
(is (= 0 (index-last-vowel "A")))
(is (= 1 (index-last-vowel "aA")))))
(deftest remove-index-test
(testing "degenerate case"
(is (= "abc" (remove-index "abc" nil))))
(testing "single letter"
(is (= "" (remove-index "a" 0))))
(testing "multiple letters"
(is (= "bc" (remove-index "abc" 0)))
(is (= "ac" (remove-index "abc" 1)))
(is (= "ab" (remove-index "abc" 2)))))
(deftest remove-last-vowel-test
(testing "no vowels"
(is (= "bcd" (remove-last-vowel "bcd"))))
(testing "one vowel"
(is (= "" (remove-last-vowel "a")))
(is (= "bc" (remove-last-vowel "abc"))))
(testing "multiple vowels"
(is (= "abcd" (remove-last-vowel "abcde")))))
(deftest remove-last-vowels-test
(testing "degenerate case"
(is (= "" (remove-last-vowels ""))))
(testing "single word"
(is (= "bc" (remove-last-vowels "abc")))
(is (= "abcd" (remove-last-vowels "abcde"))))
(testing "multiple words"
(is (= "Hell, wrld!" (remove-last-vowels "Hello, world!")))
(is (= " fly flis." (remove-last-vowels "A fly flies.")))))
(ns functional-tv-puzzles.2020.remove-last-vowels-367)
(def vowels (set "aeiouAEIOU"))
;; NOTE that when a single letter (vowel) word is removed, one of the surrounding
;; spaces is removed as well, e.g.
;; "This is a test" => "Ths s tst" rather than
;; => "Ths s tst"
;; Plugin impl that splits the word at last vowel and joins surroundings
(defn no-last-vowel-1 [word]
(let [split-pos (->> word
(map-indexed (fn [i x]
(if (vowels x) i -1)))
(filter (complement neg?))
[pfx sfx] (split-at split-pos word)]
(->> [pfx (rest sfx)]
(apply str))))
;; Plugin impl whih uses partitioning, prunes last vowel
;; of applicable partition and rebuilds.
(defn no-last-vowel-2 [word]
(let [[last, before-last & begin] (reverse
(partition-by vowels word))
[last', before-last'] (if (some vowels last)
[(butlast last), before-last]
[last, (butlast before-last)]) ]
(-> begin reverse flatten vec
(into (vec before-last'))
(into (vec last'))
(#(apply str %)))))
(defn remove-last-vowels-shell [s word-fn]
(->> (clojure.string/split s #" ")
(map word-fn)
(filter seq)
(clojure.string/join " ")))
(defn remove-last-vowels
([s] ;; monolith impl
(clojure.string/split s #" ")
(sequence (comp
(map #(apply str (reverse %)))
(map #(reduce (fn [[done? acc] x]
done? [done? (conj acc x)]
(vowels x) [true acc]
:else [done? (conj acc x)]))
[nil []] %))
(map last)
(map reverse)
(map #(apply str %))
(filter seq)))
(interpose " ")
(apply str)))
([s word-fn] ;; with plugin
(remove-last-vowels-shell s word-fn)))
(def test-phrases
[["Those who dare to fail miserably can achieve greatly."
"Thos wh dar t fal miserbly cn achiev gretly."]
["Love is a serious mental disease."
"Lov s serios mentl diseas."]
["Get busy living or get busy dying."
"Gt bsy livng r gt bsy dyng."]
["Please Take A Number"
"Pleas Tak Numbr"]
["Hi, there!",
"H, ther!"]
["This is not a test.",
"Ths s nt tst."]
["Mary didn't have a big huge lamb",
"Mry ddn't hav bg hug lmb"]] )
(defn test-all []
(let [pass? (fn [f]
(->> test-phrases
(map (fn [[input expected]]
(= expected (f input))))
(every? identity)))]
(->> [remove-last-vowels,
#(remove-last-vowels % no-last-vowel-1)
#(remove-last-vowels % no-last-vowel-2)]
(every? pass?))))
(require '[clojure.string :as string])
(defn is-vowel? [character]
(contains? #{\a \e \i \o \u} character))
(defn remove-last-vowel [word]
(defn go [xs flag]
(or (empty? xs) flag)
(is-vowel? (first xs))
(cons "" (go (rest xs) true))
(cons (first xs) (go (rest xs) false))))
(string/join "" (reverse (go ((comp reverse seq) word) false))))
(defn remove-last-vowels [sentence]
(let [words (string/split sentence #" ")
words-without-last-vowels (map (comp not-empty remove-last-vowel) words)]
(string/join " " (remove nil? words-without-last-vowels))))
(ns remove-vowels.core-test
(:use midje.sweet)
(:require [remove-vowels.core :refer :all]))
(facts "remove-last-vowel"
(fact "removes the last vowel"
(remove-last-vowel "there!") => "ther!"))
(facts "remove-last-vowels"
(fact "test 1"
(remove-last-vowels "Hi, there!") => "H, ther!")
(fact "test 2"
(remove-last-vowels "This is not a test.") => "Ths s nt tst.")
(fact "test 3"
(remove-last-vowels "Hippopotamus") => "Hippopotams"))
$ lein midje
All checks (4) succeeded.
(defn remove-last-vowel [w]
(let [[f r] (->> (reverse w)
(split-with #(not ( #{\a \A \e \E \i \I \o \O \u \U} %))))]
(apply str (reverse (concat f (rest r))))))
(defn remove-last-vowels [s]
(->> (map remove-last-vowel (clojure.string/split s #" "))
(interpose " ")
(apply str)) " " " "))
(defn remove-last-vowels
(let [first-vowel-split (fn [w]
(clojure.string/split w #"[aeiou]" 2))
word-mangler (fn [word] (->> word
(apply str)
(clojure.string/join "")
(apply str)))]
(->> (clojure.string/split phrase #" ")
(map word-mangler)
(remove #(= "" %) ) ;; if there's nothing left (a singleton vowel) drop it to avoid double spaces
(clojure.string/join " "))))
(def l9n-vowels
{:english "aeiou"
:swedish "aeiouåäö"})
(def l9n-pattern
(->> l9n-vowels
(reduce-kv (fn [m k v]
(let [vowel-re (re-pattern (str "(?i)[" v "](?=[^" v "]*$)"))]
(assoc m k vowel-re)))
(defn remove-last-vowel
[text lang]
(string/replace text (l9n-pattern lang) ""))
(remove-last-vowel "Hi there!" :english)
;; => "Hi ther!"
(remove-last-vowel "This is not a test." :english)
;; => "This is not a tst."
(remove-last-vowel "Hippopotamus" :english)
;; => "Hippopotams"
(remove-last-vowel "Heja Eric, du är bäst!" :swedish)
;; => "Heja Eric, du är bst!"
(ns main
"Solution to the following challenge: given a string, delete the last vowel of
each word."
(:require [com.rpl.specter :as s]
[clojure.string :as str]))
(def words
"A specter navigator that moves from a string to its individual words."
(s/parser #(str/split % #" ")
#(str/join " " %)))
(def chars
"A specter navigator that moves from a string to its characters."
(s/parser vec #(apply str %)))
(defn delete-last-vowel
"Delete the last vowel in each word of the string."
(s/setval [words s/ALL chars (s/filterer #{\a \e \i \o \u}) s/LAST] s/NONE string))
(delete-last-vowel "Hi there!")
(delete-last-vowel "This is not a test.")
(delete-last-vowel "This is interesting!"))
(defn vowel? [ch]
(case ch
(\a \e \i \o \u) true
(defn remove-last-vowels [phrase]
(loop [i (dec (count phrase)) seen? false chv (vec (seq phrase))]
(if (neg? i)
(apply str chv)
(let [c (chv i)]
(cond (= c \space) (recur (dec i) false chv)
seen? (recur (dec i) true chv)
(vowel? c) (recur (dec i) true (assoc chv i nil))
:else (recur (dec i) false chv))))))
(defn fast-remove-last-vowels [^String phrase]
(loop [i (dec (count phrase)) seen? false sb (StringBuilder. phrase)]
(if (neg? i)
(.toString sb)
(let [c (.charAt phrase i)]
(cond (= c \space) (recur (dec i) false sb)
seen? (recur (dec i) true sb)
(vowel? c) (recur (dec i) true (.deleteCharAt sb i))
:else (recur (dec i) false sb))))))
(ns purelyfunctional-newsletters.issue-368
(:require [clojure.test :refer :all]
[clojure.string :as str]))
(def vowels #{\a \e \i \o \u
\A \E \I \O \U})
(defn vowel? [ch]
(contains? vowels ch))
(defn not-vowel? [ch]
(not (vowel? ch)))
(defn remove-last-vowel [s]
(let [reversed (reverse s)
head (take-while not-vowel? reversed)
tail (-> (drop-while not-vowel? reversed)
(-> (concat head tail)
(defn remove-last-vowels [s]
(let [words (for [word (str/split s #" ")]
(remove-last-vowel word))
words (filter (comp not empty?) words)]
(str/join " " words)))
(deftest remove-last-vowels-tests
(is (= "H, ther!" (remove-last-vowels "Hi, there!")))
(is (= "Ths s nt tst." (remove-last-vowels "This is not a test.")))
(is (= "Hippopotams" (remove-last-vowels "Hippopotamus"))))
Copy link

miner commented Mar 13, 2020

correction on Steve Miner: vowel? should also include the capital vowel characters in the case expression.

Copy link

PEZ commented Mar 13, 2020

Oh, I had missed that it should be for each word!

Copy link

jeroenvanwijgerden commented Mar 17, 2020

Kenny Shen, Viktor P
You can use a set as a function:
(#{\a} \a) ; => \a (truthy)
(#{\a} \b) ; => nil (falsey)
If you know your set does not contain false or nil ((#{false} false) ; => false, whereas (contains? #{false} false) ; => true), you do not need contains?.

Viktor P
Check out complement. You could refactor to
(def not-vowel? (complement vowel?))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment