Я бы предложил создать тип, содержащий заголовок, описание и решение проблемы. Затем я бы создал один или несколько модулей, содержащих функции, которые возвращают решения для каждой проблемы, используя стандартное соглашение об именах, например problemN
, где N
- это problemId
. С этим определением я просто использовал бы отражение, чтобы найти функцию, которая возвращает решение для данной проблемы, и вызвать ее:
open System.Reflection
type Problem =
{
Title: string
Description: string
Solution: int // This could even be a function, int -> int or whatever
}
module Solutions =
let problem1 () =
{ Title = "#1"
Description = "The first problem"
Solution = 42
}
let printSolution problemId =
match Assembly.GetExecutingAssembly().GetTypes() |> Array.tryFind (fun t -> t.Name = "Solutions") with
| Some solutions ->
match solutions.GetMethod(sprintf "problem%d" problemId) with
| null ->
printfn "Solution to Problem %d not found" problemId
| func ->
let problem = func.Invoke(null, [||]) |> unbox<Problem>
printfn "Problem %d: %s" problemId problem.Title
printfn " %s" problem.Description
printfn " Solution = %d" problem.Solution
| None -> printfn "Solutions module not found"
Вы можете вернуть экземпляр Problem
вместо его печати в вашей реальной библиотеке, но, как определено, вы бы назвали его так:
printSolution 1
И это напечатало бы следующее:
Problem 1: #1
The first problem
Solution = 42
EDIT
Сочетание ответа на вопрос ifo20 в комментариях с отличным предложением cadull об использовании пользовательских атрибутов представляет собой более гибкое решение, которое позволит определять решения во многих различных модулях / файлах и не полагаться на соглашение об именах. чтобы найти их.
open System
open System.Reflection
type Problem =
{
Title: string
Description: string
Solution: int // This could even be a function, int -> int or whatever
}
[<AllowNullLiteral>]
type SolutionModuleAttribute () =
inherit Attribute()
[<AllowNullLiteral>]
type SolutionAttribute (problemId: int) =
inherit Attribute()
member __.ProblemId = problemId
[<SolutionModule>]
module SomeSolutions =
[<Solution(1)>]
let firstProblem () =
{ Title = "#1"
Description = "The first problem"
Solution = 42
}
[<SolutionModule>]
module MoreSolutions =
[<Solution(2)>]
let secondProblem () =
{ Title = "#2"
Description = "The second problem"
Solution = 17
}
let findSolutions () =
Assembly.GetExecutingAssembly().GetTypes()
|> Array.filter (fun t -> t.GetCustomAttribute<SolutionModuleAttribute>() |> isNull |> not)
|> Array.collect (fun t -> t.GetMethods())
|> Array.choose (fun m ->
match m.GetCustomAttribute<SolutionAttribute>() with
| null -> None
| attribute -> Some (attribute.ProblemId, fun () -> m.Invoke(null, [||]) |> unbox<Problem>))
|> Map.ofArray
let printSolution =
let solutions = findSolutions()
fun problemId ->
match solutions |> Map.tryFind problemId with
| Some func ->
let problem = func()
printfn "Problem %d: %s" problemId problem.Title
printfn " %s" problem.Description
printfn " Solution = %d" problem.Solution
| None ->
printfn "Solution for Problem %d not found" problemId
Самым большим изменением, кроме использования атрибутов для идентификации решений и модулей, которые их содержат, является рефакторинг логики поиска в ее собственную функцию. Теперь это возвращает Map<int, (unit -> Problem)>
, поэтому вам нужно только просмотреть сборку и найти решения по их атрибутам один раз, затем вы можете использовать карту для поиска решений для каждой проблемы.
Использование и вывод функции printSolution
остаются прежними:
printSolution 2
Problem 2: #2
The second problem
Solution = 17