13
13
import TestsUtils
14
14
15
15
// Mini benchmark implementing roman numeral conversions to/from integers.
16
- // Measures performance of String/Substring/UTF8View with very short string
17
- // arguments and methods: hasPrefix/starts(with:), removeFirst/dropFirst and
18
- // String.append().
19
- //
20
- // For comparison, there's one extra variant with character based parsing
21
- // algorithm: `Roman.DictCharInt.map.reduce`.
16
+ // Measures performance of Substring.starts(with:), dropFirst and String.append
17
+ // with very short string arguments.
22
18
23
19
let t : [ BenchmarkCategory ] = [ . api, . String, . algorithm]
24
- let N = 270 // 1100
20
+ let N = 270
25
21
26
22
public let RomanNumbers = [
27
- // Imperative style variants:
28
- // String permuatations
29
- BenchmarkInfo (
30
- name: " Roman.String.hasPrefix.removeFirst " ,
31
- runFunction: {
32
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanShPrF: ) ) } ,
33
- tags: t) ,
34
- BenchmarkInfo (
35
- name: " Roman.String.hasPrefix.dropFirst " ,
36
- runFunction: {
37
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanShPdF: ) ) } ,
38
- tags: t) ,
39
- BenchmarkInfo (
40
- name: " Roman.String.startsWith.dropFirst " ,
41
- runFunction: {
42
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanSsWdF: ) ) } ,
43
- tags: t) ,
44
- BenchmarkInfo (
45
- name: " Roman.String.startsWith.removeFirst " ,
46
- runFunction: {
47
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanSsWrF: ) ) } ,
48
- tags: t) ,
49
- // Substring permutations
50
- BenchmarkInfo (
51
- name: " Roman.Substring.hasPrefix.removeFirst " ,
52
- runFunction: {
53
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanSShPrF: ) ) } ,
54
- tags: t) ,
55
- BenchmarkInfo (
56
- name: " Roman.Substring.hasPrefix.dropFirst " ,
57
- runFunction: {
58
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanSShPdF: ) ) } ,
59
- tags: t) ,
60
23
BenchmarkInfo (
61
24
name: " Roman.Substring.startsWith.dropFirst " ,
62
25
runFunction: {
63
26
checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanSSsWdF: ) ) } ,
64
27
tags: t) ,
65
- BenchmarkInfo (
66
- name: " Roman.Substring.startsWith.removeFirst " ,
67
- runFunction: {
68
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanSSsWrF: ) ) } ,
69
- tags: t) ,
70
- // UTF8View SubSequence
71
- BenchmarkInfo (
72
- name: " Roman.UTF8ViewSS.startsWith.dropFirst " ,
73
- runFunction: {
74
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanU8SSsWdF: ) ) } ,
75
- tags: t) ,
76
- BenchmarkInfo (
77
- name: " Roman.UTF8ViewSS.startsWith.removeFirst " ,
78
- runFunction: {
79
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanU8SSsWrF: ) ) } ,
80
- tags: t) ,
81
- // FP-style variants:
82
- BenchmarkInfo (
83
- name: " Roman.String.hasPrefix.dropFirst.RI " ,
84
- runFunction: {
85
- checkId ( $0, upTo: N, { $0. romanReduceInto } , Int . init ( romanReduceInto: ) ) } ,
86
- tags: t) ,
87
- BenchmarkInfo (
88
- name: " Roman.String.hasPrefix.dropFirst.R " ,
89
- runFunction: {
90
- checkId ( $0, upTo: N, { $0. romanReduce } , Int . init ( romanReduce: ) ) } ,
91
- tags: t) ,
92
- BenchmarkInfo (
93
- name: " Roman.DictCharInt.map.reduce " ,
94
- runFunction: {
95
- checkId ( $0, upTo: N, { $0. romanNumeral } , Int . init ( romanMapReduce: ) ) } ,
96
- tags: t) ,
97
28
]
98
29
99
30
@inline ( __always)
@@ -112,8 +43,6 @@ let romanTable: KeyValuePairs<String, Int> = [
112
43
" I " : 1 ,
113
44
]
114
45
115
- let romanTableUTF8 = romanTable. map { ( $0. utf8, $1) }
116
-
117
46
extension BinaryInteger {
118
47
// Imperative Style
119
48
// See https://www.rosettacode.org/wiki/Roman_numerals/Encode#Swift
@@ -131,82 +60,6 @@ extension BinaryInteger {
131
60
return result
132
61
}
133
62
134
- // String permutations (romanS)
135
-
136
- init ? ( romanShPrF number: String ) {
137
- self = 0
138
- var raw = number
139
- for (numeral, value) in romanTable {
140
- while raw. hasPrefix ( numeral) {
141
- self += Self ( value)
142
- raw. removeFirst ( numeral. count)
143
- }
144
- }
145
- guard raw. isEmpty else { return nil }
146
- }
147
-
148
- init ? ( romanShPdF number: String ) {
149
- self = 0
150
- var raw = number
151
- for (numeral, value) in romanTable {
152
- while raw. hasPrefix ( numeral) {
153
- self += Self ( value)
154
- raw = String ( raw. dropFirst ( numeral. count) )
155
- }
156
- }
157
- guard raw. isEmpty else { return nil }
158
- }
159
-
160
- init ? ( romanSsWdF number: String ) {
161
- self = 0
162
- var raw = number
163
- for (numeral, value) in romanTable {
164
- while raw. starts ( with: numeral) {
165
- self += Self ( value)
166
- raw = String ( raw. dropFirst ( numeral. count) )
167
- }
168
- }
169
- guard raw. isEmpty else { return nil }
170
- }
171
-
172
- init ? ( romanSsWrF number: String ) {
173
- self = 0
174
- var raw = number
175
- for (numeral, value) in romanTable {
176
- while raw. starts ( with: numeral) {
177
- self += Self ( value)
178
- raw. removeFirst ( numeral. count)
179
- }
180
- }
181
- guard raw. isEmpty else { return nil }
182
- }
183
-
184
- // Substring permutations (romanSS)
185
-
186
- init ? ( romanSShPrF number: String ) {
187
- self = 0
188
- var raw = Substring ( number)
189
- for (numeral, value) in romanTable {
190
- while raw. hasPrefix ( numeral) {
191
- self += Self ( value)
192
- raw. removeFirst ( numeral. count)
193
- }
194
- }
195
- guard raw. isEmpty else { return nil }
196
- }
197
-
198
- init ? ( romanSShPdF number: String ) {
199
- self = 0
200
- var raw = Substring ( number)
201
- for (numeral, value) in romanTable {
202
- while raw. hasPrefix ( numeral) {
203
- self += Self ( value)
204
- raw = raw. dropFirst ( numeral. count)
205
- }
206
- }
207
- guard raw. isEmpty else { return nil }
208
- }
209
-
210
63
init ? ( romanSSsWdF number: String ) {
211
64
self = 0
212
65
var raw = Substring ( number)
@@ -218,133 +71,4 @@ extension BinaryInteger {
218
71
}
219
72
guard raw. isEmpty else { return nil }
220
73
}
221
-
222
- init ? ( romanSSsWrF number: String ) {
223
- self = 0
224
- var raw = Substring ( number)
225
- for (numeral, value) in romanTable {
226
- while raw. starts ( with: numeral) {
227
- self += Self ( value)
228
- raw. removeFirst ( numeral. count)
229
- }
230
- }
231
- guard raw. isEmpty else { return nil }
232
- }
233
-
234
- // UTF8View SubSequence
235
-
236
- init ? ( romanU8SSsWdF number: String ) {
237
- self = 0
238
- var raw = number. utf8 [ ... ]
239
- for (numeral, value) in romanTableUTF8 {
240
- while raw. starts ( with: numeral) {
241
- self += Self ( value)
242
- raw = raw. dropFirst ( numeral. count)
243
- }
244
- }
245
- guard raw. isEmpty else { return nil }
246
- }
247
-
248
- init ? ( romanU8SSsWrF number: String ) {
249
- self = 0
250
- var raw = number. utf8 [ ... ]
251
- for (numeral, value) in romanTableUTF8 {
252
- while raw. starts ( with: numeral) {
253
- self += Self ( value)
254
- raw. removeFirst ( numeral. count)
255
- }
256
- }
257
- guard raw. isEmpty else { return nil }
258
- }
259
- }
260
-
261
- extension BinaryInteger {
262
- // FP-style
263
- // Following is a translation of the imperative algorithm into functional
264
- // style: for-in loop is replaced with reduction and while loop with recusion.
265
- // XXX: These functions are not tail call optimized... 🤷♂️ (ARC on Strings?)
266
-
267
- typealias State = ( number: String , value: Self )
268
- typealias Roman = ( numeral: String , value: Int )
269
-
270
- // Classic functional style with reduce
271
-
272
- static func parseRomanNumeral( _ running: State , candidate r: Roman ) -> State {
273
- guard running. number. hasPrefix ( r. numeral) else { return running }
274
- return parseRomanNumeral ( ( String ( running. number. dropFirst ( r. numeral. count) ) ,
275
- running. value + Self( r. value) ) , candidate: r)
276
- }
277
-
278
- static func buildRomanNumeral( _ running: State , candidate r: Roman ) -> State {
279
- guard running. value >= r. value else { return running }
280
- return buildRomanNumeral (
281
- ( running. number + r. numeral, running. value - Self( r. value) ) , candidate: r)
282
- }
283
-
284
- var romanReduce : String {
285
- return romanTable. reduce ( ( " " , self ) , Self . buildRomanNumeral) . number
286
- }
287
-
288
- init ? ( romanReduce number: String ) {
289
- let ( remainder, value) = romanTable. reduce (
290
- ( number, Self ( 0 ) ) , Self . parseRomanNumeral)
291
- guard remainder. isEmpty else { return nil }
292
- self = value
293
- }
294
-
295
- // Swifty mutable hybrid functional style with reduce(into:)
296
-
297
- static func parseRomanNumeral( _ running: inout State , candidate r: Roman ) {
298
- guard running. number. hasPrefix ( r. numeral) else { return }
299
- running. number = String ( running. number. dropFirst ( r. numeral. count) )
300
- running. value += Self ( r. value)
301
- parseRomanNumeral ( & running, candidate: r)
302
- }
303
-
304
- static func buildRomanNumeral( _ running: inout State , candidate r: Roman ) {
305
- guard running. value >= r. value else { return }
306
- running. value -= Self ( r. value)
307
- running. number += r. numeral
308
- buildRomanNumeral ( & running, candidate: r)
309
- }
310
-
311
- var romanReduceInto : String {
312
- return romanTable. reduce ( into: ( " " , self ) , Self . buildRomanNumeral) . number
313
- }
314
-
315
- init ? ( romanReduceInto number: String ) {
316
- let ( remainder, value) = romanTable. reduce ( into:
317
- ( number, Self ( 0 ) ) , Self . parseRomanNumeral)
318
- guard remainder. isEmpty else { return nil }
319
- self = value
320
- }
321
- }
322
-
323
- // Parsing with Dictionary and map reduce.
324
- // See `fromRoman2` https://www.rosettacode.org/wiki/Roman_numerals/Decode#Scala
325
-
326
- let romanDigits : Dictionary < Character , Int > = [
327
- " I " : 1 , " V " : 5 , " X " : 10 , " L " : 50 , " C " : 100 , " D " : 500 , " M " : 1000
328
- ]
329
-
330
- extension BinaryInteger {
331
- typealias RunningSum = ( sum: Self , last: Self )
332
-
333
- static func sumRomanDigits( r: RunningSum ? , digitValue: Self ? ) -> RunningSum ? {
334
- switch ( r, digitValue) {
335
- case let ( r? , value? ) :
336
- return ( r. sum + value - ( r. last < value ? 2 * r. last : 0 ) , value)
337
- default :
338
- return nil
339
- }
340
- }
341
-
342
- init ? ( romanMapReduce number: String ) {
343
- guard let r = ( number
344
- . lazy // brings about 2x improvement over eager
345
- . map { romanDigits [ $0] . map { Self ( $0) } }
346
- . reduce ( ( Self ( 0 ) , Self ( 0 ) ) , Self . sumRomanDigits)
347
- ) else { return nil }
348
- self = r. sum
349
- }
350
74
}
0 commit comments