Skip to content

Instantly share code, notes, and snippets.

@ericnormand
Last active March 17, 2020 10:26
Show Gist options
  • Save ericnormand/9f9a1e288ba06b9a953356d071f4eeda to your computer and use it in GitHub Desktop.
Save ericnormand/9f9a1e288ba06b9a953356d071f4eeda to your computer and use it in GitHub Desktop.

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.

Examples:

(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 []]
(cond
(nil? f)
(doseq [c wov] (.append sb c))
(contains? vowels f)
(do
(doseq [c wv] (.append sb c))
(recur rst [f] []))
(re-matches #"\W" (str f))
(do
(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
[chars]
(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."
[chars]
{:pre [(= 1 (count (filter #(= \y %) chars)))]}
(->> chars
(filter #(not= \y %))
(chars->word)))
(defn- remove-last-vowel
[word]
(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)
(chars->word))
(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."
[s]
(if (not (empty? s))
(->> (str/split s #"\s+")
(map remove-last-vowel)
(filter #(not (empty? %)))
(str/join " "))
s))
;;TESTS
(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
set))
(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."
[s]
(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)))
s))
(defn- remove-last-vowel
"Removes the last vowel in `s`."
[s]
(remove-index s (index-last-vowel s)))
(defn remove-last-vowels
"Removes the last vowel of each space-separatated word in `s`."
[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?))
last)
[pfx sfx] (split-at split-pos word)]
(->> [pfx (rest sfx)]
flatten
(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]
(cond
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."]
["Hippopotamus",
"Hippopotams"]
["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]
(cond
(or (empty? xs) flag)
xs
(is-vowel? (first xs))
(cons "" (go (rest xs) true))
:else
(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))))
(comment
test.clj
(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]
(clojure.string/replace
(->> (map remove-last-vowel (clojure.string/split s #" "))
(interpose " ")
(apply str)) " " " "))
(defn remove-last-vowels
[phrase]
(let [first-vowel-split (fn [w]
(clojure.string/split w #"[aeiou]" 2))
word-mangler (fn [word] (->> word
reverse
(apply str)
first-vowel-split
(clojure.string/join "")
reverse
(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) ""))
(comment
(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."
[string]
(s/setval [words s/ALL chars (s/filterer #{\a \e \i \o \u}) s/LAST] s/NONE string))
(comment
(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
false))
(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)
rest)]
(-> (concat head tail)
reverse
(clojure.string/join))))
(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"))))
@miner
Copy link

miner commented Mar 13, 2020

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

@PEZ
Copy link

PEZ commented Mar 13, 2020

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

@jeroenvanwijgerden
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