如何获取Julia复合类型的深复制?

9

以下是需要翻译的内容:

这里是场景设置。我定义了多个复合类型,它们都有自己的字段和构造函数。下面展示两个简化的组件:

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组件都指向同一个对象,操作mixture.components[k]将影响所有K组件。在Python中,我可以通过深拷贝来解决这个问题。但是,在Julia中,复合类型的深拷贝未定义。我该如何解决这个问题?

1个回答

13

针对您的具体问题作出回答:

在Julia中定义新类型时,通常会扩展一些标准方法到您的新类型中,包括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文档性能提示中指出:为组合类型的字段声明特定类型。请注意,您可以对这些声明进行参数化,例如:

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
我的第二个外部构造函数对应于您的内部构造函数。我认为您在此处追求的行为是使用重复K次的相同组件初始化Mixturecomponents的每个字段。因此,我使用循环推导式对x进行操作,只要对x定义了deepcopy方法,它就可以工作。如果不存在deepcopy方法,则会出现No Method Exists错误。这种编程称为鸭子类型,在Julia中通常不会有性能损失。
请注意,我的第二个外部构造函数将调用我的第一个外部构造函数K次,每次,我的第一个外部构造函数将调用我的内部构造函数。在更复杂的情况下,嵌套功能将大大减少代码重复。
抱歉,我知道这很多。希望有所帮助。

感谢您的完整回复。我熟悉多重分派的概念,但不确定最好的方法是否是修改Base.deepcopy。 同时感谢您的评论。关于第三条评论和省略构造函数中类型声明的需求,虽然我成功使用了您的方法,但我并不完全理解它! - Adham
@Adham 我已经更新了我的答案。如果还不清楚,请告诉我,我会再试一次 :-) - Colin T Bowers
谢谢。现在对我这个初学Julia的人来说更清晰了(笑) - Adham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接