Java Functional Programming--Stepik

課程地址:https://stepik.org/course/1595
代碼地址:https://github.com/zzaoen/StepikJavaFunctionalProgramming

簡單題

Basic.java(在倉庫中,Basic.java文件中包含了這些方法的實現)

2.2.1 Write a lambda expression that accepts two integers arguments and returns max of them.

# 1
IntBinaryOperator operator = (x, y) -> x > y ? x : y;

# 2
IntBinaryOperator operator = Math::max;

2.2.2 Write a lambda expression that accepts a long value and returns a next even number.

# 1
IntUnaryOperator operator = x -> x + 2 - (x % 2);

# 2
IntUnaryOperator operator = x -> (x | 1) + 1;

2.3 Write a lambda expression that accepts seven (!) string arguments and returns a string in upper case concatenated from all of them (in the order of arguments).

# 1
(a, b, c, d, e, f, g) -> (a + b + c + d + e + f + g).toUpperCase();

# 2
(a, b, c, d, e, f, g) -> Stream.of(a, b, c, d, e, f, g)
    .reduce("", String::concat)
    .toUpperCase();

在Java中沒有現成可以接受7個字符串並返回1個字符串的方法,不過我們可以手動寫一個函數式接口滿足這個條件。

@FunctionalInterface
interface SevenArgsFunction<A, B, C, D, E, F, G, R>{
    R apply(A a, B b, C c, D d, E e, F f, G g);
}
//調用
SevenArgsFunction<String, String, String, String, String, String, String, String> function = (a, b, c, d, e, f, g) -> (a + b + c + d + e + f + g).toUpperCase();
function.apply("a", "b", "c", "d", "e", "f", "g");

2.4 Write a lambda expression that accepts two long arguments as a range borders and calculates (returns) production of all numbers in this range (inclusively).

# 1
LongBinaryOperator operator = (x, y) -> {
    int res = 1;
    while (x < y)
        res *= y--;
    return res;
};

# 2
LongBinaryOperator operator = (x, y) -> LongStream.rangeClosed(x, y)
    .reduce(1L, (acc, temp) -> acc * temp);

# 3
LongBinaryOperator operator = (x, y) -> LongStream.rangeClosed(x, y)
    .reduce(1L, Math::multiplyExact);

2.5 Write a lambda expression that accepts a list of strings and returns new list of distinct strings (without repeating). The order of elements in the result list may be any (elements will be sorted by the testing system).

# 1
Function<List<String>, List<String>> listConsumer = strings -> strings.stream()
    .distinct()
    .collect(Collectors.toList());

# 2
Function<List<String>, List<String>> listFunction = strings -> new ArrayList(new HashSet(strings));

2.6.1 Using closure write a lambda expression that calculates a∗x2+b∗x+c where a, b, c are context final variables. Note, the result is double.

//final int a = 1, b = 2, c = 3;
DoubleUnaryOperator operator = x -> a * x * x + b * x + c;

Note

Operator和Function功能類似,都是接受輸入並返回結果。

IntUnaryOperator和IntBinaryOperator分別接受1個和2個int類型的輸入,執行後返回一個int類型的結果。

Function<Integer, Integer>和BinaryFunction<Integer, Integer, Interger>分別接受1個和2個Integer類型的輸入,執行後返回一個Integer類型的結果。也有和類型綁定的Function,比如IntFunction, LongFunction, DoubleFunction等,R指定返回的類型。

2.6.2 Using closure write a lambda expression that adds prefix (before) and suffix (after) to its single string argument; prefix and suffix are final variables and will be available in the context during testing.

//final String prefix = "pre-", suffix = "-suf";
# 1
Function<String, String> stringFunction = s -> prefix + s.trim() + suffix;

# 2
Function<String, String> stringFunction = s -> String.join("", prefix, s.trim(), suffix);

2.9 Behaviour parametrization with lambda expressions. 1. get list with all non-empty accounts (balance > 0) and save it to the variable nonEmptyAccounts; 2. get all non-locked accounts with too much money (balance >= 100 000 000) and save it to the variable accountsWithTooMuchMoney.

//Account account1 = new Account("111", 100000001, false);
//Account account2 = new Account("222", 0, false);
//Account account3 = new Account("333", 1000, false);
//List<Account> accounts = Arrays.asList(account1, account2, account3);
# 1
List<Account> balanceOverZeroList = accounts.stream()
                .filter(account -> account.getBalance() > 0)
                .collect(Collectors.toList());
