我正在尝试构建一个F#控制台应用程序,该应用程序调用一个库来解决一系列数学/编程问题。
我的方法涉及构建大型求解程序功能图(特别是Map<int, (unit -> int)>
)。
然后,应用程序简单地要求输入整数id并调用相应的求解器以显示解决方案(解决方案必须运行代码+运行计时器,因此不能仅存储字符串)。
我的初始设置如下所示。但是,我知道,随着我添加更多解决方案,这很快就会变得一团糟-在100个解决方案之后,我将拥有>> 1,000行(并且一次将它们一次添加到Map中似乎很可笑)。
我正在考虑的一种方法是为每个问题创建一个.fs文件,然后调用该模块,例如Problem001
。
然后,我将在Map构建功能中使用约100条线(例如Map.Add(1, Problem001.solver)
)。
我的问题是:上面的想法是最好的方法吗?(如果这样,是否有一种将所有不同模块整合到一个Map中的更简洁的方法?)
如果不是,最好的方法是什么?
类库:Library.fs
namespace Library
module Problems =
let Titles =
Map.empty
.Add(1, "Title1") // etc
let Descriptions =
Map.empty
.Add(1, "Desc1") // etc
module Solutions =
let solution1 () =
// logic & solution annotation, unique to each problem
printfn "Solution annotation/text"
ans // return integer answer
let solution2 () = // etc
printfn "blah"
ans
let solvers =
Map.empty
.Add(1, solution1)
.Add(2, solution2)
控制台应用程序:Program.fs
let main argv =
// request & validate input
printfn Problems.Descriptions.[problemId]
let solver = Solutions.solvers.[problemId]
solver()
答案 0 :(得分:7)
我建议创建一种类型来包含标题,说明和解决方案问题。然后,我将使用标准命名约定(例如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
编辑
将评论中对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