【问题标题】:How to obtain deep copies of Julia composite types?如何获得 Julia 复合类型的深层副本?
【发布时间】:2015-06-17 14:56:20
【问题描述】:

所以这里是设置。我有多个复合类型定义了它们自己的字段和构造函数。让我们在这里展示两个简化的组件:

type component1
    x
    y
end

type component2
    x
    y
    z
end

现在我想定义一个新类型,以便它可以在其中保存一个大小为 K 的先前定义的复合类型的数组。所以它是一个参数复合类型,有两个字段:一个是整数K,另一个是传递类型的大小为K的数组。

type mixture{T}
    components::Array{T, 1}
    K::Int64

    function mixture(qq::T, K::Int64)
        components = Array{typeof(qq), K}
        for k in 1:K
            components[k] = qq
        end
        new(components, K)
    end
end

但这不是正确的做法。因为所有 K 个组件都指向一个对象,并且操作 mix.components[k] 将影响所有 K 个组件。在 python 中,我可以用 deepcopy 解决这个问题。但是 Julia 中的 deepcopy 没有为复合类型定义。我该如何解决这个问题?

【问题讨论】:

    标签: julia


    【解决方案1】:

    对您的具体问题的回答:

    当您在 Julia 中定义新类型时,通常会将 Base 中的一些标准方法扩展到您的新类型,包括 deepcopy。例如:

    type MyType
        x::Vector
        y::Vector
    end
    import Base.deepcopy
    Base.deepcopy(m::MyType) = MyType(deepcopy(m.x), deepcopy(m.y))
    

    现在您可以通过MyType 的实例调用deepcopy,您将获得一个新的、真正独立的MyType 副本作为输出。

    注意,我的import Base.deepcopy 实际上是多余的,因为我在函数定义中引用了Base,例如Base.deepcopy(m::MyType)。但是,我做了这两件事来向您展示从 Base 扩展方法的两种方式。

    第二个注意,如果你的类型有很多字段,你可以改为使用deepcopy 遍历字段,如下所示:

    Base.deepcopy(m::MyType) = MyType([ deepcopy(getfield(m, k)) for k = 1:length(names(m)) ]...)
    

    对您的代码的评论:

    首先,Julia 的标准做法是大写类型名称,例如Component1 而不是 component1。当然,您不必这样做,但是...

    第二,来自Julia docs performance tips:为复合类型的字段声明特定类型。请注意,您可以参数化这些声明,例如

    type Component1{T1, T2}
        x::T1
        y::T2
    end
    

    第三,我会这样定义你的新类型:

    type Mixture{T}
        components::Vector{T}
        Mixture{T}(c::Vector{T}) = new(c)
    end
    Mixture{T}(c::Vector{T}) = Mixture{eltype(c)}(c)
    Mixture(x, K::Int) = Mixture([ deepcopy(x) for k = 1:K ])
    

    我的代码和你的代码有几个重要的区别。我会一次过一个。

    您的K 字段是多余的(我认为),因为它似乎只是components 的长度。因此,将length 方法扩展到您的新类型可能会更简单,如下所示:

    Base.length(m::Mixture) = length(m.components)
    

    现在您可以使用length(m),其中mMixture 的一个实例,以获取之前存储在K 字段中的内容。

    您的类型mixture 中的内部构造函数异常。标准做法是内部构造函数采用与您类型的字段一对一(按顺序)对应的参数,然后内部构造函数的其余部分只执行您希望在初始化时执行的任何错误检查类型。您偏离了这一点,因为 qq 不是(必然)一个数组。这种行为更好地保留给外部构造函数。那么,我对构造函数做了什么?

    Mixture 的内部构造函数除了通过new 将参数传递到字段中之外并没有真正做任何事情。这是因为目前我不需要执行任何错误检查(但我将来可以轻松添加一些)。

    如果我想调用这个内部构造函数,我需要写类似m = Mixture{MyType}(x),其中xVector{MyType}。这有点烦人。所以我的第一个外部构造函数使用eltype(x) 自动推断{...} 的内容。由于我的第一个外部构造函数,我现在可以使用m = Mixture(x) 而不是m = Mixture{MyType}(x) 来初始化Mixture

    我的第二个外部构造函数对应于你的内部构造函数。在我看来,您在这里的行为是在components 的每个字段中使用相同的组件初始化Mixture,重复K 次。所以我通过对x 的循环理解来做到这一点,只要为x 定义了deepcopy 方法,它就可以工作。如果不存在deepcopy 方法,您将收到No Method Exists 错误。这种编程称为鸭式编程,在 Julia 中使用它通常不会降低性能。

    注意,我的第二个外部构造函数将调用我的第一个外部构造函数K 次,并且每次,我的第一个外部构造函数都会调用我的内部构造函数。在更复杂的场景中,以这种方式嵌套功能将大大减少代码重复。

    抱歉,我知道这有很多需要注意的地方。希望对您有所帮助。

    【讨论】:

    • 感谢您的完整回复。我熟悉多次调度的概念,但我不确定最好的方法是否是修改 Base.deepcopy。也感谢cmets。关于第三条评论和避免在构造函数中声明类型的需要,虽然我设法使用了你的方法但我不太明白!
    • @Adham 我已经更新了我的答案。如果仍然不清楚,请告诉我,我会再试一次:-)
    • 谢谢。现在更清楚了(对于我作为 Julia 的初学者来说:D)
    猜你喜欢
    • 2016-06-21
    • 2011-10-25
    • 2020-11-06
    • 1970-01-01
    • 2015-12-28
    • 1970-01-01
    相关资源
    最近更新 更多