Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
Heptazhou committed Nov 21, 2024
1 parent c8c99db commit 7e1888f
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 10 deletions.
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Exts"
uuid = "0b12d779-4123-4875-9d6c-e33c2e29e2c9"
authors = ["Heptazhou <zhou at 0h7z dot com>"]
version = "0.2.4"
version = "0.2.5"

[deps]
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
Expand All @@ -15,11 +15,13 @@ DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
FITSIO = "525bcba6-941b-5504-bd06-fd0dc1a4d2eb"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"

[extensions]
DataFramesExt = "DataFrames"
FITSIOExt = ["DataFrames", "FITSIO"]
StatisticsExt = "StatsBase"
YAMLExt = "YAML"

[compat]
CFITSIO = "≥ 1.4.2"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ pkg> add Exts
julia> using Exts

julia> ODict(Exts.ext(:)) # to see which extensions are loaded
OrderedDict{Symbol, Union{Nothing, Module}} with 4 entries:
OrderedDict{Symbol, Union{Nothing, Module}} with 5 entries:
:BaseExt => Exts.BaseExt
:DataFramesExt => DataFramesExt
:FITSIOExt => FITSIOExt
:StatisticsExt => StatisticsExt
:YAMLExt => YAMLExt
```

1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ end)
using DataFrames: DataFrames
using FITSIO: FITSIO
using StatsBase: StatsBase
using YAML: YAML

const cache = URL::String -> (URL, HTTP.download(URL * "objects.inv", mktempdir()))
const entry = ODict{String, String}()
Expand Down
18 changes: 18 additions & 0 deletions docs/src/YAMLExt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## API reference
```@autodocs
Modules = [YAMLExt]
Order = [:module]
```

## Types
```@autodocs
Modules = [YAMLExt]
Order = [:constant, :type]
```

## Functions
```@autodocs
Modules = [YAMLExt]
Order = [:function, :macro]
```

3 changes: 2 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ pkg> add Exts
julia> using Exts
julia> ODict(Exts.ext(:)) # to see which extensions are loaded
OrderedDict{Symbol, Union{Nothing, Module}} with 4 entries:
OrderedDict{Symbol, Union{Nothing, Module}} with 5 entries:
:BaseExt => Exts.BaseExt
:DataFramesExt => DataFramesExt
:FITSIOExt => FITSIOExt
:StatisticsExt => StatisticsExt
:YAMLExt => YAMLExt
```

*****
Expand Down
33 changes: 33 additions & 0 deletions ext/YAMLExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (C) 2024 Heptazhou <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

"""
YAMLExt
"""
module YAMLExt

using Exts: lmap
using YAML: YAML, yaml

"""
yaml(data...; delim = "") -> String
Return a YAML-formatted string of the `data`.
"""
function YAML.yaml(xs...; delim = "")::String
join(lmap(yaml, xs), delim)
end

end

3 changes: 3 additions & 0 deletions src/Exts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ end

include("Function.jl")

# https://github.com/JuliaCollections/OrderedCollections.jl/pull/120
include("OrderedCollectionsExt.jl")

# StatisticsExt
function nanmean end

Expand Down
5 changes: 3 additions & 2 deletions src/Function.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function ext(::Colon)::VTuple{Pair{Symbol, Maybe{Module}}}
:DataFrames,
:FITSIO,
:Statistics,
:YAML,
)
map(x -> Symbol(x, :Ext) => ext(x), xs)
end
Expand Down Expand Up @@ -114,7 +115,7 @@ function isdirpath(path::AbstractString)::Bool
Base.isdirpath(path)
end

