From 43b3ad0fa8d8796fd11c58acc4f96eae5b490733 Mon Sep 17 00:00:00 2001 From: Simon Schoelly Date: Wed, 22 Nov 2023 02:46:09 +0100 Subject: [PATCH 1/5] Add FrozenVector to wrap neighbors --- src/Graphs.jl | 1 + src/SimpleGraphs/SimpleGraphs.jl | 5 +++-- src/core.jl | 16 +++++++++------- src/frozenvector.jl | 23 +++++++++++++++++++++++ src/interface.jl | 14 ++++++++------ 5 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 src/frozenvector.jl diff --git a/src/Graphs.jl b/src/Graphs.jl index 86bc0946a..982974d1b 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -463,6 +463,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 7c2589d7f..242ff35c1 100644 --- a/src/SimpleGraphs/SimpleGraphs.jl +++ b/src/SimpleGraphs/SimpleGraphs.jl @@ -15,6 +15,7 @@ import Graphs: AbstractGraph, AbstractEdge, AbstractEdgeIter, + FrozenVector, src, dst, edgetype, @@ -152,8 +153,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 cb8abd175..95ca786c4 100644 --- a/src/core.jl +++ b/src/core.jl @@ -220,9 +220,10 @@ 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 @@ -235,14 +236,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 ``` """ @@ -256,9 +257,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..701a4c272 --- /dev/null +++ b/src/frozenvector.jl @@ -0,0 +1,23 @@ +""" + 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 280efcb22..695b46db3 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 ``` """ From 3edd095b75e5e8393210fadc04246db547b06bf2 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sat, 21 Feb 2026 01:43:31 -0500 Subject: [PATCH 2/5] Apply JuliaFormatter (BlueStyle, v2.2.0) Co-Authored-By: Claude Opus 4.6 --- src/frozenvector.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/frozenvector.jl b/src/frozenvector.jl index 701a4c272..c234df0c3 100644 --- a/src/frozenvector.jl +++ b/src/frozenvector.jl @@ -9,9 +9,11 @@ end Base.size(v::FrozenVector) = Base.size(v.wrapped) -Base.@propagate_inbounds Base.getindex(v::FrozenVector, i::Int) = Base.getindex(v.wrapped, i) +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.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) From 15513c0574c9959cdb08ffff28f3eb934a272f23 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Sat, 21 Feb 2026 17:07:10 -0500 Subject: [PATCH 3/5] Fix ReverseView doctest to match FrozenVector output Co-Authored-By: Claude Opus 4.6 --- src/wrappedGraphs/graphviews.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wrappedGraphs/graphviews.jl b/src/wrappedGraphs/graphviews.jl index 3ce33562e..ad6674549 100644 --- a/src/wrappedGraphs/graphviews.jl +++ b/src/wrappedGraphs/graphviews.jl @@ -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 ``` """ From 1aeffcc23aa82b33057c3f8134baf50a0746bba2 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Mon, 23 Feb 2026 16:35:20 -0500 Subject: [PATCH 4/5] Add FrozenVector breaking change to CHANGELOG Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7686d15ed..bf1c0541b 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` From b0372cb0d10d171050e2da6c93732b61e68d20a7 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Tue, 24 Feb 2026 15:01:32 -0500 Subject: [PATCH 5/5] Add doctest filter for FrozenVector empty array printing Julia 1.13 changed how empty AbstractVector subtypes are displayed. Previously (<=1.12), an empty FrozenVector printed as: 0-element Graphs.FrozenVector{Int64} In Julia 1.13+, it prints as: Int64[] This broke the doctests for `neighbors` and `ReverseView` that show empty FrozenVector output. The fix adds a local doctest filter to each affected jldoctest block that normalizes both representations before comparison. Co-Authored-By: Claude Opus 4.6 --- src/core.jl | 2 +- src/wrappedGraphs/graphviews.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core.jl b/src/core.jl index bbf86e2ec..81c0ffb48 100644 --- a/src/core.jl +++ b/src/core.jl @@ -254,7 +254,7 @@ 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); diff --git a/src/wrappedGraphs/graphviews.jl b/src/wrappedGraphs/graphviews.jl index ad6674549..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);