List<Account> list = accounts.stream()
                .filter(account -> (account.getBalance() >= 100000000 && !account.isLocked()))
                .collect(Collectors.toList());

# 2
Predicate<Account> balanceOverZeroPred = (Account account) -> account.getBalance() > 0;
Predicate<Account> balanceTooMuchPred = (Account account) -> account.getBalance() > 100000000;
Predicate<Account> isLockedPred = (Account account) -> !account.isLocked();

List<Account> balanceOverZeroList = accounts.stream()
                .filter(balanceOverZeroPred).collect(Collectors.toList());
List<Account> list = accounts.stream()
 			   .filter(balanceTooMuchPred.and(isLockedPred))
                .collect(Collectors.toList());

2.10 Write your own functional interface (TernaryIntPredicate) and use it with a lambda expression. The interface must have a single non-static (and non-default) method test with three int arguments that returns boolean value. The lambda expression has to return true if all passed values are different otherwise false.

@FunctionalInterface
interface TernaryIntPredicate {
    boolean test(int arg1, int arg2, int arg3);
}
//調用
TernaryIntPredicate allValuesAreDifferentPredicate = (x, y, z) -> x != y && y != z && x != z;

2.11 Understanding of the function composition

composeandThen

default IntUnaryOperator compose(IntUnaryOperator before) {
	Objects.requireNonNull(before);
	return (int v) -> applyAsInt(before.applyAsInt(v));
}
default IntUnaryOperator andThen(IntUnaryOperator after) {
	Objects.requireNonNull(after);
	return (int t) -> after.applyAsInt(applyAsInt(t));
}

compose(before): Returns a composed operator that first applies the {@code before} operator to its input, and then applies this operator to the result.

a.compse(b)是b先執行並將結果作爲參數帶入到a中執行。

andThen(after): Returns a composed operator that first applies this operator to its input, and then applies the {@code after} operator to the result.

a.addThen(b)是a先執行並將結果作爲參數帶入到b中執行。

IntUnaryOperator mult2 = num -> num * 2;
IntUnaryOperator add3 = num -> num + 3;
IntUnaryOperator combinedOperator = add3.compose(mult2.andThen(add3)).andThen(mult2);

mult2.andThen(add3) 先mult2,然後andThen 5 * 2 + 3 = 13
add3.compose(13) 13 + 3 = 16
16.addThen(mult2) 16 * 2 = 32

2.12 Write the disjunctAll method that accepts a list of IntPredicate’s and returns a single IntPredicate. The result predicate is a disjunction of all input predicates.

//List<IntPredicate> predicates = ...;
# 1
IntPredicate resPredicate = x -> false;
predicates.forEach(predicate -> resPredicate.or(predicate));

# 2
IntPredicate resPredicate = predicates.stream().reduce(x -> false, IntPredicate::or);

# 3
IntPredicate resPredicate = predicates.stream().reduce(IntPredicate::or).orElse(x -> false);

2.17 Write a method using Stream API to check the input number is prime or not.

# 1
boolean res = !LongStream.range(2, number/2+1).anyMatch(x -> number % x == 0);
    
# 2
boolean res = LongStream.range(2, number / 2 + 1).noneMatch(i -> number % i == 0);

# 3
boolean res = new BigInteger(String.valueOf(number)).isProbablePrime(1)    

2.18 Create a stream that will detect bad words in a text according to a bad words list. All words in the text are divided by whitespaces (always only one whitespace between two words).

//String text = ...;
//List<String> badWords = ...;
# 1
Stream<String> res = badWords.stream().filter(text::contains).sorted().distinct();

# 2
Stream<String> res = Arrays.stream(text.split("\\s")).filter(badWords::contains).sorted().distinct();

# 3
Stream<String> res = Arrays.stream(text.split("\\s")).filter(s -> badWords.stream().anyMatch(badWord -> badWord.equals(s))).sorted().distinct();

第一種方法是將List badWords中的每一個元素通過String.contains()方法與text比較;

第二種方法是將text按照空格拆分,得到的每一個元素與通過List.contains()方法與badWords比較;

第三種方法和第二種類似。

