Math.swift 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Argument Parser open source project
  4. //
  5. // Copyright (c) 2020 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See https://swift.org/LICENSE.txt for license information
  9. //
  10. //===----------------------------------------------------------------------===//
  11. import ArgumentParser
  12. @main
  13. struct Math: ParsableCommand {
  14. // Customize your command's help and subcommands by implementing the
  15. // `configuration` property.
  16. static let configuration = CommandConfiguration(
  17. // Optional abstracts and discussions are used for help output.
  18. abstract: "A utility for performing maths.",
  19. // Commands can define a version for automatic '--version' support.
  20. version: "1.0.0",
  21. // Pass an array to `subcommands` to set up a nested tree of subcommands.
  22. // With language support for type-level introspection, this could be
  23. // provided by automatically finding nested `ParsableCommand` types.
  24. subcommands: [Add.self, Multiply.self, Statistics.self],
  25. // A default subcommand, when provided, is automatically selected if a
  26. // subcommand is not given on the command line.
  27. defaultSubcommand: Add.self)
  28. }
  29. struct Options: ParsableArguments {
  30. @Flag(
  31. name: [.customLong("hex-output"), .customShort("x")],
  32. help: "Use hexadecimal notation for the result.")
  33. var hexadecimalOutput = false
  34. @Argument(
  35. help: "A group of integers to operate on.")
  36. var values: [Int] = []
  37. }
  38. extension Math {
  39. static func format(_ result: Int, usingHex: Bool) -> String {
  40. usingHex
  41. ? String(result, radix: 16)
  42. : String(result)
  43. }
  44. struct Add: ParsableCommand {
  45. static let configuration =
  46. CommandConfiguration(abstract: "Print the sum of the values.")
  47. // The `@OptionGroup` attribute includes the flags, options, and
  48. // arguments defined by another `ParsableArguments` type.
  49. @OptionGroup var options: Options
  50. mutating func run() {
  51. let result = options.values.reduce(0, +)
  52. print(format(result, usingHex: options.hexadecimalOutput))
  53. }
  54. }
  55. struct Multiply: ParsableCommand {
  56. static let configuration = CommandConfiguration(
  57. abstract: "Print the product of the values.",
  58. aliases: ["mul"])
  59. @OptionGroup var options: Options
  60. mutating func run() {
  61. let result = options.values.reduce(1, *)
  62. print(format(result, usingHex: options.hexadecimalOutput))
  63. }
  64. }
  65. }
  66. // In practice, these nested types could be broken out into different files.
  67. extension Math {
  68. struct Statistics: ParsableCommand {
  69. static let configuration = CommandConfiguration(
  70. // Command names are automatically generated from the type name
  71. // by default; you can specify an override here.
  72. commandName: "stats",
  73. abstract: "Calculate descriptive statistics.",
  74. subcommands: [Average.self, StandardDeviation.self, Quantiles.self])
  75. }
  76. }
  77. extension Math.Statistics {
  78. struct Average: ParsableCommand {
  79. static let configuration = CommandConfiguration(
  80. abstract: "Print the average of the values.",
  81. version: "1.5.0-alpha",
  82. aliases: ["avg"])
  83. enum Kind: String, ExpressibleByArgument, CaseIterable {
  84. case mean, median, mode
  85. }
  86. @Option(help: "The kind of average to provide.")
  87. var kind: Kind = .mean
  88. @Argument(help: "A group of floating-point values to operate on.")
  89. var values: [Double] = []
  90. func validate() throws {
  91. if (kind == .median || kind == .mode) && values.isEmpty {
  92. throw ValidationError(
  93. "Please provide at least one value to calculate the \(kind).")
  94. }
  95. }
  96. func calculateMean() -> Double {
  97. guard !values.isEmpty else {
  98. return 0
  99. }
  100. let sum = values.reduce(0, +)
  101. return sum / Double(values.count)
  102. }
  103. func calculateMedian() -> Double {
  104. guard !values.isEmpty else {
  105. return 0
  106. }
  107. let sorted = values.sorted()
  108. let mid = sorted.count / 2
  109. if sorted.count.isMultiple(of: 2) {
  110. return (sorted[mid - 1] + sorted[mid]) / 2
  111. } else {
  112. return sorted[mid]
  113. }
  114. }
  115. func calculateMode() -> [Double] {
  116. guard !values.isEmpty else {
  117. return []
  118. }
  119. let grouped = Dictionary(grouping: values, by: { $0 })
  120. let highestFrequency = grouped.lazy.map { $0.value.count }.max() ?? 0
  121. return grouped.filter { _, v in v.count == highestFrequency }
  122. .map { k, _ in k }
  123. }
  124. mutating func run() {
  125. switch kind {
  126. case .mean:
  127. print(calculateMean())
  128. case .median:
  129. print(calculateMedian())
  130. case .mode:
  131. let result = calculateMode()
  132. .map(String.init(describing:))
  133. .joined(separator: " ")
  134. print(result)
  135. }
  136. }
  137. }
  138. struct StandardDeviation: ParsableCommand {
  139. static let configuration = CommandConfiguration(
  140. commandName: "stdev",
  141. abstract: "Print the standard deviation of the values.")
  142. @Argument(help: "A group of floating-point values to operate on.")
  143. var values: [Double] = []
  144. mutating func run() {
  145. if values.isEmpty {
  146. print(0.0)
  147. } else {
  148. let sum = values.reduce(0, +)
  149. let mean = sum / Double(values.count)
  150. let squaredErrors =
  151. values
  152. .map { $0 - mean }
  153. .map { $0 * $0 }
  154. let variance = squaredErrors.reduce(0, +) / Double(values.count)
  155. let result = variance.squareRoot()
  156. print(result)
  157. }
  158. }
  159. }
  160. struct Quantiles: ParsableCommand {
  161. static let configuration = CommandConfiguration(
  162. abstract: "Print the quantiles of the values (TBD).")
  163. @Argument(
  164. completion: .list(["alphabet", "alligator", "branch", "braggart"]))
  165. var oneOfFour: String?
  166. @Argument(
  167. completion: .custom { _, _, _ in
  168. ["alabaster", "breakfast", "crunch", "crash"]
  169. }
  170. )
  171. var customArg: String?
  172. @available(
  173. *, deprecated,
  174. message: "Deprecated use of custom completion for @Argument"
  175. )
  176. @Argument(
  177. completion: .custom { _ in ["alabaster", "breakfast", "crunch", "crash"] }
  178. )
  179. var customDeprecatedArg: String?
  180. @Argument(help: "A group of floating-point values to operate on.")
  181. var values: [Double] = []
  182. // These args and the validation method are for testing exit codes:
  183. @Flag(help: .hidden)
  184. var testSuccessExitCode = false
  185. @Flag(help: .hidden)
  186. var testFailureExitCode = false
  187. @Flag(help: .hidden)
  188. var testValidationExitCode = false
  189. @Option(help: .hidden)
  190. var testCustomExitCode: Int32?
  191. // These args are for testing custom completion scripts:
  192. @Option(completion: .file(extensions: ["txt", "md"]))
  193. var file: String?
  194. @Option(completion: .directory)
  195. var directory: String?
  196. @Option(
  197. completion: .shellCommand("head -100 '/usr/share/dict/words' | tail -50")
  198. )
  199. var shell: String?
  200. @Option(completion: .custom(customCompletion))
  201. var custom: String?
  202. @available(
  203. *, deprecated, message: "Deprecated use of custom completion for @Option"
  204. )
  205. @Option(completion: .custom(customDeprecatedCompletion))
  206. var customDeprecated: String?
  207. func validate() throws {
  208. if testSuccessExitCode {
  209. throw ExitCode.success
  210. }
  211. if testFailureExitCode {
  212. throw ExitCode.failure
  213. }
  214. if testValidationExitCode {
  215. throw ExitCode.validationFailure
  216. }
  217. if let exitCode = testCustomExitCode {
  218. throw ExitCode(exitCode)
  219. }
  220. }
  221. }
  222. }
  223. func customCompletion(_ s: [String], _: Int, _: String) -> [String] {
  224. (s.last ?? "").starts(with: "a")
  225. ? ["aardvark", "aaaaalbert"]
  226. : ["hello", "helicopter", "heliotrope"]
  227. }
  228. func customDeprecatedCompletion(_ s: [String]) -> [String] {
  229. (s.last ?? "").starts(with: "a")
  230. ? ["aardvark", "aaaaalbert"]
  231. : ["hello", "helicopter", "heliotrope"]
  232. }