使用 Julia 的元编程解决罗马数字

问题描述 投票:0回答:1

我用常规的 Julia 代码解决了这个练习,并不太难:

function to_roman(number::Int64)
    0 < number < 4000 || error("not in range")

    parts = Char[]
    while number > 0
        if number >= 1000
            push!(parts, 'M')
            number -= 1000
        elseif number >= 500
            push!(parts, 'D')
            number -= 500
        # ...
            number -= 10
        elseif number >= 5
            push!(parts, 'V')
            number -= 5
        elseif number >= 1
            push!(parts, 'I')
            number -= 1
        end
    end
    join(parts)
end

这只能解决简单的数字问题,但可以使示例更短。现在我就是我,我对这种冗余感到震惊。所以我进行了迭代 2:

const values = Dict(
    'M' => 1000,
    'C' => 100,
    'X' => 10,
    'I' => 1,
    'D' => 500,
    'L' => 50,
    'V' => 5,
)
Base.:+(number::Integer, c::Char) = number + values[c]
Base.:-(number::Integer, c::Char) = number - values[c]

function to_roman(number::Int64)
    0 < number < 4000 || error("must be between 1 and 3999 inclusive")

    parts = Char[]
    while number > 0
        for digit in "MDCLXVI"
            if number >= values[digit]
                push!(parts, digit)
                number -= digit
                break
            end
        end
    end
    join(parts)
end

这也是如此,但让我思考。我不能用元编程创建上面多余的

if
吗?无论如何,我想对此进行一些学习。

但我什至无法靠近:

macro gen(number)
    code = quote
        parts = Char[]
        while $(esc(number)) > 0
        end
    end
    for (i,c) in enumerate("MDCLXVI")
        exp = :(
            if $(esc(number)) >= $(values[c])
                push!(parts, $c)
                $(esc(number)) -= $(values[c])
            end
        )
        if i > 1
            exp.head = :elseif
        end
        # This is not correct anymore because I introduced the while.
        push!(code.args, exp)
    end
    push!(code.args, :(return parts))
    dump(code)
    return code
end

function test(number)
    @gen number
end

test(1111)

这是我能做的最好的事情,但后来我注意到我需要有

while
(所以这个代码不起作用)。现在我什至不知道要附加到哪个
.args
,因为那里还有几个
LineNumberNode
节点可以移动重要的节点。

我有一种感觉,我没有从正确的角度处理这个问题/我使用了正确的工具。

julia metaprogramming
1个回答
0
投票

休息一下,突然间,事情就开始起作用了。

我的问题是,我正在考虑一种自上而下的方法,手动改变

.args
Expr
并从示例中计算我需要哪些索引等等......太手动和复杂了。

回过头来,我意识到我需要自下而上,从

I
案例作为一个完整的 if 开始,然后始终将我迄今为止所拥有的内容插入到更新、更大的
if
else
中。

最后,将其全部打包在一个

while
中并处理好数组,完成。

macro generate_ifs(number)
    values = Dict(
        'M' => 1000,
        'C' => 100,
        'X' => 10,
        'I' => 1,
        'D' => 500,
        'L' => 50,
        'V' => 5,
    )
    code = :()
    for c in "IVXLCDM"
        code = :(
            if $(esc(number)) >= $(values[c])
                push!(parts, $c)
                $(esc(number)) -= $(values[c])
            else
                $code
            end
        )
    end
    return quote
        parts = Char[]
        while $(esc(number)) > 0
            $code
        end
        return join(parts)
    end
end

function to_roman(number::Int64)
    0 < number < 4000 || error("must be between 1 and 3999 inclusive")
    @generate_ifs number
end

无论如何,这就是我想出的解决方案,我也能够解出正确的数字。

我仍然对评论和想法感兴趣。

© www.soinside.com 2019 - 2024. All rights reserved.