Created
September 6, 2018 00:08
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: 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