Julia:压平数组/元组的数组

33

在Julia中,vec函数可以将多维数组转换为一维数组。 但对于数组的数组或数组的元组它不起作用。 除了使用数组推导之外,还有其他方法可以展平数组的数组/元组吗?或者是数组的数组/元组的数组/元组吗?或者……

5个回答

45

Iterators.flatten(x) 创建一个生成器,可以迭代遍历 x 的每个元素。它可以处理一些你所描述的情况,例如:

Iterators.flatten(x) 创建一个生成器,可以迭代遍历 x 的每个元素。它可以处理一些你所描述的情况,例如:

julia> collect(Iterators.flatten([(1,2,3),[4,5],6]))
6-element Array{Any,1}:
 1
 2
 3
 4
 5
 6

如果你有嵌套了多层数组和元组的数据结构,那么你应该重新考虑你的数据结构,因为它听起来不稳定。但是,你可以使用多个调用 flatten 来处理,例如:

julia> collect(Iterators.flatten([(1,2,[3,3,3,3]),[4,5],6]))
6-element Array{Any,1}:
 1            
 2            
  [3, 3, 3, 3]
 4            
 5            
 6            

julia> collect(Iterators.flatten(Iterators.flatten([(1,2,[3,3,3,3]),[4,5],6])))
9-element Array{Any,1}:
 1
 2
 3
 3
 3
 3
 4
 5
 6

请注意,我的所有示例都返回一个Array{Any,1}。这对性能来说是一个不好的迹象,因为它意味着编译器无法为输出数组的元素确定单个具体类型。我选择这些示例是因为我读到你的问题时觉得你可能已经有了类型不稳定的容器。


这正是我一直在寻找的!在我的情况下,只涉及到数组,没有元组,所以这不是我的问题。然而,在添加了“Iterators”包并调用“using Iterators”之后,当我尝试在REPL中使用flatten时,它告诉我该函数不存在。我正在使用Julia 0.6.1版本。 - Pigna
9
现在的Iterators已经内置在基础库中了,旧的Iterators包已经不推荐使用。如果你现在安装它,我不知道会发生什么,但如果有任何问题就卸载它。你需要使用using Base.Iterators来获取导出的方法(包括flatten),或者使用import Base.Iterators: flatten来仅获取一个方法。 - gggg
第一个示例在当前版本的Julia中是类型稳定的(至少从v1.6开始)。它返回一个6元素Vector{Int64} - rashid

22

要将一个数组的数组展平,可以简单地使用vcat()函数,如下所示:

julia> A = [[1,2,3],[4,5], [6,7]]
Vector{Int64}[3]
    Int64[3]
    Int64[2]
    Int64[2]
julia> flat = vcat(A...)
Int64[7]
    1
    2
    3
    4
    5
    6
    7

2
似乎不适用于数组的数组的数组。 - Timmmm
如果您的数组没有任意嵌套,则这似乎是性能最佳的选项。 - schneiderfelipe

17

最简单的方法是两次应用省略号...

A = [[1,2,3],[4,5], [6,7]]
flat = [(A...)...]
println(flat)

输出结果将会是[1, 2, 3, 4, 5, 6, 7]


1
这是非常棒且紧凑的语法,但是我仍然无论如何都不能理解这个星号操作符如何工作-您能否详细说明一下该表达式的计算过程呢? - Hansang
展开运算符用于函数调用中,将一个参数拆分为多个参数[Julialang FAQ]https://docs.julialang.org/en/v1/manual/faq/#The-two-uses-of-the-...-operator:-slurping-and-splatting。在这里,它被用在数组文字[ ]中,也可以工作。如果该运算符只使用一次,它将把展开的数组元素合并回一个数组中,实际上什么也不做。有了方括号,展开操作会被评估两次:它将展开的数组再次拆分成一个数组。解包、解包、打包。 - Ricoter
这段话很简略,但我提醒你注意星号操作符可能会导致一些非常意外的减速。试试 x = [rand(n) for n in rand(1:20, 100000)]。比较一下 @btime [(x...)...](21.215毫秒和1049049次分配)和@btime vcat(x...)(2.836毫秒和3次分配)的区别。 - MentatOfDune

8
如果您使用RecursiveArrayTools.jl中的VectorOfArray,它会使用索引回退来为VectorOfArray A提供convert(Array,A)。请注意保留HTML标记。
julia> using RecursiveArrayTools

julia> A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
3-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [4, 5, 6]
 [7, 8, 9]

julia> VA = VectorOfArray(A)
3-element Array{Array{Int64,1},1}:
 [1, 2, 3]
 [4, 5, 6]
 [7, 8, 9]

首先,它作为一个懒惰的包装器来执行索引而不进行转换:

julia> VA[1,3]
7

请注意,列是单独的数组,这样仍然是“列主要”(即按列索引高效)。但是它有一个直接的转换:
julia> convert(Array,VA)
3×3 Array{Int64,2}:
 1  4  7
 2  5  8
 3  6  9

另一种处理此转换的方法是执行类似于hcat(A...)的操作,但如果您要拆分很多数组,则速度会很慢!现在,您可能会想:编写一个预先分配矩阵的函数,然后遍历并填充它怎么样?这几乎就是VectorOfArray上的convert所做的,除了convert在此处使用的回退利用了Tim Holy的Cartesian机制。曾经,我写过那个函数:
function vecvec_to_mat(vecvec)
  mat = Matrix{eltype(eltype(vecvec))}(length(vecvec),length(vecvec[1]))
  for i in 1:length(vecvec)
    mat[i,:] .= vecvec[i]
  end
  mat
end

但是我后来放弃了它,因为备选方案更快。因此,YMMV,但这是解决你问题的几种方式。


1

针对Julia 0.7x:

针对数组:

flat(arr::Array) = mapreduce(x -> isa(x, Array) ? flat(x) : x, append!, arr,init=[])

针对元组:

flat(arr::Tuple) = mapreduce(x -> isa(x, Tuple) ? flat(x) : x, append!, arr,init=[])

适用于任意深度。 请参见:https://rosettacode.org/wiki/Flatten_a_list#Julia 数组/元组的代码:

function flatten(arr)
    rst = Any[]
    grep(v) =   for x in v
                if isa(x, Tuple) ||  isa(x, Array)
                grep(x) 
                else push!(rst, x) end
                end
    grep(arr)
    rst
end

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