2.19 You have two IntStream. The first stream contains even numbers and the second stream contains odd numbers. Create the third stream that contains numbers from both streams which is divisible by 3 and 5. Two first suitable numbers in the sorted list must be skipped.

//IntStream evenStream = ...;
//IntStream oddStream = ...;
# 1
IntStream res = IntStream.concat(evenStream, oddStream)
                .filter(x -> x % 15 == 0)
                .sorted()
                .skip(2);

# 2
List<Integer> resList = evenStream.filter(x -> x % 15 == 0)
                .collect(LinkedList::new, List::add, List::addAll);
resList.addAll(oddStream.filter(x -> x % 15 == 0)
                .collect(LinkedList::new, List::add, List::addAll));
IntStream res = resList.stream().mapToInt(x -> x).sorted().skip(2);

第二個方法中,collect()方法不能直接通過collect(Collectors.toList())將結果轉成List的原因是evenStream是IntStream,IntStream的collect()方法定義如下:

<R> R collect(Supplier<R> supplier,
                  ObjIntConsumer<R> accumulator,
                  BiConsumer<R, R> combiner);

而Stream的collect()方法定義如下:

<R, A> R collect(Collector<? super T, A, R> collector);

如果不是IntStream,而是Stream,那麼調用collect(Collectors.toList())就不會有問題了。

List<Integer> list = Arrays.asList(1, 2, 3);
        List<Integer> collect = list.stream().filter(x -> x > 2).collect(Collectors.toList());

2.20 Write a method to calculate a factorial value using stream.

//long n = 5;
# 1
long res = LongStream.rangeClosed(2, n).reduce(1L, Math::multiplyExact);

# 2
long res = LongStream.rangeClosed(2, n).reduce(1L, (x, y) -> x * y);    

2.21 Write a method for calculating the sum of odd numbers in the given interval (inclusively) using Stream API.

# 1
long res = LongStream.rangeClosed(start, end)
                .filter(x -> x % 2 == 1)
                .sum();

# 2
long res = LongStream.rangeClosed(start, end)
                .filter(x -> x % 2 == 1)
                .reduce(0L, Long::sum);

2.31 Create a parallel LongStream for filtering prime numbers in the given range (inclusively).

LongStream res = LongStream.rangeClosed(rangeBegin, rangeEnd)
                .parallel()
                .filter(x -> (x & 1) != 0);

chain of responsibility

./chainofresp/TestCOR.java

2.14 The chain of responsibility pattern in the functional style

stream operations

2.15 knowledge of stream operations
intermediate: always lazy and return a new stream
e.g.: limit, distinct, sorted, filter, map, skip

terminal: not lazy and return a value or produce a side-effect
e.g.: average, max, reduce, anyMatch, collect, count

flatmap

TestFlatMap.java

2.23 Calculates the number of employees with salary >= threshold (only for 111- departments)

//List<Department> departments = ...;
//long threshold = ...;

# 1
long res = departments.stream()
                .filter(department -> department.getCode().startsWith("111-"))
                .flatMap(department -> department.getEmployees().stream())
                .filter(employee -> employee.getSalary() >= threshold)
                .count();

# 2
long res = departments.stream()
                .filter(department -> department.getCode().startsWith("111-"))
                .flatMapToLong(department -> department.getEmployees().stream().mapToLong(Employee::getSalary))
                .filter(salary -> salary >= threshold)
                .count();

# 3
long res = departments.stream()
                .filter(department -> department.getCode().startsWith("111-"))
                .map(Department::getEmployees)
                .flatMap(list -> list.stream()) //.flatMap(List::stream)
                .map(Employee::getSalary)
                .filter(salary -> salary >= threshold)
                .count();

在第二種方法中有一句調用

flatMapToLong(department -> department.getEmployees().stream().mapToLong(Employee::getSalary))

flatMapToLong和mapToLong,這兩個方法都定義在Stream類中

LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);

從作用上來理解,mapToLong()方法得到對象可以直接得到long類型的屬性,比如mapToLong(Employee::getSalary),getSalary返回的是long類型的值。而flatMapToLong()方法作用的對象內部包含一個集合,從集合包含的對象可以得到long類型的屬性,比如通過department對象獲得一個Employee的List,通過Employee對象獲得long類型的salary。

2.24 Write a method using Stream API that calculates the total sum of canceled transactions for all non-empty accounts (balance > 0).

