[Java 8] (9) Lambda expression optimization for recursion (bottom)-Use Memo mode (memoization pattern).

Source: Internet
Author: User
Tags map data structure

Improve performance with Memo mode (memoization pattern)

The pattern, in other word, is to cache the results that require a lot of computation and then get it right the next time you need it. Therefore, it is sufficient to use only one map at the bottom.

However, it is important to note that this pattern is useful only if one set of parameters corresponds to the same value.

In many algorithms, this model is widely used in algorithms such as divide-and-conquer method and dynamic programming (programming). Dynamic programming, in the process of finding the optimal solution, will decompose the original task into several sub-tasks, which will inevitably decompose itself into smaller tasks. As a result, there will be quite a lot of repetitive small tasks that need to be solved on the whole. Obviously, when the input parameters are the same, a task only needs to be solved once, and the result is saved after the solution. The next time you need to solve this task, you will first query whether the task has been resolved, if the answer is yes, then only need to directly return the result is OK.

It is such a simple optimization measure that it is often possible to change the time complexity of the code from exponential to linear.

Take a classic pole-cutting problem (rod cutting problem) (or more formal definition here: Wikipedia) as an example of how to implement a memo pattern in conjunction with a lambda expression.

First, give a brief account of the background of the problem.

A company would wholesale some rods (rod) and then retail them. But as the lengths of the rods are different, the prices that can be sold are different. Therefore, in order to maximize profits, it is necessary to combine the length price information to determine what length should be cut to achieve maximum profit.

For example, the following code:

  List<integer> pricevalues = arrays.aslist (2, 1, 1, 2, 2, 2, 1, 8, 9, 15);     

The expression means: the length of 1 rod can sell 2 yuan, the length of 2 of the rod can sell 1 yuan, and so on, the length of 10 of the rod can sell 15 yuan.

When the length of the rod to be cut is 5 o'clock, there are up to 16 cutting methods (2^ (5-1)). As shown below:

For this issue, the dynamic programming algorithm can be implemented without considering the use of the memo pattern as follows:

Publicint Maxprofit (Finalint length) {int profit= (length<= Prices. Size ())? Prices. Get (length-1): 0; for (int I 1; I < length; I++) { int pricewhencut = Maxprofit (i) + maxprofit (length -i); Span class= "K" style= "font-weight:bold;" >if (Profit < pricewhencut) profit = Pricewhencut; } return profit;}         

And from the above program can be found that there are many duplicate sub-problems. These repetitive sub-problems are constantly tangled, losing a lot of unnecessary performance. The lengths of the rods are 5 and 22 o'clock respectively, and the running time is 0.001 seconds and 34.612 seconds respectively. It can be seen that when the length of the rod increases, the performance decreases very very significantly.

Because the principle of Memo mode is very simple, so it is very simple to implement, only need to add a map read operation to the head of the above Maxprofit method and judge the result. But if you do, the reusability of the code will not be very good. Every place that needs to use the memo pattern needs to write the judgment logic separately, so is there a common approach? The answer is yes, by virtue of the power of lambda expressions, we assume that there is a static method callmemoized to find the optimal solution by passing in a policy and input values:

Publicint Maxprofit (Finalint rodlenth) {Return Callmemoized ((Finalfunction<Integer,integer> func,FinalInteger length)-> {int profit= (length<= Prices. Size ())? Prices. Get (length-1):0;Forint i = Span class= "M" style= "color: #945277;" >1; I < length; I++) {int pricewhencut = func. Apply (i) + func.apply ( Length -i); if (Profit < Pricewhencut) profit = pricewhencut;} return profit; }, Rodlenth),               

Let's take a closer look at the intent of this piece of code. The first parameter type that the Callmemoized method accepts is this:

 public static <t, r> r Callmemoized (final bifunction<function<t,r>, T, r> function, final t input)               

The parameter function of the bifunction type actually encapsulates a policy, which has three parts:

    1. Function: The answer r is obtained by passing in the parameter T. This int priceWhenCut = func.apply(i) + func.apply(length - i) can be seen from the very obvious point of the code. Think of it as a doorway to a memo.
    2. T: represents the parameter T required to solve the problem.
    3. R: Represents the answer to the question R.

The above T and R all refer to the type.

Let's look at the implementation of the Callmemoized method:

PublicClassMemoizer {PublicStatic<TR>RCallmemoized (Finalbifunction<function<TR>,TR>functionFinalTInput) {function<TR> memoized=Newfunction<TR> () {PrivateFinalmap<TR> Store=Newhashmap<> (); public r apply (final t input) { Span class= "K" style= "font-weight:bold;" >return store.computeifabsent (Input, key -.apply ( Span class= "NB" style= "color: #0086b3;" >this, key)); } }; return memoized.apply ( Input); }}

In this method, the implementation of an anonymous function interface is first declared. It defines the core---map structure of the memo pattern. Then in its apply method, the following logic is completed with a new Computeifabsent method added to the map interface in Java 8:

    1. Check if the value that corresponds to input in the above code is present in the underlying map of the memo by passing in the key
    2. If present, skip to step 4
    3. If it does not exist, the value corresponding to the key is computed according to the second parameter of computeifabsent (a lambda expression)
    4. Returns the resulting value

Specific to the method of the source code:

DefaultV Computeifabsent (K Key,function<? SuperK? ExtendsV> mappingfunction) {objectsv V; if ((v = get (key)) == null) { V NewValue; if ((newvalue = Mappingfunction.apply (Key) != null) {put (key, NewValue); return newvalue;} } return v;}          

key-Function.apply (this, key) , where this is very ingenious, it actually points to this anonymous function instance that holds the map structure. It is passed into the algorithm policy as the first parameter, and the key to be solved is passed into the algorithm policy as the second parameter. Here the so-called algorithm strategy, is actually called callmemoized method, the incoming form is bifunction<function<t,r>, T, R> The parameter of the .

Therefore, all sub-problems are solved only once. After you get the answer to the sub-question, the answer is placed in the map data structure for future use. This is how you implement the memo pattern with lambda.

The code above may seem a bit weird, which is normal. When you read them over and over again, and you can rewrite them with your own thoughts, that is, when you have a deeper understanding of lambda expressions.

After using memo mode, the bar length still takes 5 and 22 o'clock, resulting in a running time of 0.050 seconds and 0.092 seconds, respectively. When the length of the rod increases, the performance does not fall as much as before. This is entirely thanks to the memo mode, where all tasks are only run once.

[Java 8] (9) Lambda expression optimization for recursion (bottom)-Use Memo mode (memoization pattern).

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.