Skip to content

Instantly share code, notes, and snippets.

@steverichey
Created September 6, 2018 00:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steverichey/c32ff535ba49990f39c7e6333d6d967a to your computer and use it in GitHub Desktop.
Save steverichey/c32ff535ba49990f39c7e6333d6d967a to your computer and use it in GitHub Desktop.
The Genie script I used to generate this: https://twitter.com/thesteverichey/status/1031229631586861056
//: Playground - noun: a place where people can play
import Cocoa
let caseLookup: [Character: Character] = [
"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "k": "K", "l": "L", "m": "M", "n": "N",
"o": "O", "p": "P", "q": "Q", "r": "R", "s": "S", "t": "T", "u": "U", "v": "V", "w": "W", "x": "X", "y": "Y", "z": "Z"
]
extension Dictionary where Value: Equatable {
func key(forValue value: Value) -> Key? {
return first { $0.1 == value }?.0
}
}
extension Character {
var isVowel: Bool {
return ["a", "e", "i", "o", "u"].contains(self)
}
var lowercased: Character {
return caseLookup.key(forValue: self) ?? self
}
var uppercased: Character {
return caseLookup[self] ?? self
}
}
extension Sequence where Element == Character {
var vowelCount: Int {
return reduce(0) { $0 + ($1.isVowel ? 1 : 0) }
}
var vowelsOnly: [Element] {
return filter { $0.isVowel }
}
var withoutVowels: [Element] {
return filter { !$0.isVowel }
}
var lowercased: [Element] {
return map { $0.lowercased }
}
var uppercased: [Element] {
return map { $0.uppercased }
}
var capitalizingFirstOnly: [Element] {
return prefix(1).uppercased + dropFirst().lowercased
}
func replacingFirstWith(character: Character) -> [Element] {
return [character] + dropFirst()
}
func replacingLastWith(character: Character) -> [Element] {
return dropLast() + [character]
}
}
extension String {
var isQuoted: Bool {
guard let f = first, let l = last else {
return false
}
return f == "\"" && l == "\""
}
func replacingMiddleWith(character: Character) -> String {
return String(prefix(1) + (2..<count).map { _ in character } + suffix(1))
}
func limitTo(length: Int) -> String {
guard length > 0 else {
return ""
}
let difference = count - length
if difference == 0 {
return self
} else if difference < 0 {
return "\(self)\((0..<(-difference)).map { _ in String(suffix(1)) }.joined(separator: ""))"
} else if vowelCount == difference {
return String(withoutVowels)
} else {
let firstLetter = prefix(1)
let lastLetter = suffix(1)
let middleLetters = dropFirst().dropLast()
let middleConsonants = middleLetters.filter { !$0.isVowel }
if middleConsonants.count == (length - 2) {
return firstLetter + middleConsonants + lastLetter
} else if middleConsonants.count < (length - 2) {
// need to include some vowels
let vowelsToInclude = (length - 2) - middleConsonants.count
var result = ""
var includeCount = 0
for letter in middleLetters {
if letter.isVowel && includeCount < vowelsToInclude {
includeCount += 1
result += String(letter)
} else if !letter.isVowel {
result += String(letter)
}
}
return firstLetter + result + lastLetter
} else {
// need to drop some consonants
return String(firstLetter + middleConsonants.prefix(length - 2) + lastLetter)
}
}
}
func performOnEachWord(_ transform: (String) -> String) -> String {
return split(separator: " ").map( { transform(String($0)) } ).joined(separator: " ")
}
}
func spellOut(number: Int, capitalize: Bool = false) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .spellOut
guard let numberString = numberFormatter.string(from: NSNumber(value: number)) else {
fatalError("failed to get number string for \(number)")
}
return capitalize ? String(numberString.capitalizingFirstOnly) : numberString
}
func pluralize(_ text: String, suffix: String, count: Int) -> String {
return count == 1 ? text : text + suffix
}
enum EveryWordRule: CustomStringConvertible {
case startsWith(Character)
case startsWithAll(String)
case middleIs(Character)
case endsWith(Character)
case endsWithAll(String)
case startsAndEndsWith(Character)
case isExactLength(Int)
case replace(String)
case none
var description: String {
switch self {
case .startsWith(let char):
return "make every word start with \"\(char)\""
case .middleIs(let char):
return "make every word have \"\(char)\" in the middle"
case .endsWith(let char):
return "make every word end with \"\(char)\""
case .startsAndEndsWith(let char):
return "make every word start and end with \"\(char)\""
case .isExactLength(let length):
return "make every word have \(spellOut(number: length)) letters"
case .endsWithAll(let string):
return "make every word end in \"\(string)\""
case .replace(let string):
return "make every word \"\(string)\""
case .startsWithAll(let string):
return "make every word start with \"\(string)\""
case .none:
return "make nothing happen"
}
}
func parse(text: String) -> String {
switch self {
case .startsWith(let char):
return text.performOnEachWord({ $0.isQuoted ? $0 : String($0.replacingFirstWith(character: char)) })
case .middleIs(let char):
return text.performOnEachWord({ $0.isQuoted ? $0 : $0.replacingMiddleWith(character: char) })
case .endsWith(let char):
return text.performOnEachWord({ $0.isQuoted ? $0 : String($0.replacingLastWith(character: char)) })
case .startsAndEndsWith(let char):
return text.performOnEachWord({ $0.isQuoted ? $0 : String($0.replacingFirstWith(character: char).replacingLastWith(character: char)) })
case .isExactLength(let length):
return text.performOnEachWord({ $0.isQuoted ? $0 : $0.limitTo(length: length) })
case .endsWithAll(let string):
return text.performOnEachWord({ $0.isQuoted ? $0 : $0.prefix($0.count - string.count) + string })
case .replace(let string):
return text.performOnEachWord({ $0.isQuoted ? $0 : string })
case .startsWithAll(let string):
return text.performOnEachWord({ $0.isQuoted ? $0 : string + $0.suffix($0.count - string.count) })
case .none:
return text
}
}
}
extension Sequence where Element == EveryWordRule {
func parse(text: String) -> String {
var mutableText = text
for rule in self {
mutableText = rule.parse(text: mutableText)
}
return mutableText
}
}
struct Speaker {
let name: String
func speak(_ speech: String, with rule: EveryWordRule = .none) -> String {
return "\(rule.parse(text: name)): \(rule.parse(text: speech))"
}
func speak<S: Sequence>(_ speech: String, withMany rules: S) -> String where S.Element == EveryWordRule {
return "\(rules.parse(text: name)): \(rules.parse(text: speech))"
}
}
struct RuleContainer: CustomStringConvertible {
let rules: [EveryWordRule]
let firstSpeaker: Speaker
let secondSpeaker: Speaker
var description: String {
var result = firstSpeaker.speak("you have \(spellOut(number: rules.count)) \(pluralize("wish", suffix: "es", count: rules.count))")
result += "\n"
for index in 0..<rules.count {
result += secondSpeaker.speak(rules[index].description, withMany: rules.prefix(index))
result += "\n"
result += firstSpeaker.speak("okay", withMany: rules.prefix(index + 1))
result += "\n"
}
result += secondSpeaker.speak("nice", withMany: rules)
return result
}
}
let example1 = RuleContainer(
rules: [
EveryWordRule.endsWith("d"),
EveryWordRule.isExactLength(3),
EveryWordRule.startsWith("r")
],
firstSpeaker: Speaker(name: "genie"),
secondSpeaker: Speaker(name: "me")
)
print(example1.description)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment