Search

Dark theme | Light theme
Showing posts with label Java 14. Show all posts
Showing posts with label Java 14. Show all posts

June 11, 2020

Java Joy: Reapply Function With Stream iterate

In Java we can use the iterate method of the Stream class to create an unbounded stream based on function invocations. We pass to the iterate method an initial value and a function that can be applied to the value. The first element in the unbounded stream is the initial value, the next element is the result of the function invocation with as argument the value from the previous element and this continues for each new element. Suppose we have a function expressed as lambda expression i -> i + 2. When we use this lambda expression with the iterate method and a initial value of 1 we get a stream of 1, 1 -> 1 + 2, 3 -> 3 + 2, ....

As we get an unbounded stream we must for example use limit to get the values we want from the stream. But we can also use an extra argument for the iterate method that is a Predicate definition. The iterate method will provide elements as long as the result of the Predicate is true. This way we the result of the iterate method is a bounded stream.

In the following Java example we use the iterate method with different arguments and lambda expressions:

package mrhaki.stream;

import java.math.BigInteger;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toUnmodifiableList;

public class Iterate {

    public static void main(String[] args) {
        // Create unbounded stream with odd numbers.
        var odds = Stream.iterate(1, i -> i + 2);

        // We use limit(5) to get the first 10 odd numbers from the unbounded stream.
        assert odds.limit(5).collect(toUnmodifiableList()).equals(List.of(1, 3, 5, 7, 9));

        // Create stream with even numbers, but here we use a predicate as
        // second argument to determine that we stop after value 10.
        var evens = Stream.iterate(0, i -> i <= 10, i -> i + 2);
        assert evens.collect(toUnmodifiableList()).equals(List.of(0, 2, 4, 6, 8, 10));


        // Define infinite stream with growing string.
        // The first element is ar, next argh, then arghgh etc.
        var pirate = Stream.iterate("ar", s -> s + "gh");

        // We get the 5-th element for a crumpy pirate.
        var crumpyPirate = pirate.skip(4).findFirst().get();
        assert crumpyPirate.equals("arghghghgh");


        // Function that returns the given amount
        // plus interest of 1.25%.
        UnaryOperator<Double> cumulativeInterest = amount -> amount + (amount * 0.0125);

        // Lazy sequence where each entry is the
        // cumulative amount with interest based
        // on the previous entry.
        // We start our savings at 500.
        var savings = Stream.iterate(500d, cumulativeInterest);

        // First element is start value, so we skip first five elements
        // to get value after 5 years.
        assert savings.skip(5).findFirst().get() == 532.0410768127441;

        
        // Define infinite unbounded stream
        // where each element is the doubled value of the previous element.
        var wheatChessboard = Stream.iterate(BigInteger.valueOf(1), value -> value.add(value));

        // Sum of all values for all chessboard squares is an impressive number.
        var square64 = wheatChessboard.limit(64).reduce(BigInteger::add).get();
        assert square64.equals(new BigInteger("18446744073709551615"));
    }
}

Written with Java 14.

June 10, 2020

Java Joy: Infinite Stream Of Values Or Method Invocations

In Java we can use the generate method of the Stream class to create an infinite stream of values. The values are coming from a Supplier instance we pass as argument to the generate method. The Supplier instance usually will be a lambda expression. To give back a fixed value we simply implement a Supplier that returns the value. We can also have different values when we use a method that returns a different value on each invocation, for example the randomUUID method of the UUID class. When we use such a method we can create the Supplier as method reference: UUID::randomUUID.

The generate method returns an unbounded stream. We must use methods like limit and takeWhile to get a bounded stream again. We must use findFirst or findAny to terminate the unbounded stream and get a value.

In the following example we use the generate method with a Supplier that returns a repeating fixed String value and some different values from invoking the now method of LocalTime:

package mrhaki.stream;

import java.time.LocalTime;
import java.util.List;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toUnmodifiableList;

public class Generate {
    
    public static void main(String[] args) {
        // Create a infinite stream where each item is the string value "Java".
        // We let the supplier function return a fixed value "Java".
        assert Stream.generate(() -> "Java")
                     .limit(4)
                     .collect(toUnmodifiableList()).equals(List.of("Java", "Java", "Java", "Java"));

        // Create an infinite stream of function invocations of LocalTime.now().
        // The supplier function returns a different value for each invocation.
        var currentTimes = Stream.generate(LocalTime::now)
                                 .limit(2)
                                 .collect(toUnmodifiableList());
        assert currentTimes.get(0).isBefore(currentTimes.get(1));

        // Create a list of 100 time values where each value should be later than the next.
        var timeSeries = Stream.generate(LocalTime::now).limit(100).collect(toUnmodifiableList());
        assert latestTime(timeSeries).equals(timeSeries.get(timeSeries.size() - 1));
    }

    /**
     * Get the latest time from a serie of time values.
     *
     * @param times List with time values.
     * @return Latest time value from the collection.
     */
    private static LocalTime latestTime(List<LocalTime> times) {
        return times.stream().reduce((acc, time) -> {
            if (acc.isAfter(time)) { return acc; } else { return time; }
        }).get();
    }
}

Written with Java 14.

April 11, 2020

Java Joy: Using Functions To Replace Values In Strings

Since Java 9 we can use a function as argument for the Matcher.replaceAll method. The function is invoked with a single argument of type MatchResult and must return a String value. The MatchResult object contains a found match we can get using the group method. If there are capturing groups in the regular expression used for replacing a value we can use group method with the capturing group index as argument.

In the following example we use the replaceAll method and we use a regular expression without and with capturing groups:

package mrhaki.pattern;

import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

public class Replace {
    public static void main(String[] args) {
        // Define a pattern to find text between brackets.
        Pattern admonition = Pattern.compile("\\[\\w+\\]");
        
        // Sample text.
        String text = "[note] Pay attention. [info] Read more.";
        
        // Function to turn a found result for regular expression to upper case.
        Function<MatchResult, String> bigAdmonition = match -> match.group().toUpperCase();
        
        assert admonition.matcher(text).replaceAll(bigAdmonition).equals("[NOTE] Pay attention. [INFO] Read more.");

        
        // Pattern for capturing numbers from string like: run20=390ms.
        Pattern boundaries = Pattern.compile("run(\\d+)=(\\d+)ms");

        // Function to calculate seconds from milliseconds.
        Function<MatchResult, String> runResult = match -> {
            double time = Double.parseDouble(match.group(2));
            return "Execution " + match.group(1) + " took " + (time / 1000) + " seconds.";
        };

        assert boundaries.matcher("run20=390ms").replaceAll(runResult).equals("Execution 20 took 0.39 seconds.");
    }
}

Written with Java 14.

April 9, 2020

Java Joy: Using Named Capturing Groups In Regular Expressions

In Java we can define capturing groups in regular expression. We can refer to these groups (if found) by the index from the group as defined in the regular expression. Instead of relying on the index of the group we can give a capturing group a name and use that name to reference the group. The format of the group name is ?<name> as first element of the group definition. The name of the group can be used with the group method of the Matcher class. Also we can use the name when we want to reference the capturing group for example with the replaceAll method of a Matcher object. The format is ${name} to reference the group by name. Finally we can use a named capturing group also as backreference in a regular expression using the syntax \k<name>.

In the following example we define a regular expression with named groups and use them with several methods:

package mrhaki.pattern;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NamedPatterns {
    public static void main(String[] args) {
        // Define pattern and use names for the capturing groups.
        // The first group has the name project, second org unit number and finally a project number.
        // The format is ?<name>.
        // To make sure the separator is - or / (and not a combination)
        // we use group with name sep and use the backreference \k<sep> to match.
        Pattern issuePattern = Pattern.compile("(?<project>[A-Z]{3})(?<sep>[-/])(?<org>\\w{3})\\k<sep>(?<num>\\d+)$");

        // Create Matcher with a string value.
        Matcher issueMatcher = issuePattern.matcher("PRJ-CLD-42");

        assert issueMatcher.matches();
        
        // We can use capturing group names to get group.
        assert issueMatcher.group("project").equals("PRJ");
        assert issueMatcher.group("org").equals("CLD");
        assert issueMatcher.group("num").equals("42");
        
        // Using separator / also matches.
        assert issuePattern.matcher("EUR/ACC/91").matches();
        
        // But we cannot mix - and /.
        assert !issuePattern.matcher("EUR-ACC/91").matches();

        // Backreferences to the capturing groups can be used by
        // their names, using the syntax ${name}.
        assert issueMatcher.replaceAll("${project} ${num} in ${org}.").equals("PRJ 42 in CLD.");
    }
}

Written with Java 14.