使用Discriminated Union将整数转换为罗马数字

时间:2016-02-17 14:56:53

标签: f#

我正在表演一个卡塔而且失败了:

有人可以提供有关我如何构建以下代码的指导:

将整数转换为罗马数字

type RomanNumerals = | I    // 1
                     | IV   // 4
                     | V    // 5
                     | X    // 10
                     | L    // 50
                     | C    // 100
                     | D    // 500
                     | M    // 1000

let getOccurrances integer unit symbol = 
    [for i in [1..(integer / unit)] -> symbol]

let convertInteger = function
    | v when v > 1000 -> getOccurrances v 1000 M
    | v when v > 500  -> getOccurrances v 500  D
    | v when v > 100  -> getOccurrances v 100  C
    | v when v > 50   -> getOccurrances v 50   L
    | v when v > 10   -> getOccurrances v 10   X
    | v when v > 5    -> getOccurrances v  5   V
    | v when v > 4    -> getOccurrances v  4   IV
    | v when v > 1    -> getOccurrances v  1   I
    | _ -> []



let romanNumerals = convertInteger 3001
printf "%A" romanNumerals

2 个答案:

答案 0 :(得分:3)

主要问题是你没有使用递归,所以它与3001匹配,3001大于1000,因此它只需要放3 M。但是,您希望以递归方式执行此操作,直到它降至0。

这是一个有效的例子.....确实不是很好......

type RomanNumerals = | I    // 1
                     | IV   // 4
                     | V    // 5
                     | X    // 10
                     | L    // 50
                     | C    // 100
                     | D    // 500
                     | M    // 1000

let getOccurrances integer unit symbol = 
    [for i in [1..(integer / unit)] -> symbol]

let rec convertInteger (romanList: RomanNumerals list) v = 
    match v with
    | v when v >= 1000 -> let temp = getOccurrances v 1000 M
                          convertInteger (temp|> List.append(romanList)) (v - temp.Length * 1000)
    | v when v >= 500  -> let temp = getOccurrances v 500  D
                          convertInteger (temp |> List.append(romanList)) (v - temp.Length * 500)
    | v when v >= 100  -> let temp = getOccurrances v 100  C
                          convertInteger (temp |> List.append(romanList)) (v - temp.Length * 100)
    | v when v >= 50   -> let temp = getOccurrances v 50  L
                          convertInteger (temp |> List.append(romanList)) (v - temp.Length * 50)
    | v when v >= 10   -> let temp = getOccurrances v 10  X
                          convertInteger (temp |> List.append(romanList)) (v - temp.Length * 10)
    | v when v >= 5    -> let temp = getOccurrances v 5  V
                          convertInteger (temp |> List.append(romanList)) (v - temp.Length * 5)
    | v when v >= 4    -> let temp = getOccurrances v 4  IV
                          convertInteger (temp |> List.append(romanList)) (v - temp.Length * 4)
    | v when v >= 1    -> let temp = getOccurrances v  1  I
                          convertInteger (temp|> List.append(romanList)) (v - temp.Length * 1)
    | _ -> romanList   


[<EntryPoint>]
let main argv = 
    let romanNumerals = convertInteger [] 3001
    printf "%A" romanNumerals
    //returns [M; M; M; I]
    0 // return an integer exit code

答案 1 :(得分:3)

@Ringil在评论中有一个很好的领先:使用与union案例匹配的列表及其值导致我认为更惯用的代码:

type RomanNumerals =
| I     // 1
| IV    // 4
| V     // 5
| IX    // 9
| X     // 10
| XL    // 40
| L     // 50
| XC    // 90
| C     // 100
| CD    // 400
| D     // 500
| CM    // 900
| M     // 1000
with
    // This associates the DU cases to their values
    static member values = [
        M, 1000
        CM, 900
        D, 500
        CD, 400
        C, 100
        XC, 90
        L, 50
        XL, 40
        X, 10
        IX, 9
        V, 5
        IV, 4
        I, 1
    ]

let rec convertInteger i =
    // Base case: We've reached zero, so return an empty list
    if i = 0 then
        []
    // Otherwise...
    else
        // Try to find the first symbol for which the value is <= of the remaining input
        match List.tryFind (fun (symbol, value) -> value <= i) RomanNumerals.values with
        | Some (symbol, value) ->
            // If there is one, prepend it to the result of recursion
            symbol :: convertInteger (i - value)
        | None ->
            // If there is non, we've messed up somehow so throw an exception.
            // Since we have a case for I = 1, this should never happen but we
            // still have to handle it explicitely - otherwise, the compiler complains
            // about a missing case in the match.
            failwithf "Got a non-zero integer that isn't matched by an existing numeral: %i" i

[<EntryPoint>]
let main argv =
    let input = 3001
    let romanNumerals = convertInteger input

    // Print the resulting list
    printfn "%A" romanNumerals // prints [M; M; M; I]

    // Alternatively, print each character individually:
    List.iter (printf "%A") romanNumerals // prints MMMI
    printfn ""

    0

注意,如上所述,每次对convertInteger的递归调用都必须:

  1. 检查基本情况;
  2. 调用自身并捕获返回值,然后;
  3. 前置自己的结果并返回。
  4. 因为它必须在递归调用之后才能工作,所以它将无法利用tail call optimization。解决此问题的方法是将其更改为以下代码:

    let convertInteger i =
        // Define an inner recursive function that carries the intermediate state as an argument
        let rec loop result i =
            // Base case: We've reached zero, so return the current result
            if i = 0 then
                result
            // Otherwise...
            else
                // Try to find the first symbol for which the value is <= of the remaining input
                match List.tryFind (fun (symbol, value) -> value <= i) RomanNumerals.values with
                | Some (symbol, value) ->
                    // If there is one, prepend the state to the result
                    // Since we're always prepending, we'll have to reverse
                    // the list once we're done but that's okay
                    let newResult = symbol :: result // `a :: b` means "prepend element a to list b"
                    // Note that this is a tail call, since nothing is done with
                    // the result once the function returns. This allows for tail call optimization
                    loop newResult (i - value)
                | None ->
                    // If there is non, we've messed up somehow so throw an exception.
                    // Since we have a case for I = 1, this should never happen but we
                    // still have to handle it explicitely - otherwise, the compiler complains
                    // about a missing case in the match.
                    failwithf "Got a non-zero integer that isn't matched by an existing numeral: %i" i
    
        // Then call the function with an initial state - an empty list in this case
        let result = loop [] i
        // And reverse the result, since it was built backwards
        List.rev result
    

    这就是我通常在F#中编写递归函数的方法:一个带有简单接口的非递归外部函数 - 在这种情况下只是要转换的整数 - 以及一个传递状态的内部递归函数。

    随意询问有关代码的任何问题 - 我已经尝试在我的评论中做到彻底,但我可能错过了部分内容。 =)