Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable getproperty for Symbol keys #43

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
authors = ["Andy Ferris <[email protected]>"]
name = "Dictionaries"
uuid = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
version = "0.3.7"
version = "0.3.8"

[deps]
Indexing = "313cdc1a-70c2-5d6a-ae34-0150d3930a38"
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,29 @@ julia> delete!(dict, "d")

Note that `insert!` and `delete!` are precise in the sense that `insert!` will error if the index already exists, and `delete!` will error if the index does not. The `set!` function provides "upsert" functionality ("update or insert") and `unset!` is useful for removing an index that may or may not exist.

#### Dictionaries with `Symbol` keys

If the keys of a dictionary are `Symbol`s, then it is possible to use Julia's `getproperty` syntax sugar to access elements.

```julia
julia> dict = Dictionary([:a, :b, :c], [1, 2, 3])
3-element Dictionary{Symbol,Int64}
:a │ 1
:b │ 2
:c │ 3

julia> dict.b
2

julia> dict.a = 100; dict
3-element Dictionary{Symbol,Int64}
:a │ 100
:b │ 2
:c │ 3
```

This property makes them good substitutes for `NamedTuple`s, or for storing and accessing the rows or columns of a table.

### Working with dictionaries

Dictionaries can be manipulated and transformed using a similar interface to Julia's built-in arrays. The first thing to note is that dictionaries iterate values, so it easy to perform simple analytics on the dictionary values.
Expand Down
4 changes: 2 additions & 2 deletions src/Indices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -505,15 +505,15 @@ function __distinct!(indices::AbstractIndices, itr, s, x_old)
end

function randtoken(rng::Random.AbstractRNG, inds::Indices)
if inds.holes === 0
if _holes(inds) === 0
return (0, rand(rng, Base.OneTo(length(inds))))
end

# Rejection sampling to handle deleted tokens (which are sparse)
range = Base.OneTo(length(_hashes(inds)))
while true
i = rand(rng, range)
if inds.hashes[i] !== deletion_mask
if _hashes(inds)[i] !== deletion_mask
return (0, i)
end
end
Expand Down
10 changes: 5 additions & 5 deletions src/indexing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ end

## getproperty is equivalent to indexing with a `Symbol`

Copy link
Contributor

@aplavin aplavin Mar 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base.propertynames(d::AbstractDictionary) = keys(d)

# @propagate_inbounds Base.getproperty(d::AbstractDictionary, s::Symbol) = d[s]
# @propagate_inbounds function Base.setproperty!(d::AbstractDictionary, s::Symbol, x)
# d[s] = x
# return x
# end
@propagate_inbounds Base.getproperty(d::AbstractDictionary, s::Symbol) = d[s]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings and Ints also work: d."abc" and d.:123.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could probably restrict the AbstractDictionary type to ones containing Symbol keys only and return an explicit error otherwise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also Strings and Ints should work, as I commented half a year ago above :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So something like

Suggested change
@propagate_inbounds Base.getproperty(d::AbstractDictionary, s::Symbol) = d[s]
@propagate_inbounds Base.getproperty(d::AbstractDictionary{T}, s::T) where {T<:Union{Symbol,Integer,String}} = d[s]
Base.getproperty(d::AbstractDictionary{T}, s) where {T} = error("`getproperty` can only be used on dictionaries with keys of type `Symbol`, `Integer` or `String` (got $(T))")

@propagate_inbounds function Base.setproperty!(d::AbstractDictionary, s::Symbol, x)
d[s] = x
return x
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also

using ConstructionBase
ConstructionBase.setproperties(obj::AbstractDictionary, patch::NamedTuple) = merge(obj, Dictionary(keys(patch), values(patch)))

would be nice

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Julia 1.9 it'll be a lot easier to include opt-in "weak" dependencies on other packages on the ecosystem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that was just a suggestion - ConstructionBase is so lightweight and widely used that I didn't even think it can/should be made a weakdep.
I may make a PR with ConstructionBase and Accessors integrations then.

## Non-scalar indexing

Expand Down