# 1
long res = accounts.stream()
                .filter(account -> account.getBalance() > 0)
                .flatMap(account -> account.getTransactions().stream())
                .filter(transaction -> transaction.getState().equals("CANCELED"))
                .mapToLong(Transaction::getSum)
                .sum(); //.reduce(0L, Long::sum);    

# 2
long res = accounts.stream()
                .filter(account -> account.getBalance() > 0)
                .flatMapToLong(accout -> accout.getTransactions()
                        .stream()
                        .filter(transaction -> transaction
                                .getState().equals("CANCELED"))
                        .mapToLong(Transaction::getSum))
                .sum()

Collectors

TestCollectors.java

2.25 Existing collectors in Java8

Collectors.toCollection
Collectors.toList
Collectors.toCouncurrentMap
Collectors.toMap
Collectors.toSet

Collectors.partitioningBy
Collectors.groupingBy
Collectors.reducing
Collectors.summingDouble
Collectors.maxBy

2.26 Write a collector that evaluates the product of squares of integer numbers in a Stream.

//List<Integer> numbers = Arrays.asList(1, 2, 3);
# 1
long res = numbers.stream().collect(reducing(1, (acc, x) -> acc * x * x));

# 2
long res = numbers.stream().collect(reducing(1, x -> x * x, Math::multiplyExact));

2.27 Write a collector that partitions all words in a stream into two groups: palindromes (true) and usual words (false).

//String[] words = new String[]{"aaa", "aa", "a", "ba"};
# 1
Map<Boolean, List<String>> res = Arrays.stream(words)
                .collect(Collectors.partitioningBy(word -> new StringBuilder(word).reverse().equals(word)));

# 2
Map<Boolean, List<String>> res = Arrays.stream(words)
                .collect(Collectors.partitioningBy(word -> word.contentEquals(new StringBuffer(word).reverse())));    

2.28 Write a collector that calculates the total sum of transactions (long type, not integer) by each account (i.e. by account number).

//Transaction transaction1 = new Transaction("123", 100, new Account("111", 1000));
//Transaction transaction2 = new Transaction("f22", 1000, new Account("112", 1000));
//List<Transaction> transactions = Arrays.asList(transaction1, transaction2);
# 1
Map<String, Long> res = transactions.stream()
                .collect(Collectors.groupingBy(x -> x.getAccount().getNumber(), Collectors.summingLong(Transaction::getSum)));

# 2
Map<String, Long> res = transactions.stream()
                .collect(Collectors.toMap(x -> x.getAccount().getNumber(), Transaction::getSum, Long::sum));

2.29 Write a collector that calculates how many times was clicked* each url by users.

LogEntry logEntry1 = new LogEntry("2017", "zz", "www.google.com");
LogEntry logEntry2 = new LogEntry("2018", "ab", "www.baidu.com");
LogEntry logEntry3 = new LogEntry("2018", "ab", "www.google.com");
List<LogEntry> logs = Arrays.asList(logEntry1, logEntry2, logEntry3);
Map<String, Long> res = logs.stream()
                .collect(Collectors.groupingBy(LogEntry::getUrl, Collectors.counting()));

Curry

TestCurry.java

2.33.1 Write a curried form of the function f(x,y,z)=x+y∗y+z∗z∗z using lambda expressions in Java 8 style.

Function<Integer, Function<Integer, Function<Integer, Integer>>> function = x -> y -> z -> x + y * y + z * z * z;
function.apply(2).apply(3).apply(4);

接收3個Integer類型的值,並返回1個Integer類型的值。

Function<Integer, Function<Integer, Function<Integer, Integer>>> function = ...;

第一層可以看成是Function<Integer, Function>,所以第一層調用得到的是結果還是一個Function類型的對象。

Function<Integer, Function<Integer, Integer>> = function.apply(2);

最後一層Function的第二個參數指定了返回值的類型。

2.33.2 Write a curried function (using lambdas) that accepts four string arguments and concatenated all in one string.

Function<String, Function<String, Function<String, Function<String, String>>>> function = str1 -> str2 -> str3 -> str4 -> str1.toLowerCase() + str3.toUpperCase() + str2.toLowerCase() + str4.toUpperCase();
function.apply("aa").apply("bb").apply("cc").apply("dd");

2.34

multifunctionalMapper accepts a list of operators (mappers) and returns a new operator. The returned operator accepts a list of integer numbers and sequentially applies each mapper to each number in the list (performs multiple transformations). The result is a list with transformed values.

