about | help
CodingBat code practice

Code Help and Videos >

 Java Functional Mapping

Related practice problems: Functional-1

Java functional/lambda code is kind of magical, able to solve certain problem types with just 1 or 2 lines of code.

replaceAll() Method

In general, "mapping" is a strategy that starts with a collection of items, and runs a function on each item individually to compute a new value for that item. The Java replaceAll() method is an easy way to do mapping.

As a first example, we'll use the "doubling" problem: given a list of integers, double each integer in the list. Here is a mapping solution:

public List<Integer> doubling(List<Integer> nums) {
  nums.replaceAll(n -> n * 2);
  return nums;
}

Lambda Syntax

How does the above code work? First look at this snippet of code:   n -> n * 2

Mapping uses a little function that takes in one item and computes the new value for that item. For the doubling problem, we want a function that takes in a single Integer and returns double its value.

The code snippet n -> n * 2 is that function in "lambda" syntax: take in a number and return it multiplied by 2.

The first "n" is the parameter name, followed by "->" and then the expression for the new value. Lambdas can be more complex, but they work great for simple little functions like this. The data type, Integer in this case, is determined by the lambda's context.

replaceAll() + Lambda

The method list.replaceAll(lambda) calls the provided lambda function once for each item in the collection to compute each item's new value which is stored back into the collection. ReplaceAll() changes the original collection in-place and does not return anything. Therefore, after the collection is changed, we need the final return nums;

With this functional/mapping solution, we don't have to write any sort of loop to iterate over the collection, and we don't mess with index numbers. We provide the lambda of what we want done to the items, and replaceAll() does all the bookkeeping for us. Nice!

Mapping replaceAll() Practice

The doubling problem required just 1 line of code to do the work, and a similar style will work for any problem where we want to replace each item with a computed value. Each of these problems puts the main work in 1 line of code:

practice: doubling - the doubling problem shown above

practice: addStar - add "*" at end of each string

practice: copies3 - concatenate a string together 3x

replaceAll() Limitations

Limitation 1: since replaceAll() stores the new value back into the original collection, it only works if the new value is the same type as the original, e.g. int → int, or String → String. Fortunately, that's the common case. To change type, use streams (below).

Limitation 2: mapping works best where each item is handled on its own. It's not easy to make a relative reference like an element "to the left" of another. For a relative problem like that, a traditional loop is probably better.

Loop vs. Lambda Comparison

Here's a non-lambda solution to the doubling problem, it's a lot longer!

public List<Integer> doubling(List<Integer> nums) {
  List<Integer> result = new ArrayList<>();
  for (Integer n:nums) {
    result.add(n * 2);
  }
  return result;
}

Lambdas don't work for all cases, but for the pattern of transforming each element in a collection, lambdas work great.

Java Functional Stream

The Java "stream" classes provide another, more flexible way to do mapping. Here is the solution to the doubling problem written with the stream system:

public List<Integer> doubling(List<Integer> nums) {
  return nums.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());
}

The stream() call sets up the list for mapping. That call is followed by any number of map(lambda) calls to map (or filter) the item values as we have done. Finally the collect() call collapses the stream down to a list or set or whatever, which is returned. The stream system returns a new collection rather than changing the original. The stream system has many features, but it's a bit wordy. For common, simple problems, I'm happy to use simple old replaceAll().

Editorial aside: the stream code would be shorter if there were a stream method like toList(), a very common case, instead of requiring the lengthy collect( ... ) phrase. Perhaps I'll send in a suggestion for the next version of Java.

For more about streams, see the Java Stream Docs

Next Step: Filtering

This section is just about mapping. The next step is filtering.

CodingBat.com code practice. Copyright 2017 Nick Parlante.