Dictionary - inverse and consolidating

I have found this solution in Python and want to write in Julia something that will yield the same end result.

It inverses the keys and values in a dictionary and consolidate the values in the inverse dictionary into lists that hold what was keys in the original dictionary, that had the same values.

This example says it all, (in Python syntax):

orig_dict # the original dictionary
{‘one’: 10, ‘two’: 20, ‘three’: 20, ‘four’: 10, ‘five’: 30}

inverted_dict # the inverted dictionary
{10: [‘one’, ‘four’], 20: [‘two’, ‘three’], 30: [‘five’]}

And here is the Python code that makes this inversion:

inverted_my_dict = {}
    for key, value in my_dict.items():
        inverted_my_dict.setdefault(value, list()).append(key)

The iteration line in Julia:
for (k, v) in my_dict

Asking for help here with the next step, the one that does the inverse and consolidation work. It is a one line in Python but it does two things.
How would you write something in Julia that will produce the same result?

Something like:

my_dict = Dict("one" => 10, "two" => 20, "three" => 20, "four" => 10, "five" => 30)
inverted_dict = Dict{valtype(my_dict), Vector{keytype(my_dict)}}()

for (k, v) in my_dict
    push!(get!(() -> valtype(inverted_dict)[], inverted_dict, v), k)
end
julia> inverted_dict
Dict{Int64,Array{String,1}} with 3 entries:
  30 => ["five"]
  10 => ["four", "one"]
  20 => ["two", "three"]

perhaps

4 Likes

This works perfectly!

Thank you for the good help.

You are using the following signature for get!

get!(f::Function, collection, key)
...
This is intended to be called using `do` block syntax:

get!(dict, key) do
    # default value calculated here
    time()
end

Could you please explain the example: isn’t get! missing a parameter? Why time()? Could you also give an example of the use of get! in the do-block?

Edit: Thank you @kristoffer.carlsson for pointing out my oversight and the code is working now.

# I forgot the `( )`. Fixed now
dict = Dict{Any, Any}()

# The doc example
get!(dict, :a) do
    time()
end

# The equivalent
get!( ()-> time(), dict, :b)

# If you already have function
function f()
    return time()
end

get!(f, dict, :c)

println(duct)  # Dict{Any,Any}(:a => 1.593679792519603e9,:b => 1.59367979306765e9,:c => 1.59367992264622e9)

My original post:

AFAIK,

  1. do block in Julia replaces the first parameter (f::Function) of get!(). Use do block if the function is more than a one-liner, for readability.
  2. time() is to give a value to each element in dict. It can be anything as you see fit.

So it is equivalent to

get!(x-> time(), dict, key)
1 Like

You want to end that with a () to create an instance, and not just refer to the type.

2 Likes

Thank you. That was my oversight. The code is working perfectly now.

I opened a PR with your clarification to the docs.

Often I have a one-to-one mapping, and then I define for myself

Base.inv(dict::Dict) = Dict(value => key for (key, value) in dict)

Using SplitApplyCombine’s group we get some inverted dictionary similar to the solution above, but not as good as type is Any.
Can it be improved?

using SplitApplyCombine

my_dict = Dict("one" => 10, "two" => 20, "three" => 20, "four" => 10, "five" => 30)

inverted = group(k -> my_dict[k], keys(my_dict))

3-element Dictionaries.Dictionary{Any, Vector{String}}
 20 │ ["two", "three"]
 10 │ ["four", "one"]
 30 │ ["five"]