Джулия идиоматический способ разделения вектора на подвекторы на основе условия

#julia #idioms

#джулия #идиомы

Вопрос:

Допустим, у меня есть вектор a = [1, 0, 1, 2, 3, 4, 5, 0, 5, 6, 7, 8, 0, 9, 0] , и я хочу разделить его на меньшие векторы на основе условия, зависящего от значения в этом массиве. Например. значение равно нулю. Таким образом, я хочу получить вектор следующих векторов

  [1, 0]
 [1, 2, 3, 4, 5, 0]
 [5, 6, 7, 8, 0]
 [9, 0]
  

До сих пор это работало для меня как наивное решение, но оно теряет тип.

 function split_by_λ(a::Vector, λ)
    b = []
    temp = []
    for i in a
        push!(temp, i)
        if λ(i)
            push!(b, temp)
            temp = []
        end
    end
    b
end
split_by_λ(a, isequal(0))
  

Затем я попытался поиграть с диапазонами, которые кажутся немного более идиоматичными и не теряют тип.

 function split_by_λ(a::Vector, λ)
    idx = findall(λ, a)
    ranges = [(:)(i==1 ? 1 : idx[i-1] 1, idx[i]) for i in eachindex(idx)]
    map(x->a[x], ranges)
end

split_by_λ(a, isequal(0))
  

но это все еще кажется очень громоздким, поскольку это довольно простая задача.
Есть ли что-то, чего мне не хватает, какой-то более простой способ?

Ответ №1:

Может быть, у кого-то есть более короткая идея, но вот моя:

 julia> inds = vcat(0,findall(==(0),a),length(a))


julia> getindex.(Ref(a), (:).(inds[1:end-1]. 1,inds[2:end]))
5-element Array{Array{Int64,1},1}:
 [1, 0]
 [1, 2, 3, 4, 5, 0]
 [5, 6, 7, 8, 0]
 [9, 0]
 []
  

Или если вы хотите избежать копирования a

 julia> view.(Ref(a), (:).(inds[1:end-1]. 1,inds[2:end]))
5-element Array{SubArray{Int64,1,Array{Int64,1},Tuple{UnitRange{Int64}},true},1}:
 [1, 0]
 [1, 2, 3, 4, 5, 0]
 [5, 6, 7, 8, 0]
 [9, 0]
 0-element view(::Array{Int64,1}, 16:15) with eltype Int64

  

Комментарии:

1. Да, это кажется более юлианским, спасибо! Кроме того, не могли бы вы пояснить, почему нужно делать Ref(a) вместо того, чтобы просто передавать a? Я пытался сделать что-то подобное, транслируя через getindex, но я не понимаю, почему это не работает без передачи ссылки.

2. Ref(a) позволяет избежать повторной трансляции a . Вы хотите транслировать по набору диапазонов полностью a каждый раз.

Ответ №2:

Почти такой же, как ответ Пшемыслава, но, возможно, меньше загадочный плотный:

 function split_by(λ, a::Vector)
    first, last = firstindex(a), lastindex(a)
    splits = [first-1; findall(λ, a); last]
    s1, s2 = @view(splits[1:end-1]), @view(splits[2:end])
    return [view(a, i1 1:i2) for (i1, i2) in zip(s1, s2)]
end
  

Кроме того, я изменил подпись на обычную «сначала функции», которая позволяет использовать do -блоки. Кроме того, это должно работать со смещенной индексацией.

Можно было бы, конечно, избавиться от промежуточных распределений, но я думаю, что это становится уродливым без yield :

 function split_by(λ, a::Vector)
    result = Vector{typeof(view(a, 1:0))}()
    l = firstindex(a)
    r = firstindex(a)
    while r <= lastindex(a)
        if λ(a[r])
            push!(result, @view(a[l:r]))
            l = r   1
        end
        r  = 1
    end
    push!(result, @view(a[l:end]))

    return result
end
  

Комментарии:

1. Я думаю, цель конкурса — сделать это в одной строке 😉

2. Я мог бы поклясться, что был пакет, делающий что-то подобное.

3. Я тоже не смог его найти. Я немного отредактировал свой код — возможно, он менее загадочный, а возможно, и нет