Defining a parameter out of pairs and values

I have a set of pair like the following, and each pair comes with a value attached to it. For isntance the value of a pair (1,2) will be 2.5. There are another set of persons. Each person also connected to the subset of pairs. For instance, for p is connected to [(1, 2), (1, 7), (2, 3)].


Pairs  = [(1, 2), (1, 7), (2, 3), (2, 5),(3, 4), (3, 5), (4, 1), (4, 5), (5, 2)]
Values = [ 2.5,    4.1,    7.4,    5.6,   2.8,    3.7,    6.9,     0.2,    3.2 ]

Persons = ['p','q']
p_pairs = [   [(1, 2), (1, 7), (2, 3)], 
              [(1, 2), (2, 3), (2, 5) ,(5, 2)] 
           ]

The question is, how to bulid up a parameter person_values such that it return the value of the associated pairs?

For instance person_values['p',1, 2] = 2.5 and person_values['p',2, 3] = person_values['q',2, 3] = 7.4. In other words person_values[ {elements of p1} , { elemnts of pairs} ] = related values

Please check if this is right for you:

function person_values(person, pair, Persons, p_pairs, Pairs, Values)
    d1, d2 = Dict(Persons .=> p_pairs), Dict(Pairs .=> Values)
    haskey(d1, person) && (pair in d1[person]) ? d2[pair] : nothing
end

Pairs  = [(1, 2), (1, 7), (2, 3), (2, 5),(3, 4), (3, 5), (4, 1), (4, 5), (5, 2)]
Values = [ 2.5,    4.1,    7.4,    5.6,   2.8,    3.7,    6.9,     0.2,    3.2 ]

Persons = ['p','q']
p_pairs = [  [(1, 2), (1, 7), (2, 3)], [(1, 2), (2, 3), (2, 5) ,(5, 2)] ]

person_values('p', (1,2), Persons, p_pairs, Pairs, Values)      # 2.5
person_values('q', (2,3), Persons, p_pairs, Pairs, Values)      # 7.4
1 Like

@rafael.guerra this is very interesting to use two dicts. Is there any way to separate pairs? Like person_values['p',1, 2] = 2.5 not like this pairs person_values['p', (1, 2)] = 2.5

If you are not going to keep the Dicts for future lookups I really recommend just using findfirst and then using the index in the other Vector.

2 Likes

You could accept two separate arguments to the function, and create the tuple inside it:

function person_values2(person, x, y, Persons, p_pairs, Pairs, Values)
    d1, d2 = Dict(Persons .=> p_pairs), Dict(Pairs .=> Values)
    haskey(d1, person) && ((x,y) in d1[person]) ? d2[(x,y)] : nothing
end

person_values2('p', 1, 2, Persons, p_pairs, Pairs, Values)      # 2.5
person_values2('q', 2, 3, Persons, p_pairs, Pairs, Values)      # 7.4

NB: I’m just a user, better listen to Henrique’s advice.

2 Likes

try using this function


function person_values(person, pair...)
    get(d2,only(intersect(get(d1,person,nothing),[pair])),nothing)
end


person_values('q',2, 3)
person_values('p',1, 2) 


# or


function person_values(person, pair...)
    pp=intersect(get(d1,person,nothing),[pair])
    isempty(pp) ? nothing : get(d2,pp[1],nothing)
end



1 Like

Thanks @rocco_sprmnt21 , so, to define a parameter we should use function? always?

I was wondering why this does not work?

[ get(d2,only(intersect(get(d1,person,nothing),[pair])),nothing) for person in Persons for pair in pairs]

in other word; is there a way to extent this over aloop to assign all at once?? (No function)

Actually I’m a novice and never heard of this before. Thanks for recommending @Henrique_Becker .
I don’t know whether I’ll need to keep the dics at future, but I know I want to unpair while keeping the order of pairs (since (2,3) <> (3,2) )

1 Like

I’m not sure I understand what the expected result is, but I try


[ (person,pair)=>person_values(person,pair...)  for (person,pair) in Iterators.product(Persons,Pairs)][:]

18-element Vector{Pair{Tuple{Char, Tuple{Int64, Int64}}}}:
 ('p', (1, 2)) => 2.5
 ('q', (1, 2)) => 2.5
 ('p', (1, 7)) => 4.1
 ('q', (1, 7)) => nothing
 ('p', (2, 3)) => 7.4
 ('q', (2, 3)) => 7.4
 ('p', (2, 5)) => nothing
 ('q', (2, 5)) => 5.6
 ('p', (3, 4)) => nothing
 ('q', (3, 4)) => nothing
 ('p', (3, 5)) => nothing
 ('q', (3, 5)) => nothing
 ('p', (4, 1)) => nothing
 ('q', (4, 1)) => nothing
 ('p', (4, 5)) => nothing
 ('q', (4, 5)) => nothing
 ('p', (5, 2)) => nothing
 ('q', (5, 2)) => 3.2

