From c812132d8fb7734dc0bc18761705139ca3dba2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-E=CC=81tienne?= Date: Sun, 5 Mar 2023 21:43:25 +1100 Subject: [PATCH] Re-architected spelling alphabets and added more metadata --- Sources/Speller/Speller.swift | 2 +- Sources/Speller/SpellingAlphabet.swift | 35 +- Sources/Speller/SpellingAlphabets/Czech.swift | 28 -- .../Speller/SpellingAlphabets/Danish.swift | 30 -- Sources/Speller/SpellingAlphabets/Dutch.swift | 29 -- .../Speller/SpellingAlphabets/Finnish.swift | 30 -- .../Speller/SpellingAlphabets/French.swift | 28 -- .../Speller/SpellingAlphabets/German.swift | 30 -- .../InternationalRadiotelephony.swift | 34 -- .../Speller/SpellingAlphabets/Italian.swift | 28 -- Sources/Speller/SpellingAlphabets/LAPD.swift | 28 -- .../Speller/SpellingAlphabets/Norwegian.swift | 30 -- .../SpellingAlphabets/PGPWordList.swift | 314 ----------------- .../SpellingAlphabets/Portuguese.swift | 28 -- .../PortugueseBrazilian.swift | 28 -- .../Speller/SpellingAlphabets/Slovene.swift | 30 -- .../Speller/SpellingAlphabets/Spanish.swift | 29 -- .../SpellingAlphabet+Catalogue.swift | 20 ++ .../SpellingAlphabet+Czech.swift | 33 ++ .../SpellingAlphabet+Danish.swift | 35 ++ .../SpellingAlphabet+Dutch.swift | 34 ++ .../SpellingAlphabet+Finnish.swift | 35 ++ .../SpellingAlphabet+French.swift | 33 ++ .../SpellingAlphabet+German.swift | 35 ++ ...Alphabet+InternationalRadiotelephony.swift | 39 +++ .../SpellingAlphabet+Italian.swift | 33 ++ .../SpellingAlphabet+LAPD.swift | 33 ++ .../SpellingAlphabet+Norwegian.swift | 35 ++ .../SpellingAlphabet+PGPWordList.swift | 323 ++++++++++++++++++ .../SpellingAlphabet+PortugueseBrazil.swift | 33 ++ .../SpellingAlphabet+PortuguesePortugal.swift | 33 ++ .../SpellingAlphabet+Slovene.swift | 35 ++ .../SpellingAlphabet+Spanish.swift | 34 ++ .../SpellingAlphabet+Swedish.swift | 35 ++ .../SpellingAlphabet+Turkish.swift | 35 ++ .../SpellingAlphabet+USFinancial.swift | 33 ++ .../Speller/SpellingAlphabets/Swedish.swift | 30 -- .../Speller/SpellingAlphabets/Turkish.swift | 30 -- .../SpellingAlphabets/USFinancial.swift | 28 -- Tests/SpellerTests/SpellerTests.swift | 54 +-- .../SpellerTests/SpellingAlphabetTests.swift | 72 ++-- .../SpellingAlphabetsCatalogueTests.swift | 39 +++ 42 files changed, 1054 insertions(+), 886 deletions(-) delete mode 100644 Sources/Speller/SpellingAlphabets/Czech.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Danish.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Dutch.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Finnish.swift delete mode 100644 Sources/Speller/SpellingAlphabets/French.swift delete mode 100644 Sources/Speller/SpellingAlphabets/German.swift delete mode 100644 Sources/Speller/SpellingAlphabets/InternationalRadiotelephony.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Italian.swift delete mode 100644 Sources/Speller/SpellingAlphabets/LAPD.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Norwegian.swift delete mode 100644 Sources/Speller/SpellingAlphabets/PGPWordList.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Portuguese.swift delete mode 100644 Sources/Speller/SpellingAlphabets/PortugueseBrazilian.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Slovene.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Spanish.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Catalogue.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Czech.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Danish.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Dutch.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Finnish.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+French.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+German.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+InternationalRadiotelephony.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Italian.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+LAPD.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Norwegian.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+PGPWordList.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+PortugueseBrazil.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+PortuguesePortugal.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Slovene.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Spanish.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Swedish.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+Turkish.swift create mode 100644 Sources/Speller/SpellingAlphabets/SpellingAlphabet+USFinancial.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Swedish.swift delete mode 100644 Sources/Speller/SpellingAlphabets/Turkish.swift delete mode 100644 Sources/Speller/SpellingAlphabets/USFinancial.swift create mode 100644 Tests/SpellerTests/SpellingAlphabetsCatalogueTests.swift diff --git a/Sources/Speller/Speller.swift b/Sources/Speller/Speller.swift index 6e2659a..ecd4207 100644 --- a/Sources/Speller/Speller.swift +++ b/Sources/Speller/Speller.swift @@ -15,7 +15,7 @@ public class Speller { /// - alphabet: The spelling alphabet to be used to spell out the input phrase /// - useSpellingAlphabetNumbers: Whether to use the spelling alphabet's numbers, or default to simple number names /// - Returns: An array of `SpelledCharacter`s describing each character of the input phrase - public static func spell(phrase: String, withSpellingAlphabet alphabet: Alphabet.Type, useSpellingAlphabetNumbers: Bool) -> [SpelledCharacter] { + public static func spell(phrase: String, withSpellingAlphabet alphabet: SpellingAlphabet, useSpellingAlphabetNumbers: Bool) -> [SpelledCharacter] { let spelling = alphabet.spell(phrase, withNumbers: useSpellingAlphabetNumbers) return describeUnknownCharacters(inSpelling: spelling) } diff --git a/Sources/Speller/SpellingAlphabet.swift b/Sources/Speller/SpellingAlphabet.swift index ee6da0a..4ce4383 100644 --- a/Sources/Speller/SpellingAlphabet.swift +++ b/Sources/Speller/SpellingAlphabet.swift @@ -7,22 +7,37 @@ import Foundation public typealias SpellingAlphabetContent = [String: CodeWordCollection] -public protocol SpellingAlphabet { - static var mainContent: SpellingAlphabetContent { get } - static var numbersContent: SpellingAlphabetContent { get } - static func spell(_ phrase: String, withNumbers: Bool) -> [SpelledCharacter] -} +public class SpellingAlphabet { + + let uniqueIdentifier: String + + let associatedLanguageCode: String? + + let associatedRegionCode: String? -extension SpellingAlphabet { + let recommendedJoinerWord: String? + + let mainContent: SpellingAlphabetContent + + let numbersContent: SpellingAlphabetContent + + internal init(uniqueIdentifier: String, associatedLanguageCode: String? = nil, associatedRegionCode: String? = nil, recommendedJoinerWord: String? = nil, mainContent: SpellingAlphabetContent, numbersContent: SpellingAlphabetContent) { + self.uniqueIdentifier = uniqueIdentifier + self.associatedLanguageCode = associatedLanguageCode + self.associatedRegionCode = associatedRegionCode + self.recommendedJoinerWord = recommendedJoinerWord + self.mainContent = mainContent + self.numbersContent = numbersContent + } - public static func spell(_ phrase: String, withNumbers: Bool) -> [SpelledCharacter] { + public func spell(_ phrase: String, withNumbers: Bool) -> [SpelledCharacter] { return enumerate(phrase: phrase) .enumerated() .map { spell(character: $1, atIndex: $0, withNumbers: withNumbers) } } // MARK: - Private helpers - Spelling - private static func enumerate(phrase: String) -> [String] { + private func enumerate(phrase: String) -> [String] { var characters: [String] = [] phrase.enumerateSubstrings(in: phrase.startIndex.. SpelledCharacter { + private func spell(character: String, atIndex index: Int, withNumbers: Bool) -> SpelledCharacter { if let codeWordCollection = codeWordCollection(forCharacter: character, withNumbers: withNumbers) { return SpelledCharacter(character: character, position: index, spellingResult: .match(codeWordCollection)) } else { @@ -41,7 +56,7 @@ extension SpellingAlphabet { } } - private static func codeWordCollection(forCharacter character: String, withNumbers: Bool) -> CodeWordCollection? { + private func codeWordCollection(forCharacter character: String, withNumbers: Bool) -> CodeWordCollection? { let spellingContent = withNumbers ? mainContent.merging(numbersContent, uniquingKeysWith: { lhs, _ in lhs }) : mainContent if let codeWordCollection = spellingContent[character] { diff --git a/Sources/Speller/SpellingAlphabets/Czech.swift b/Sources/Speller/SpellingAlphabets/Czech.swift deleted file mode 100644 index daf691d..0000000 --- a/Sources/Speller/SpellingAlphabets/Czech.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct Czech: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Adam", "B": "Božena", - "C": "Cyril", "D": "David", - "E": "Emil", "F": "František", - "G": "Gustav", "H": "Helena", - "I": "Ivan", "J": "Josef", - "K": "Karel", "L": "Ludvik", - "M": "Marie", "N": "Norbert", - "O": "Otakar", "P": "Petr", - "Q": "Quido", "R": "Rudolf", - "S": "Svatopluk", "T": "Tomáš", - "U": "Urban", "V": "Václav", - "W": "Dvojité V", "X": "Xaver", - "Y": "Ypsilon", "Z": "Zuzana" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/Danish.swift b/Sources/Speller/SpellingAlphabets/Danish.swift deleted file mode 100644 index 6b473ad..0000000 --- a/Sources/Speller/SpellingAlphabets/Danish.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct Danish: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Anna", "Å": "Aase", - "Æ": "Ægir", "B": "Bernhard", - "C": "Cecilie", "D": "David", - "E": "Erik", "F": "Frederik", - "G": "Georg", "H": "Hans", - "I": "Ida", "J": "Johan", - "K": "Karen", "L": "Ludvig", - "M": "Mari", "N": "Nikolaj", - "O": "Odin", "Ø": "Øresund", - "P": "Peter", "Q": "Quintus", - "R": "Rasmus", "S": "Søren", - "T": "Theodor", "U": "Ulla", - "V": "Viggo", "W": "William", - "X": "Xerxes", "Y": "Yrsa", - "Z": "Zackarias" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/Dutch.swift b/Sources/Speller/SpellingAlphabets/Dutch.swift deleted file mode 100644 index fa6010d..0000000 --- a/Sources/Speller/SpellingAlphabets/Dutch.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct Dutch: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Anton", "B": "Bernard", - "C": "Cornelis", "D": "Dirk", - "E": "Eduard", "F": "Ferdinand", - "G": "Gerard", "H": "Hendrik", - "I": "Izaak", "J": "Julius", - "K": "Karel", "L": "Lodewijk", - "M": "Maria", "N": "Nico", - "O": "Otto", "P": "Pieter", - "Q": "Quotiënt", "R": "Richard", - "S": "Simon", "T": "Theodor", - "U": "Utrecht", "V": "Victor", - "W": "Willem", "X": "Xanthippe", - "IJ": "IJmuiden", "Y": "Ypsilon", - "Z": "Zaandam" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/Finnish.swift b/Sources/Speller/SpellingAlphabets/Finnish.swift deleted file mode 100644 index 11b5c27..0000000 --- a/Sources/Speller/SpellingAlphabets/Finnish.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct Finnish: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Aarne", "Ä": "Äiti", - "Å": "Åke", "B": "Bertta", - "C": "Celsius", "D": "Daavid", - "E": "Eemeli", "F": "Faarao", - "G": "Gideon", "H": "Heikki", - "I": "Iivari", "J": "Jussi", - "K": "Kalle", "L": "Lauri", - "M": "Matti", "N": "Niilo", - "O": "Otto", "Ö": "Öljy", - "P": "Paavo", "Q": "Kuu", - "R": "Risto", "S": "Sakari", - "T": "Tyyne", "U": "Urho", - "V": "Vihtori", "W": "Wiski", - "X": "Äksä", "Y": "Yrjö", - "Z": "Tseta" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/French.swift b/Sources/Speller/SpellingAlphabets/French.swift deleted file mode 100644 index 4ef6f70..0000000 --- a/Sources/Speller/SpellingAlphabets/French.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct French: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Anatole", "B": "Berthe", - "C": "Célestin", "D": "Désiré", - "E": "Eugène", "F": "François", - "G": "Gaston", "H": "Henri", - "I": "Irma", "J": "Joseph", - "K": "Kléber", "L": "Louis", - "M": "Marcel", "N": "Nicolas", - "O": "Oscar", "P": "Pierre", - "Q": "Quintal", "R": "Raoul", - "S": "Suzanne", "T": "Thérèse", - "U": "Ursule", "V": "Victor", - "W": "William", "X": "Xavier", - "Y": "Yvonne", "Z": "Zoé" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/German.swift b/Sources/Speller/SpellingAlphabets/German.swift deleted file mode 100644 index cf868f8..0000000 --- a/Sources/Speller/SpellingAlphabets/German.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct German: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Anton", "Ä": "Ärger", - "B": "Berta", "C": "Cäsar", - "D": "Dora", "E": "Emil", - "F": "Friedrich", "G": "Gustav", - "H": "Heinrich", "I": "Ida", - "J": "Julius", "K": ["Kaufmann", "Konrad"], - "L": "Ludwig", "M": "Martha", - "N": "Nordpol", "O": "Otto", - "Ö": "Ökonom", "P": "Paula", - "Q": "Quelle", "R": "Richard", - "S": ["Samuel", "Siegfried"], "ß": "Eszett", - "T": "Theodor", "U": "Ulrich", - "Ü": ["Übermut", "Übel"], "V": "Viktor", - "W": "Wilhelm", "X": ["Xanthippe", "Xaver"], - "Y": "Ypsilon", "Z": ["Zacharias", "Zürich"] - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/InternationalRadiotelephony.swift b/Sources/Speller/SpellingAlphabets/InternationalRadiotelephony.swift deleted file mode 100644 index 9ffc7cb..0000000 --- a/Sources/Speller/SpellingAlphabets/InternationalRadiotelephony.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct InternationalRadiotelephony: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Alfa", "B": "Bravo", - "C": "Charlie", "D": "Delta", - "E": "Echo", "F": "Foxtrot", - "G": "Golf", "H": "Hotel", - "I": "India", "J": "Juliett", - "K": "Kilo", "L": "Lima", - "M": "Mike", "N": "November", - "O": "Oscar", "P": "Papa", - "Q": "Quebec", "R": "Romeo", - "S": "Sierra", "T": "Tango", - "U": "Uniform", "V": "Victor", - "W": "Whiskey", "X": "X-ray", - "Y": "Yankee", "Z": "Zulu", - ] - - public static var numbersContent: SpellingAlphabetContent = [ - "1": "Unaone", "2": "Bissotwo", - "3": "Terrathree", "4": "Kartefour", - "5": "Pantafive", "6": "Soxisix", - "7": "Setteseven", "8": "Oktoeight", - "9": "Novenine", "0": "Nadazero" - ] - -} diff --git a/Sources/Speller/SpellingAlphabets/Italian.swift b/Sources/Speller/SpellingAlphabets/Italian.swift deleted file mode 100644 index aaf0f57..0000000 --- a/Sources/Speller/SpellingAlphabets/Italian.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct Italian: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Ancona", "B": "Bologna", - "C": "Como", "D": "Domodossola", - "E": "Empoli", "F": "Firenze", - "G": "Genova", "H": "Hotel", - "I": "Imola", "J": "Jolly", - "K": "Kappa", "L": "Livorno", - "M": "Milano", "N": "Napoli", - "O": "Otranto", "P": "Padova", - "Q": "Quadro", "R": "Roma", - "S": "Savona", "T": "Torino", - "U": "Udine", "V": "Venezia", - "W": "Washington", "X": ["Xeres", "Xilofono"], - "Y": ["York", "Yacht"], "Z": "Zara" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/LAPD.swift b/Sources/Speller/SpellingAlphabets/LAPD.swift deleted file mode 100644 index 39241b1..0000000 --- a/Sources/Speller/SpellingAlphabets/LAPD.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct LAPD: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Adam", "B": "Boy", - "C": "Charles", "D": "David", - "E": "Edward", "F": "Frank", - "G": "George", "H": "Henry", - "I": "Ida", "J": "John", - "K": "King", "L": "Lincoln", - "M": "Mary", "N": "Nora", - "O": "Ocean", "P": "Paul", - "Q": "Queen", "R": "Robert", - "S": "Sam", "T": "Tom", - "U": "Union", "V": "Victor", - "W": "William", "X": "X-ray", - "Y": "Young", "Z": "Zebra" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/Norwegian.swift b/Sources/Speller/SpellingAlphabets/Norwegian.swift deleted file mode 100644 index f4d43bf..0000000 --- a/Sources/Speller/SpellingAlphabets/Norwegian.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct Norwegian: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "A": "Anna", "Å": "Åse", - "Æ": "Ærlig", "B": "Bernhard", - "C": "Caesar", "D": "David", - "E": "Edith", "F": "Fredrik", - "G": "Gustav", "H": "Harald", - "I": "Ivar", "J": "Johan", - "K": "Karin", "L": "Ludvig", - "M": "Martin", "N": "Nils", - "O": "Olivia", "Ø": "Østen", - "P": "Petter", "Q": "Quintus", - "R": "Rikard", "S": "Sigrid", - "T": "Teodor", "U": "Ulrik", - "V": "Enkelt-V", "W": "Dobbelt-V", - "X": "Xerxes", "Y": "Yngling", - "Z": "Zakarias" - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - -} diff --git a/Sources/Speller/SpellingAlphabets/PGPWordList.swift b/Sources/Speller/SpellingAlphabets/PGPWordList.swift deleted file mode 100644 index 0e6ff15..0000000 --- a/Sources/Speller/SpellingAlphabets/PGPWordList.swift +++ /dev/null @@ -1,314 +0,0 @@ -// -// Speller -// Copyright © Jean-Étienne. All rights reserved. -// - -import Foundation - -public struct PGPWordList: SpellingAlphabet { - - public static var mainContent: SpellingAlphabetContent = [ - "00": ["aardvark", "adroitness"], - "01": ["absurd", "adviser"], - "02": ["accrue", "aftermath"], - "03": ["acme", "aggregate"], - "04": ["adrift", "alkali"], - "05": ["adult", "almighty"], - "06": ["afflict", "amulet"], - "07": ["ahead", "amusement"], - "08": ["aimless", "antenna"], - "09": ["Algol", "applicant"], - "0A": ["allow", "Apollo"], - "0B": ["alone", "armistice"], - "0C": ["ammo", "article"], - "0D": ["ancient", "asteroid"], - "0E": ["apple", "Atlantic"], - "0F": ["artist", "atmosphere"], - "10": ["assume", "autopsy"], - "11": ["Athens", "Babylon"], - "12": ["atlas", "backwater"], - "13": ["Aztec", "barbecue"], - "14": ["baboon", "belowground"], - "15": ["backfield", "bifocals"], - "16": ["backward", "bodyguard"], - "17": ["banjo", "bookseller"], - "18": ["beaming", "borderline"], - "19": ["bedlamp", "bottomless"], - "1A": ["beehive", "Bradbury"], - "1B": ["beeswax", "bravado"], - "1C": ["befriend", "Brazilian"], - "1D": ["Belfast", "breakaway"], - "1E": ["berserk", "Burlington"], - "1F": ["billiard", "businessman"], - "20": ["bison", "butterfat"], - "21": ["blackjack", "Camelot"], - "22": ["blockade", "candidate"], - "23": ["blowtorch", "cannonball"], - "24": ["bluebird", "Capricorn"], - "25": ["bombast", "caravan"], - "26": ["bookshelf", "caretaker"], - "27": ["brackish", "celebrate"], - "28": ["breadline", "cellulose"], - "29": ["breakup", "certify"], - "2A": ["brickyard", "chambermaid"], - "2B": ["briefcase", "Cherokee"], - "2C": ["Burbank", "Chicago"], - "2D": ["button", "clergyman"], - "2E": ["buzzard", "coherence"], - "2F": ["cement", "combustion"], - "30": ["chairlift", "commando"], - "31": ["chatter", "company"], - "32": ["checkup", "component"], - "33": ["chisel", "concurrent"], - "34": ["choking", "confidence"], - "35": ["chopper", "conformist"], - "36": ["Christmas", "congregate"], - "37": ["clamshell", "consensus"], - "38": ["classic", "consulting"], - "39": ["classroom", "corporate"], - "3A": ["cleanup", "corrosion"], - "3B": ["clockwork", "councilman"], - "3C": ["cobra", "crossover"], - "3D": ["commence", "crucifix"], - "3E": ["concert", "cumbersome"], - "3F": ["cowbell", "customer"], - "40": ["crackdown", "Dakota"], - "41": ["cranky", "decadence"], - "42": ["crowfoot", "December"], - "43": ["crucial", "decimal"], - "44": ["crumpled", "designing"], - "45": ["crusade", "detector"], - "46": ["cubic", "detergent"], - "47": ["dashboard", "determine"], - "48": ["deadbolt", "dictator"], - "49": ["deckhand", "dinosaur"], - "4A": ["dogsled", "direction"], - "4B": ["dragnet", "disable"], - "4C": ["drainage", "disbelief"], - "4D": ["dreadful", "disruptive"], - "4E": ["drifter", "distortion"], - "4F": ["dropper", "document"], - "50": ["drumbeat", "embezzle"], - "51": ["drunken", "enchanting"], - "52": ["Dupont", "enrollment"], - "53": ["dwelling", "enterprise"], - "54": ["eating", "equation"], - "55": ["edict", "equipment"], - "56": ["egghead", "escapade"], - "57": ["eightball", "Eskimo"], - "58": ["endorse", "everyday"], - "59": ["endow", "examine"], - "5A": ["enlist", "existence"], - "5B": ["erase", "exodus"], - "5C": ["escape", "fascinate"], - "5D": ["exceed", "filament"], - "5E": ["eyeglass", "finicky"], - "5F": ["eyetooth", "forever"], - "60": ["facial", "fortitude"], - "61": ["fallout", "frequency"], - "62": ["flagpole", "gadgetry"], - "63": ["flatfoot", "Galveston"], - "64": ["flytrap", "getaway"], - "65": ["fracture", "glossary"], - "66": ["framework", "gossamer"], - "67": ["freedom", "graduate"], - "68": ["frighten", "gravity"], - "69": ["gazelle", "guitarist"], - "6A": ["Geiger", "hamburger"], - "6B": ["glitter", "Hamilton"], - "6C": ["glucose", "handiwork"], - "6D": ["goggles", "hazardous"], - "6E": ["goldfish", "headwaters"], - "6F": ["gremlin", "hemisphere"], - "70": ["guidance", "hesitate"], - "71": ["hamlet", "hideaway"], - "72": ["highchair", "holiness"], - "73": ["hockey", "hurricane"], - "74": ["indoors", "hydraulic"], - "75": ["indulge", "impartial"], - "76": ["inverse", "impetus"], - "77": ["involve", "inception"], - "78": ["island", "indigo"], - "79": ["jawbone", "inertia"], - "7A": ["keyboard", "infancy"], - "7B": ["kickoff", "inferno"], - "7C": ["kiwi", "informant"], - "7D": ["klaxon", "insincere"], - "7E": ["locale", "insurgent"], - "7F": ["lockup", "integrate"], - "80": ["merit", "intention"], - "81": ["minnow", "inventive"], - "82": ["miser", "Istanbul"], - "83": ["Mohawk", "Jamaica"], - "84": ["mural", "Jupiter"], - "85": ["music", "leprosy"], - "86": ["necklace", "letterhead"], - "87": ["Neptune", "liberty"], - "88": ["newborn", "maritime"], - "89": ["nightbird", "matchmaker"], - "8A": ["Oakland", "maverick"], - "8B": ["obtuse", "Medusa"], - "8C": ["offload", "megaton"], - "8D": ["optic", "microscope"], - "8E": ["orca", "microwave"], - "8F": ["payday", "midsummer"], - "90": ["peachy", "millionaire"], - "91": ["pheasant", "miracle"], - "92": ["physique", "misnomer"], - "93": ["playhouse", "molasses"], - "94": ["Pluto", "molecule"], - "95": ["preclude", "Montana"], - "96": ["prefer", "monument"], - "97": ["preshrunk", "mosquito"], - "98": ["printer", "narrative"], - "99": ["prowler", "nebula"], - "9A": ["pupil", "newsletter"], - "9B": ["puppy", "Norwegian"], - "9C": ["python", "October"], - "9D": ["quadrant", "Ohio"], - "9E": ["quiver", "onlooker"], - "9F": ["quota", "opulent"], - "A0": ["ragtime", "Orlando"], - "A1": ["ratchet", "outfielder"], - "A2": ["rebirth", "Pacific"], - "A3": ["reform", "pandemic"], - "A4": ["regain", "Pandora"], - "A5": ["reindeer", "paperweight"], - "A6": ["rematch", "paragon"], - "A7": ["repay", "paragraph"], - "A8": ["retouch", "paramount"], - "A9": ["revenge", "passenger"], - "AA": ["reward", "pedigree"], - "AB": ["rhythm", "Pegasus"], - "AC": ["ribcage", "penetrate"], - "AD": ["ringbolt", "perceptive"], - "AE": ["robust", "performance"], - "AF": ["rocker", "pharmacy"], - "B0": ["ruffled", "phonetic"], - "B1": ["sailboat", "photograph"], - "B2": ["sawdust", "pioneer"], - "B3": ["scallion", "pocketful"], - "B4": ["scenic", "politeness"], - "B5": ["scorecard", "positive"], - "B6": ["Scotland", "potato"], - "B7": ["seabird", "processor"], - "B8": ["select", "provincial"], - "B9": ["sentence", "proximate"], - "BA": ["shadow", "puberty"], - "BB": ["shamrock", "publisher"], - "BC": ["showgirl", "pyramid"], - "BD": ["skullcap", "quantity"], - "BE": ["skydive", "racketeer"], - "BF": ["slingshot", "rebellion"], - "C0": ["slowdown", "recipe"], - "C1": ["snapline", "recover"], - "C2": ["snapshot", "repellent"], - "C3": ["snowcap", "replica"], - "C4": ["snowslide", "reproduce"], - "C5": ["solo", "resistor"], - "C6": ["southward", "responsive"], - "C7": ["soybean", "retraction"], - "C8": ["spaniel", "retrieval"], - "C9": ["spearhead", "retrospect"], - "CA": ["spellbind", "revenue"], - "CB": ["spheroid", "revival"], - "CC": ["spigot", "revolver"], - "CD": ["spindle", "sandalwood"], - "CE": ["spyglass", "sardonic"], - "CF": ["stagehand", "Saturday"], - "D0": ["stagnate", "savagery"], - "D1": ["stairway", "scavenger"], - "D2": ["standard", "sensation"], - "D3": ["stapler", "sociable"], - "D4": ["steamship", "souvenir"], - "D5": ["sterling", "specialist"], - "D6": ["stockman", "speculate"], - "D7": ["stopwatch", "stethoscope"], - "D8": ["stormy", "stupendous"], - "D9": ["sugar", "supportive"], - "DA": ["surmount", "surrender"], - "DB": ["suspense", "suspicious"], - "DC": ["sweatband", "sympathy"], - "DD": ["swelter", "tambourine"], - "DE": ["tactics", "telephone"], - "DF": ["talon", "therapist"], - "E0": ["tapeworm", "tobacco"], - "E1": ["tempest", "tolerance"], - "E2": ["tiger", "tomorrow"], - "E3": ["tissue", "torpedo"], - "E4": ["tonic", "tradition"], - "E5": ["topmost", "travesty"], - "E6": ["tracker", "trombonist"], - "E7": ["transit", "truncated"], - "E8": ["trauma", "typewriter"], - "E9": ["treadmill", "ultimate"], - "EA": ["Trojan", "undaunted"], - "EB": ["trouble", "underfoot"], - "EC": ["tumor", "unicorn"], - "ED": ["tunnel", "unify"], - "EE": ["tycoon", "universe"], - "EF": ["uncut", "unravel"], - "F0": ["unearth", "upcoming"], - "F1": ["unwind", "vacancy"], - "F2": ["uproot", "vagabond"], - "F3": ["upset", "vertigo"], - "F4": ["upshot", "Virginia"], - "F5": ["vapor", "visitor"], - "F6": ["village", "vocalist"], - "F7": ["virus", "voyager"], - "F8": ["Vulcan", "warranty"], - "F9": ["waffle", "Waterloo"], - "FA": ["wallet", "whimsical"], - "FB": ["watchword", "Wichita"], - "FC": ["wayside", "Wilmington"], - "FD": ["willow", "Wyoming"], - "FE": ["woodlark", "yesteryear"], - "FF": ["Zulu", "Yucatan"] - ] - - public static var numbersContent: SpellingAlphabetContent = [:] - - public static func spell(_ phrase: String, withNumbers: Bool) -> [SpelledCharacter] { - return enumerate(phrase: phrase) - .enumerated() - .map { spell(character: $1, atIndex: $0) } - } - - // MARK: - Private helpers - Spelling - private static func enumerate(phrase: String) -> [String] { - let stripNonHex = StringTransform(rawValue: "[^0123456789abcdefABCDEF] Remove;") - let sanitizedPhrase = phrase - .uppercased() - .applyingTransform(stripNonHex, reverse: false) - .map { $0.components(withLength: 2) } ?? phrase.components(withLength: 2) - - return sanitizedPhrase - } - - private static func spell(character: String, atIndex index: Int) -> SpelledCharacter { - if let codeWordCollection = codeWordCollection(forCharacter: character) { - let singleCodeWordCollection = CodeWordCollection(codeWord: codeWordCollection.codeWords[index % 2]) - return SpelledCharacter(character: character, position: index, spellingResult: .match(singleCodeWordCollection)) - } else { - return SpelledCharacter(character: character, position: index, spellingResult: .unknown) - } - } - - private static func codeWordCollection(forCharacter character: String) -> CodeWordCollection? { - guard let codeWord = mainContent[character] else { return nil } - return codeWord - } - -} - -private extension String { - - func components(withLength length: Int) -> [String] { - return stride(from: 0, to: self.count, by: length).map { - let start = self.index(self.startIndex, offsetBy: $0) - let end = self.index(start, offsetBy: length, limitedBy: self.endIndex) ?? self.endIndex - return String(self[start.. [SpelledCharacter] { + return enumerate(phrase: phrase) + .enumerated() + .map { spell(character: $1, atIndex: $0) } + } + + // MARK: - Private helpers - Spelling + private func enumerate(phrase: String) -> [String] { + let stripNonHex = StringTransform(rawValue: "[^0123456789abcdefABCDEF] Remove;") + let sanitizedPhrase = phrase + .uppercased() + .applyingTransform(stripNonHex, reverse: false) + .map { $0.components(withLength: 2) } ?? phrase.components(withLength: 2) + + return sanitizedPhrase + } + + private func spell(character: String, atIndex index: Int) -> SpelledCharacter { + if let codeWordCollection = codeWordCollection(forCharacter: character) { + let singleCodeWordCollection = CodeWordCollection(codeWord: codeWordCollection.codeWords[index % 2]) + return SpelledCharacter(character: character, position: index, spellingResult: .match(singleCodeWordCollection)) + } else { + return SpelledCharacter(character: character, position: index, spellingResult: .unknown) + } + } + + private func codeWordCollection(forCharacter character: String) -> CodeWordCollection? { + guard let codeWord = mainContent[character] else { return nil } + return codeWord + } + +} + +private extension String { + + func components(withLength length: Int) -> [String] { + return stride(from: 0, to: self.count, by: length).map { + let start = self.index(self.startIndex, offsetBy: $0) + let end = self.index(start, offsetBy: length, limitedBy: self.endIndex) ?? self.endIndex + return String(self[start..