I used to meet a problem, in a comment's system, the operator need to dynamic adjust the factor to influence the weight of the comment, and then sort the comments by the weights.
Code in this blog has been tested in Python3.7.
Problem
In a comment’s system, the operator need to dynamic adjust the factor to influence the weight of the comment, and then sort the comments by the weights.
For example, there are many comments in this data structure:
Sometimes, the operator want to modify the algorithm, for example: 1. modify the factor user_weight to be user_weight * 2, and the factor likes to be likes * 2. 2. Create a new factor specify the expression for every topic.
In this time, the old for-loop function can not work anymore. We have to refactor the implementation. The new function need input an expression, and a list of topics, then return the list of topics with the weights.
How to parse the expression
In my college life, we have been taught with Reverse Polish Notation to get the value of expression. And we also learned Principles of Compiler Design. we can parse the expression to an Abstruct Syntax Tree (AST).
This picture is the AST of the expression (a+b)*(c-d)). Once we were being told the exact value of every variable, we could get the result.
Well, it seems we don’t need to parse the expression by ourselves, because the Python has a built-in module called ast.
defmake_calculate(node, data): def_eval(node): ifisinstance(node, ast.Expression): return _eval(node.body) elifisinstance(node, ast.Name): # Get the name `a, b, c, d`, return value return data[node.id] elifisinstance(node, ast.Num): # Get the number return node.n elifisinstance(node, ast.BinOp): # Get the binary operator, + - * / return BINOPS[type(node.op)](_eval(node.left), _eval(node.right)) else: raise Exception('Unsupported type {}'.format(node)) return _eval(node)
make_calculate(_node, _data) # The result is -5
The final solution
We could ask user to give us a simple expression, and then we can get the weight by this expression for every topic.
def_eval(node): ifisinstance(node, ast.Expression): return _eval(node.body) elifisinstance(node, ast.Str): # node.s will be 'user_weight', 'likes', or 'time_offset', # we can use get_user_weight, get_likes, to get the topic property value return TOPIC_UNIT[node.s](topic) elifisinstance(node, ast.Num): return node.n elifisinstance(node, ast.BinOp): return BINOPS[type(node.op)](_eval(node.left), _eval(node.right)) else: raise Exception('Unsupported type {}'.format(node)) return _eval(node.body)
for _topic in topics: _topic['weight'] = make_calculate(_node, _topic)
Summary
What we have done to simplify the real problem: we introduced a new input for operator to let him/her to decide the topic weight, and even more, we decoupled the topic weight calculation from the topic data. This could help a lot when we need to add a new property or modify the expression.