#or, if you prefere

julia> [ (person,pair...)=>person_values(person,pair...)  for (person,pair) in
                               Iterators.product(Persons,Pairs)][:]
18-element Vector{Pair{Tuple{Char, Int64, Int64}}}:
 ('p', 1, 2) => 2.5
 ('q', 1, 2) => 2.5
 ('p', 1, 7) => 4.1
 ('q', 1, 7) => nothing
 ('p', 2, 3) => 7.4
 ('q', 2, 3) => 7.4
 ('p', 2, 5) => nothing
 ('q', 2, 5) => 5.6
 ('p', 3, 4) => nothing
 ('q', 3, 4) => nothing
 ('p', 3, 5) => nothing
 ('q', 3, 5) => nothing
 ('p', 4, 1) => nothing
 ('q', 4, 1) => nothing
 ('p', 4, 5) => nothing
 ('q', 4, 5) => nothing
 ('p', 5, 2) => nothing
 ('q', 5, 2) => 3.2

Wow, all these seem very complicated. I feel lost. need to search on them soon.What I wish is just a mapping from two sets Persons and p_pairs to a given set like values.
Having too much nothing may make the program slow? because this person_values parameters will later be used somewhere over a loop.

Is there any simpler way to first filter out values and then assign. Should we use function and Dict? Is there any way to just use for

Then this should be closer to your needs

[ (person,pair...)=>person_values(person,pair...)  for (person,pair) in 
    Iterators.flatten([k.=>v for (k,v) in d1])]

which can be rewritten like this


[(p,pp...)=>d2[pp] for(p,pp) in Iterators.flatten([k.=>v for (k,v) in d1])]

7-element Vector{Pair{Tuple{Char, Int64, Int64}, Float64}}:
 ('p', 1, 2) => 2.5
 ('p', 1, 7) => 4.1
 ('p', 2, 3) => 7.4
 ('q', 1, 2) => 2.5
 ('q', 2, 3) => 7.4
 ('q', 2, 5) => 5.6
 ('q', 5, 2) => 3.2
1 Like

AFAIC, sorry but simplicity requires more time and skill :slight_smile:

3 Likes

As the Germans say: “Sorry for the long letter, I did not have the time to write a short one.”

3 Likes

Having enough time to be shorter…

julia> [(p,pp...)=>d2[pp] for(p,pp) in vcat([k.=>v for (k,v) in d1]...)]
7-element Vector{Pair{Tuple{Char, Int64, Int64}, Float64}}:
 ('p', 1, 2) => 2.5
 ('p', 1, 7) => 4.1
 ('p', 2, 3) => 7.4
 ('q', 1, 2) => 2.5
 ('q', 2, 3) => 7.4
 ('q', 2, 5) => 5.6
 ('q', 5, 2) => 3.2
1 Like

There is also the famous quote from Gauss:

You know that I write slowly. This is chiefly because I am never satisfied until I have said as much as possible in a few words, and writing briefly takes far more time than writing at length.

2 Likes

A more lower level alternative:

julia> function person_values(p, i, j; Persons=Persons, Pairs=Pairs, Values=Values, p_pairs=p_pairs)
           ip = findfirst(isequal(p), Persons)
           isnothing(ip) && error("Person not found")
           ipair = findfirst(isequal((i,j)), p_pairs[ip])
           isnothing(ipair) && return nothing
           ivalue = findfirst(isequal((i,j)), Pairs)
           isnothing(ivalue) && error("Value not found")
           return Values[ivalue]
       end
person_values (generic function with 1 method)

julia> person_values('q', 2, 3)
7.4

julia> @btime person_values('q', 2, 3; Persons=$Persons, Pairs=$Pairs, Values=$Values, p_pairs=$p_pairs)
  9.037 ns (0 allocations: 0 bytes)
7.4

ps: This is what Henrique suggested, actually.

3 Likes

@lmiq, could you confirm whether the keyword arguments default values, in your function implementation, need to be declared as global constants outside the function for it to perform best? Thanks.

I think they do.

1 Like

the version with findfirst and without dicts

[(p,pp...)=>Values[findfirst(==(pp),Pairs)] for(p,pp) in vcat([k.=>v for (k,v) in zip(Persons,p_pairs)]...)]

1 Like