@doc """
"""
lmap(f, iterators...)
Create a lazy mapping. This is another syntax for writing `(f(args...) for
Expand Down Expand Up @@ -165,7 +166,7 @@ function ∠(azimuth::Real)::Complex{<:AbstractFloat}
cispi(azimuth / 180) # 360° = 2π rad
end

@doc raw"""
"""
readstr(x) -> String
Read the entirety of `x` as a string. Equivalent to `read(x, String)`.
Expand Down
109 changes: 109 additions & 0 deletions src/OrderedCollectionsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copyright (C) 2024 Heptazhou <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

module OrderedCollectionsExt

using Base: isbitsunion, unwrap_unionall
using OrderedCollections: OrderedCollections, OrderedDict, _tablesz, hashindex, rehash!

@static if [unwrap_unionall(methods(rehash!, (OrderedDict, Int))[1].sig)...][3] == Any
#! format: noindent
function OrderedCollections.rehash!(h::OrderedDict{K, V}, newsz::Integer) where {K, V}
olds = h.slots
keys = h.keys
vals = h.vals
sz = length(olds)
newsz = _tablesz(newsz)
h.dirty = true
count0 = length(h)
if count0 == 0
resize!(h.slots, newsz)
fill!(h.slots, 0)
resize!(h.keys, 0)
resize!(h.vals, 0)
h.ndel = 0
return h
end
slots = zeros(Int32, newsz)
maxprobe = 0
if h.ndel > 0
ndel0 = h.ndel
ptrs = !isbitstype(K) && !isbitsunion(K)
to = 1
newkeys = similar(keys, count0)
newvals = similar(vals, count0)
@inbounds for from 1:length(keys)
if !ptrs || isassigned(keys, from)
k = keys[from]
hashk = hash(k) % Int
isdeleted = false
if !ptrs
iter = 0
index = (hashk & (sz - 1)) + 1
while iter <= h.maxprobe
si = olds[index]
si == from && break
(si == -from || si == 0) && (isdeleted = true; break)
index = (index & (sz - 1)) + 1
iter += 1
end
iter > h.maxprobe && (isdeleted = true)
end
if !isdeleted
index0 = index = (hashk & (newsz - 1)) + 1
# COV_EXCL_START
while slots[index] != 0
index = (index & (newsz - 1)) + 1
end
# COV_EXCL_STOP
probe = (index - index0) & (newsz - 1)
probe > maxprobe && (maxprobe = probe)
slots[index] = to
newkeys[to] = k
newvals[to] = vals[from]
to += 1
end
if h.ndel != ndel0
return rehash!(h, newsz) # COV_EXCL_LINE
end
end
end
h.keys = newkeys
h.vals = newvals
h.ndel = 0
else
@inbounds for i 1:count0
k = keys[i]
index0 = index = hashindex(k, newsz)
# COV_EXCL_START
while slots[index] != 0
index = (index & (newsz - 1)) + 1
end
# COV_EXCL_STOP
probe = (index - index0) & (newsz - 1)
probe > maxprobe && (maxprobe = probe)
slots[index] = i
if h.ndel > 0
return rehash!(h, newsz) # COV_EXCL_LINE
end
end
end
h.slots = slots
h.maxprobe = maxprobe
return h
end
end

end # module

50 changes: 45 additions & 5 deletions src/Type.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,49 @@ const OSet = OrderedSet
const UDict = Dict
const USet = Set

@doc " LDict <- LittleDict" LDict
@doc " ODict <- OrderedDict" ODict
@doc " OSet <- OrderedSet" OSet
@doc " UDict <- Dict # UnorderedDict" UDict
@doc " USet <- Set # UnorderedSet" USet
@doc """
LDict <- LittleDict
An ordered dictionary type for small numbers of keys. Rather than using
`hash` or some other sophisticated measure to store the vals in a clever
arrangement, it just keeps everything in a pair of lists.
While theoretically this has expected time complexity ``O(n)`` (vs the
hash-based `OrderedDict`/`Dict`'s expected time complexity ``O(1)``, and the
search-tree-based `SortedDict`'s expected time complexity ``O(\\log n)``), in
practice it is really fast, because it is cache & SIMD friendly.
It is reasonable to expect it to outperform an `OrderedDict`, with up to
around 30 elements in general; or with up to around 50 elements if using a
`LittleDict` backed by `Tuple`s. However, this depends on exactly how long
`isequal` and `hash` take, as well as on how many hash collisions occur etc.
""" LDict
# https://github.com/JuliaCollections/OrderedCollections.jl/blob/master/src/little_dict.jl
@doc """
ODict <- OrderedDict
`OrderedDict`s are simply dictionaries whose entries have a particular order.
The order refers to insertion order, which allows deterministic iteration
over the dictionary.
""" ODict
# https://github.com/JuliaCollections/OrderedCollections.jl/blob/master/src/ordered_dict.jl
@doc """
OSet <- OrderedSet
`OrderedSet`s are simply sets whose entries have a particular order. The
order refers to insertion order, which allows deterministic iteration over
the set.
""" OSet
# https://github.com/JuliaCollections/OrderedCollections.jl/blob/master/src/ordered_set.jl

@doc """
UDict <- Dict # UnorderedDict
See [`Base.Dict`](@extref).
""" UDict
@doc """
USet <- Set # UnorderedSet
See [`Base.Set`](@extref).
""" USet

1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
28 changes: 28 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ end
a_unionall = Union{Vector{T}, Matrix{T}, Array{T, 3}} where T
a2_missing = Array{Missing, 2}(undef, Tuple(rand(0:9, 2)))
a3_nothing = Array{Nothing, 3}(undef, Tuple(rand(0:9, 3)))
allowed_undefineds = GlobalRef.(Ref(Base), [:active_repl, :active_repl_backend])
using Exts

@test (>, <)(0) === (>(0), <(0))
Expand Down Expand Up @@ -210,6 +211,7 @@ end
@test getfirst(iseven, 1:9) == getfirst(iseven)(1:9) == 2
@test getlast(iseven, 1:9) == getlast(iseven)(1:9) == 8
@test invsqrt(2^-2) == 2
@test isempty(detect_ambiguities(Core, Base, Exts; recursive = true, allowed_undefineds))
@test log10(11, 2) isa NTuple{2, Float64}
@test Maybe{Missing} == maybe(Missing) == maybe(Missing, Missing)
@test Maybe{Nothing} == maybe(Nothing) == maybe() == Nothing
Expand Down Expand Up @@ -275,6 +277,16 @@ end
end
close.((i, o))
@test readstr(fo) "Press any key to continue . . . \n"

# https://github.com/JuliaCollections/OrderedCollections.jl/pull/120
d = ODict(nothing => 0)
@test ODict(delete!(d, nothing)...) == ODict()
d = ODict(nothing => 1, 2 => 3)
@test ODict(delete!(d, nothing)...) == ODict(2 => 3)
d = ODict(2 => 0.1, nothing => 0.2, 3 => 0.5)
@test ODict(delete!(d, nothing)...) == ODict(2 => 0.1, 3 => 0.5)
d = ODict(2 => 0.1, 5 => 0.4, nothing => 0.03, 10 => 0.4)
@test ODict(delete!(d, nothing)...) == ODict(2 => 0.1, 5 => 0.4, 10 => 0.4)
end

@testset "DataFramesExt" begin
Expand Down Expand Up @@ -312,6 +324,22 @@ end
@test mean(1:20) === nanmean(1:20, weights(zeros(20)))
end

@testset "YAMLExt" begin
using YAML: yaml
str = yaml(
S"name" => S"CI",
S"on" => LDict(:workflow_dispatch => nothing),
S"defaults" => LDict(:run => Dict(:shell => :bash)),
S"env" => LDict(:JULIA_NUM_THREADS => S"auto"),
)
@test str === """
name: CI
on:\n workflow_dispatch: ~
defaults:\n run:\n shell: bash
env:\n JULIA_NUM_THREADS: auto
"""
end

@test all(nameof.(last.(Exts.ext(:))) .== first.(Exts.ext(:)))
@static parse(Bool, get(ENV, "CI", "0")) || include("docs.jl")

0 comments on commit 7e1888f

Please sign in to comment.