diff --git a/CHANGELOG.md b/CHANGELOG.md index 2abfde308..deee20b5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # News ## dev - unreleased +- **(breaking)** `neighbors`, `inneighbors`, and `outneighbors` now return an immutable `FrozenVector` instead of `Vector` - Louvain community detection algorithm - Graph views: `ReverseView` and `UndirectedView` for directed graphs - New graph products: `strong_product`, `disjunctive_product`, `lexicographic_product`, `homomorphic_product` diff --git a/src/Graphs.jl b/src/Graphs.jl index f634e59fe..b5718b452 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -478,6 +478,7 @@ and tutorials are available at the Graphs include("interface.jl") include("utils.jl") +include("frozenvector.jl") include("deprecations.jl") include("core.jl") diff --git a/src/SimpleGraphs/SimpleGraphs.jl b/src/SimpleGraphs/SimpleGraphs.jl index 8dd38da97..b7b8f84ca 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -15,6 +15,7 @@ import Graphs: AbstractGraph, AbstractEdge, AbstractEdgeIter, + FrozenVector, src, dst, edgetype, @@ -153,8 +154,8 @@ add_edge!(g::AbstractSimpleGraph, x) = add_edge!(g, edgetype(g)(x)) has_edge(g::AbstractSimpleGraph, x, y) = has_edge(g, edgetype(g)(x, y)) add_edge!(g::AbstractSimpleGraph, x, y) = add_edge!(g, edgetype(g)(x, y)) -inneighbors(g::AbstractSimpleGraph, v::Integer) = badj(g, v) -outneighbors(g::AbstractSimpleGraph, v::Integer) = fadj(g, v) +inneighbors(g::AbstractSimpleGraph, v::Integer) = FrozenVector(badj(g, v)) +outneighbors(g::AbstractSimpleGraph, v::Integer) = FrozenVector(fadj(g, v)) function issubset(g::T, h::T) where {T<:AbstractSimpleGraph} nv(g) <= nv(h) || return false diff --git a/src/core.jl b/src/core.jl index cc9c1aa75..81c0ffb48 100644 --- a/src/core.jl +++ b/src/core.jl @@ -248,12 +248,13 @@ For directed graphs, the default is equivalent to [`outneighbors`](@ref); use [`all_neighbors`](@ref) to list inbound and outbound neighbors. ### Implementation Notes -Returns a reference to the current graph's internal structures, not a copy. -Do not modify result. If the graph is modified, the behavior is undefined: +In some cases might return a reference to the current graph's internal structures, +not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed. +If you need to modify the result use `collect` or `copy` to create a copy. # Examples -```jldoctest +```jldoctest; filter = r"0-element Graphs\\.FrozenVector\\{Int64\\}|Int64\\[\\]" julia> using Graphs julia> g = DiGraph(3); @@ -263,14 +264,14 @@ julia> add_edge!(g, 2, 3); julia> add_edge!(g, 3, 1); julia> neighbors(g, 1) -Int64[] +0-element Graphs.FrozenVector{Int64} julia> neighbors(g, 2) -1-element Vector{Int64}: +1-element Graphs.FrozenVector{Int64}: 3 julia> neighbors(g, 3) -1-element Vector{Int64}: +1-element Graphs.FrozenVector{Int64}: 1 ``` """ @@ -284,9 +285,10 @@ For undirected graphs, this is equivalent to both [`outneighbors`](@ref) and [`inneighbors`](@ref). ### Implementation Notes -Returns a reference to the current graph's internal structures, not a copy. -Do not modify result. If the graph is modified, the behavior is undefined: +In some cases might return a reference to the current graph's internal structures, +not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed. +If you need to modify the result use `collect` or `copy` to create a copy. # Examples ```jldoctest diff --git a/src/frozenvector.jl b/src/frozenvector.jl new file mode 100644 index 000000000..c234df0c3 --- /dev/null +++ b/src/frozenvector.jl @@ -0,0 +1,25 @@ +""" + FrozenVector(v::Vector) <: AbstractVector + +A data structure that wraps a `Vector` but does not allow modifications. +""" +struct FrozenVector{T} <: AbstractVector{T} + wrapped::Vector{T} +end + +Base.size(v::FrozenVector) = Base.size(v.wrapped) + +Base.@propagate_inbounds Base.getindex(v::FrozenVector, i::Int) = Base.getindex( + v.wrapped, i +) + +Base.IndexStyle(v::Type{FrozenVector{T}}) where {T} = Base.IndexStyle(Vector{T}) + +Base.iterate(v::FrozenVector) = Base.iterate(v.wrapped) +Base.iterate(v::FrozenVector, state) = Base.iterate(v.wrapped, state) + +Base.similar(v::FrozenVector) = Base.similar(v.wrapped) +Base.similar(v::FrozenVector, T::Type) = Base.similar(v.wrapped, T) +Base.similar(v::FrozenVector, T::Type, dims::Base.Dims) = Base.similar(v.wrapped, T, dims) + +Base.copy(v::FrozenVector) = Base.copy(v.wrapped) diff --git a/src/interface.jl b/src/interface.jl index 4ff21a0a6..2a9c15fb0 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -281,9 +281,10 @@ has_edge(g, e) = has_edge(g, src(e), dst(e)) Return a list of all neighbors connected to vertex `v` by an incoming edge. ### Implementation Notes -Returns a reference to the current graph's internal structures, not a copy. -Do not modify result. If the graph is modified, the behavior is undefined: +In some cases might return a reference to the current graph's internal structures, +not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed. +If you need to modify the result use `collect` or `copy` to create a copy. # Examples ```jldoctest @@ -292,7 +293,7 @@ julia> using Graphs julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); julia> inneighbors(g, 4) -2-element Vector{Int64}: +2-element Graphs.FrozenVector{Int64}: 3 5 ``` @@ -305,9 +306,10 @@ inneighbors(x, v) = _NI("inneighbors") Return a list of all neighbors connected to vertex `v` by an outgoing edge. # Implementation Notes -Returns a reference to the current graph's internal structures, not a copy. -Do not modify result. If the graph is modified, the behavior is undefined: +In some cases might return a reference to the current graph's internal structures, +not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed. +If you need to modify the result use `collect` or `copy` to create a copy. # Examples ```jldoctest @@ -316,7 +318,7 @@ julia> using Graphs julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]); julia> outneighbors(g, 4) -1-element Vector{Int64}: +1-element Graphs.FrozenVector{Int64}: 5 ``` """ diff --git a/src/wrappedGraphs/graphviews.jl b/src/wrappedGraphs/graphviews.jl index 3ce33562e..aace86be1 100644 --- a/src/wrappedGraphs/graphviews.jl +++ b/src/wrappedGraphs/graphviews.jl @@ -9,7 +9,7 @@ A graph view that wraps a directed graph and reverse the direction of every edge constructing the view may lead to incorrect results. # Examples -```jldoctest +```jldoctest; filter = r"0-element Graphs\\.FrozenVector\\{Int64\\}|Int64\\[\\]" julia> using Graphs julia> g = SimpleDiGraph(2); @@ -19,10 +19,10 @@ julia> add_edge!(g, 1, 2); julia> rg = ReverseView(g); julia> neighbors(rg, 1) -Int64[] +0-element Graphs.FrozenVector{Int64} julia> neighbors(rg, 2) -1-element Vector{Int64}: +1-element Graphs.FrozenVector{Int64}: 1 ``` """