
This formula is very simple. If it is written as a function, use the simplest return. However, if I want him to popularize it, inputting Fahrenheit can also calculate Celsius, or even wider. In a formula, as long as other n-1 variables are known, the formula can be automatically completed. What should I do?
The code is not original. I can only worship the big man's source code
Running example

Operation results
In the above code, the change of celsius also changes the Fahrenheit.
We first divide the code architecture into three modules
connector is a high-level variable that can send and receive notifications through constraints.
Because FP is used throughout the process, the methods in OOP are currently called using dictionary key. For example, celsius['forget '] means celsius.forget.
The constraint of the constraint formula can connect the connectors. After the connection, the connectors can be notified through the constraint.
The converter is responsible for all connectors and constraints in the assembly formula
Constraint Part
def make_ternary_constraint(a,b,c,ab,ca,cb): #Ternary constraint def new_value(): #The general calculation formula is used to calculate the third-party connector av,bv,cv=[connector['has_val']()for connector in(a,b,c)] if av and bv: c['set_val'](constraint,ab(a['val'],b['val'])) elif av and cv: b['set_val'](constraint,ca(c['val'],a['val'])) elif bv and cv: a['set_val'](constraint,cb(c['val'],b['val'])) def forget_value(): #Use the forget function for all three abc connectors for connector in(a,b,c): connector['forget'](constraint) #constraint is to call dictionary and pass message to the connector constraint={'new_val':new_value,#Indicates that the connector connected to the constraint has a new value 'forget':forget_value}#Indicates that the connector connected to the constraint needs to forget its value for connector in(a,b,c): #Make all three abc connectors add constraints connector['connect'](constraint) #Return ternary constraint return constraint
In the above functions, a, B and C are the three connectors participating in the constraint, and ab, ca and cb are the operators, which means that if you know both of them, you can get the third party by using the corresponding operators.
Constraint Method
new_value():
Pass has first_ Val observes whether there are known variables. If there are n-1 known variables, the unknown variables can be obtained.
forget_value():
Call the forget function in the connector to clear its value, and then forget will send a clear notification to all participating constraints (because a connector may participate in multiple constraints, such as equations), which is equivalent to a chain reaction.
After that, all connectors join this constraint and finally return.
from operator import add,sub def adder(a,b,c): #Ternary constraint of addition formula return make_ternary_constraint(a,b,c,add,sub,sub) from operator import mul,truediv def multiplier(a,b,c): #Ternary constraint of multiplication formula return make_ternary_constraint(a,b,c,mul,truediv,truediv)
Through the above high-order function, we can establish such a constraint tool to continuously reduce the number of parameters, so as to achieve better abstraction level division.
Connector Part
#Connector part def make_connector(name=None): #Accept the message from the constraint. Name is the name of the variable connector informant=None#User set constraints that store this connector constraints=[]#Store all constraints in which the connector participates (including anonymous constraints derived from the formula) def set_value(source,value): nonlocal informant val=connector['val'] if val is None: #If there is no value, set it to value and informant to constraint (through the constraint set by the user) informant,connector['val']=source,value if name is not None: print(name,'=',value) #Call new for all constraints_ value inform_all_except(source,'new_val',constraints) else: #If different values are passed in, a contradiction occurs if val!=value: print('Contradiction detected:',val,'vs',value) def forget_value(source): nonlocal informant if informant==source: #If the informant and constraint names are the same (i.e. the password is correct and not previously forgotten), set the informant and value to 0 informant,connector['val']=None,None if name is not None: print(name,'is forgotten') #Call forget on all constraints_ value inform_all_except(source,'forget',constraints) connector ={'val':None,#Current value 'set_val':set_value,#Indicates that the source requested the connector to set the current value to this value 'forget':forget_value,#Tell the connector that source asks it to forget the current value 'has_val':lambda: connector['val'] is not None,#Returns whether the connector already has a value 'connect':lambda source:constraints.append(source)}#Tell the connector to participate in the new constraint source #Connector dictionary key:string literal, value: function return connector def inform_all_except(source,message,constraints): #Notify constraints to change all connectors for c in constraints: if c!=source: c[message]()
In the above function, we name a connector name (or anonymous) Connector Method
set_val(source,value)
The value of the connector variable is set by a source (this source can be anything, such as "user" added manually above or automatically added by the constraint itself), and the source is stored (so that the forget must be set by the setter)
If there is a previous value, the conflict will not be changed.
If it was previously empty, call new_value attempts to complete the entire formula.
forget(source)
If the source is correct, the value of the connector is cleared and a clear notification is sent to all participating constraints
has_val()
Returns whether there is a current value
connect(source)
Connector participation constraints
Connector Data Member
informant
Store the user setting constraints of this connector (if it is set manually, it can be as long as it is an ID)
constraints
Store all constraints in which the connector participates (including anonymous constraints derived from the formula)
Normal Function
inform_all_except(source,message,constraints)
Chain reaction, send a message, pay attention to the source, and can't send it back to yourself
Converter Part
def make_converter(c,f): #Assembly connectors and constraints u,v,w,x,y=[make_connector()for _ in range(5)]#Five anonymous connectors, 9*c=5*(f-32) multiplier(c,w,u)#c*9=u multiplier(v,x,u)#v*5=u adder(v,y,f)#v+32=f #According to this connector, C and F can be converted to each other constant(w,9)#w:9 constant(x,5)#x:5 constant(y,32)#y:32 def constant(connector,value): #No constraints are imposed, only the value of the connector is set constraint={} connector['set_val'](constraint,value) return constraint
Here, we take the connector without constraint ({}) as a constant, because it will not be affected during forget. Therefore, we specially use a function to set it.

This formula needs to be translated to become the ternary constraint we already have. (9 and 5 may be separated because the author wants to add a little difficulty)
multiplier(c,w,u)#c*9=u multiplier(v,x,u)#v*5=u


These two formulas actually make v represent 9 / 5 Celsius, so that two connector s are connected to one.
adder(v,y,f)

This formula is actually the final formula.
Through these constraints, we successfully built the final formula.
summary

Converter network
By constructing the constraint network, we make the information on C spread to F through three-layer constraints, and vice versa. This sub architecture provides more convenience than simple function mapping. At the same time, we only need to construct a larger network to change the complexity of the formula.