Write three functions:

  1. identityTransformation repeats identity transformation three times just for example.
  2. multTwoAndThenAddOneTransformation multiplies by two each integer number and then add one to its.
  3. squareAndThenGetNextEvenNumberTransformation quares each integer number and then calculates the next even number following it
    An input list with integer numbers [1, 2, 3].
    identityTransformation returns the result list [1, 2, 3].
    multTwoAndThenAddOneTransformation returns the result list [3, 5, 7].
    squareAndThenGetNextEvenNumberTransformation returns the result list [2, 6, 10].
# 1
Function<List<IntUnaryOperator>, UnaryOperator<List<Integer>>> multifunctionalMapper = intUnaryOperators -> integers -> {
            List<Integer> resList = new ArrayList<>();
            for(int i : integers){
                for(IntUnaryOperator operator : intUnaryOperators){
                    i = operator.applyAsInt(i);
                }
                resList.add(i);
            }
            return resList;
        };

# 2
Function<List<IntUnaryOperator>, UnaryOperator<List<Integer>>> multifunctionalMapper = intUnaryOperators -> integers -> integers.stream()
                .map(x -> intUnaryOperators.stream()
                        .reduce(IntUnaryOperator::andThen)
                        .get()
                        .applyAsInt(x))
                .collect(Collectors.toList());

UnaryOperator<List<Integer>> identityTransformation = multifunctionalMapper.apply(Arrays.asList(x -> x, x -> x));

UnaryOperator<List<Integer>> multTwoAndThenAddOneTransformation =
                multifunctionalMapper.apply(Arrays.asList(x -> x * 2, x -> x + 1));

UnaryOperator<List<Integer>> squareAndThenGetNextEvenNumberTransformation =
                multifunctionalMapper.apply(Arrays.asList(x -> x * x, x -> x + 2 - (x % 2)));

2.35

reduceIntOperator accepts an initial value (seed) and a combiner function and then returns a new function that combines all values in the given integer range (inclusively) into one integer value (it’s a simple form of reduction).

sumOperator: summing integer values in the given range.

productOperator  : multiplying integer values in the given range.

BiFunc
tion<Integer, IntBinaryOperator, IntBinaryOperator> reduceIntOperator = (seed, operator) -> (left, right) -> IntStream.rangeClosed(left, right).reduce(seed, operator);

IntBinaryOperator sumOperator = reduceIntOperator.apply(0, Integer::sum);
IntBinaryOperator productOperator = reduceIntOperator.apply(1, Math::multiplyExact);

monad

TestMonad.java

2.36 Understanding monads and related things

  • It has a function unit(or pure): takes a value and return a monad wrapping the value
  • It has a function bind(or flatMap): takes a function that (the function) accepts a value and returning a monad
  • Monad is a structure that puts a value in a computational context

In practice we’ll often use an additional method map that may be defined as combination of bind and unit.
map: takes a function that (the function) accepts a value and returns a value.

Java8中現有的monads:

  • Stream

  • CompletableFuture

  • Optional

2.38.1 The method findUserByLogin(String login) that returns an optional value of type Optional.

public static Optional<User> findUserByLogin(String login) {
    //# 1
    Optional<User> res = Optional.empty();
	for(User user : users){
        if(login.equals(user.getLogin())) {
            res = Optional.ofNullable(user);
            break;
        }
    }
    //# 2
    Optional<User> res = users.stream().
                filter(user -> login.equals(user.getLogin())) //filter(user -> Objects.equals(user.getLogin(), login))
                .findFirst();
    return res;
}

2.38.2 Using the method you wrote for finding an user by their login, write a new method printBalanceIfNotEmpty(String userLogin)that prints an account balance for an existing user if balance > 0.

public static void printBalanceIfNotEmpty(String userLogin) {
        //# 1
        findUserByLogin(userLogin).ifPresent(x -> {
            if(x.getAccount() == null)
                return;
            if(x.getAccount().getBalance() > 0)
                System.out.println(x.getLogin() + ": " + x.getAccount().getBalance());
        });

        //# 2
        findUserByLogin(userLogin)
                .map(User::getAccount)
                .map(Account::getBalance)
                .filter(balance -> balance > 0)
                .ifPresent(balance -> System.out.println(userLogin + ": " + balance));
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章