代码之家  ›  专栏  ›  技术社区  ›  ifo20

构建功能的大地图?(f)

f#
  •  4
  • ifo20  · 技术社区  · 8 年前

    我正在尝试构建一个f控制台应用程序,它调用一个库来解决数学/编程问题的集合。

    我的方法涉及构建一个求解函数的大映射(特别是 Map<int, (unit -> int)> )

    然后,应用程序只需请求一个整数ID,并调用相应的解算器来显示解决方案(解决方案必须运行code+运行计时器,因此不能只存储字符串)。

    我的初始设置如下所示。不过,我知道这会很快变得混乱,因为我添加了更多的解决方案-在100个解决方案之后,我会有1000行(而且一次将它们添加到地图中一行似乎很愚蠢)。

    我正在考虑的一种方法是为每个问题创建一个.fs文件,并调用模块,例如 Problem001 . 然后我将在地图构建功能中有大约100条线(例如 Map.Add(1, Problem001.solver) )

    我的问题是:上面的想法是最好的方法吗(如果是的话,是否有一种更干净的方法将所有不同的模块整合到一个地图中?)

    如果不是,最好的方法是什么?

    类库: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()
    
    1 回复  |  直到 8 年前
        1
  •  7
  •   Aaron M. Eshbach    8 年前

    我建议创建一个包含问题标题、描述和解决方案的类型。然后,我将使用标准命名约定创建一个或多个模块,其中包含返回每个问题的解决方案的函数,例如 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