Python | functional programming | implementation of formula constraint

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:
        elif av and cv:
        elif bv and cv:
    def forget_value():
        #Use the forget function for all three abc connectors
        for connector in(a,b,c):
    #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
    #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


Pass has first_ Val observes whether there are known variables. If there are n-1 known variables, the unknown variables can be obtained.


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
        if val is None:
            #If there is no value, set it to value and informant to constraint (through the constraint set by the user)
            if name is not None:
            #Call new for all constraints_ value
            #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
            if name is not None:
                print(name,'is forgotten')
            #Call forget on all constraints_ value
    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:

In the above function, we name a connector name (or anonymous) Connector Method


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.


If the source is correct, the value of the connector is cleared and a clear notification is sent to all participating constraints


Returns whether there is a current value


Connector participation constraints

Connector Data Member


Store the user setting constraints of this connector (if it is set manually, it can be as long as it is an ID)


Store all constraints in which the connector participates (including anonymous constraints derived from the formula)

Normal Function


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)
    #According to this connector, C and F can be converted to each other

def constant(connector,value):
    #No constraints are imposed, only the value of the connector is set
    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)


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


This formula is actually the final formula.

Through these constraints, we successfully built the final formula.


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.

Posted on Mon, 22 Nov 2021 00:51:30 -0500 by BluePhoenixNC