2017-02-20 25 views
2

В Python можно использовать оператор * при распаковке итерации.Julia splat operator распаковка

In [1]: head, *tail = [1, 2, 3, 4, 5] 

In [2]: head 
Out[2]: 1 

In [3]: tail 
Out[3]: [2, 3, 4, 5] 

Я хотел бы произвести такое же поведение в Юлии. Я понял, что оператор эквивалент ... будет работать, но, похоже, он просто создает ошибку в этом контексте.

julia> head, tail... = [1, 2, 3, 4, 5] 
ERROR: syntax: invalid assignment location "tail..." 

Мне удалось получить результаты, которые я хочу использовать, но это уродливое решение.

julia> head, tail = A[1], A[2:end] 
(1,[2,3,4,5]) 

Могу ли я распаковать массив таким образом, что tail будет содержать остальные элементы после head с помощью пейнтбольный (...) оператора? Если нет, то какая самая чистая альтернатива?


Edit: Эта функция была предложена в #2626. Похоже, что это будет часть релиза 1.0.

+0

Я не думаю, что 'head, tail = A [1], A [2: end]' уродливый, он явно сообщает, что такое 'head' и' tail'. если 'A' не имеет потенциального использования, используя' head, tail = shift! (A), A' немного более эффективен. – Gnimuc

+1

@Gnimuc Это становится все более уродливым, так как больше предметов нужно распаковывать. 'a, b, c, * d = [1,2,3,4,5]' намного чище, чем использование большого количества индексирования или 'shift!' ing, IMHO. :) –

+0

Это похоже на работу для макроса. –

ответ

2

Это делает действительно звучит как работа для макроса:

function unpack(lhs, rhs) 
    len = length(lhs.args) 
    if len == 1 
     # just remove the splatting 
     l, is_splat = remove_splat(lhs.args[1]) 
     return :($l = $(esc(rhs))) 
    else 
     new_lhs = :() 
     new_rhs = quote 
      tmp = $(esc(rhs)) 
      $(Expr(:tuple)) 
     end 
     splatted = false 
     for (i, e) in enumerate(lhs.args) 
      l, is_splat = remove_splat(e) 
      if is_splat 
       splatted && error("Only one splatting operation allowed on lhs") 
       splatted = true 
       r = :(tmp[$i:end-$(len-i)]) 
      elseif splatted 
       r = :(tmp[end-$(len-i)]) 
      else 
       r = :(tmp[$i]) 
      end 
      push!(new_lhs.args, l) 
      push!(new_rhs.args[4].args, r) 
     end 
     return :($new_lhs = $new_rhs) 
    end 
end 

remove_splat(e::Symbol) = esc(e), false 

function remove_splat(e::Expr) 
    if e.head == :(...) 
     return esc(e.args[1]), true 
    else 
     return esc(e), false 
    end 
end 

macro unpack(expr) 
    if Meta.isexpr(expr, :(=)) 
     if Meta.isexpr(expr.args[1], :tuple) 
      return unpack(expr.args[1], expr.args[2]) 
     else 
      return unpack(:(($(expr.args[1]),)), expr.args[2]) 
     end 
    else 
     error("Cannot parse expression") 
    end 
end 

Это не очень хорошо испытанной, но основные вещи работают:

julia> @unpack head, tail... = [1,2,3,4] 
(1,[2,3,4]) 

julia> @unpack head, middle..., tail = [1,2,3,4,5] 
(1,[2,3,4],5) 

Несколько Юлия Gotchas:

x,y = [1,2,3] #=> x = 1, y = 2 

a = rand(3) 
a[1:3], y = [1,2,3] #=> a = [1.0,1.0,1.0], y = 2 

Макро следует за этим поведением

@unpack a[1:3], y... = [1,2,3] 
#=> a=[1.0,1.0,1.0], y=[2,3]