diff --git a/src/main/java/ru/javawebinar/topjava/model/MealTo.java b/src/main/java/ru/javawebinar/topjava/model/MealTo.java index 07f04f8db..35551cda8 100644 --- a/src/main/java/ru/javawebinar/topjava/model/MealTo.java +++ b/src/main/java/ru/javawebinar/topjava/model/MealTo.java @@ -9,7 +9,10 @@ public class MealTo { private final int calories; - private final boolean excess; +// private final AtomicBoolean excess; // filteredByAtomic (or any ref type, e.g. boolean[1]) +// private final Boolean excess; // filteredByReflection +// private final Supplier excess; // filteredByClosure + private boolean excess; public MealTo(LocalDateTime dateTime, String description, int calories, boolean excess) { this.dateTime = dateTime; @@ -18,6 +21,16 @@ public MealTo(LocalDateTime dateTime, String description, int calories, boolean this.excess = excess; } +// for filteredByClosure +// public Boolean getExcess() { +// return excess.get(); +// } + + // for filteredBySetterRecursion + public void setExcess(boolean excess) { + this.excess = excess; + } + @Override public String toString() { return "MealTo{" + diff --git a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java index c29e1fbbb..ef7a69e28 100644 --- a/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java +++ b/src/main/java/ru/javawebinar/topjava/util/MealsUtil.java @@ -7,13 +7,21 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; +import static ru.javawebinar.topjava.util.TimeUtil.isBetweenHalfOpen; public class MealsUtil { - public static void main(String[] args) { + public static void main(String[] args) throws InterruptedException { List meals = Arrays.asList( new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 10, 0), "Завтрак", 500), new Meal(LocalDateTime.of(2020, Month.JANUARY, 30, 13, 0), "Обед", 1000), @@ -24,23 +32,343 @@ public static void main(String[] args) { new Meal(LocalDateTime.of(2020, Month.JANUARY, 31, 20, 0), "Ужин", 410) ); - List mealsTo = filteredByStreams(meals, LocalTime.of(7, 0), LocalTime.of(12, 0), 2000); + final LocalTime startTime = LocalTime.of(7, 0); + final LocalTime endTime = LocalTime.of(12, 0); + + List mealsTo = filteredByStreams(meals, startTime, endTime, 2000); mealsTo.forEach(System.out::println); + + System.out.println(filteredByCycles(meals, startTime, endTime, 2000)); + + // Optional2 recursion + System.out.println(filteredByRecursion(meals, startTime, endTime, 2000)); + System.out.println(filteredBySetterRecursion(meals, startTime, endTime, 2000)); + System.out.println(filteredByRecursionWithCycleAndRunnable(meals, startTime, endTime, 2000)); + + // Optional2 reference type + // System.out.println(filteredByAtomic(meals, startTime, endTime, 2000)); // or boolean[1] + // System.out.println(filteredByReflection(meals, startTime, endTime, 2000)); + + // Optional2 delayed execution + // System.out.println(filteredByClosure(meals, startTime, endTime, 2000)); + System.out.println(filteredByExecutor(meals, startTime, endTime, 2000)); + System.out.println(filteredByLock(meals, startTime, endTime, 2000)); + System.out.println(filteredByCountDownLatch(meals, startTime, endTime, 2000)); + System.out.println(filteredByPredicate(meals, startTime, endTime, 2000)); + System.out.println(filteredByConsumerChain(meals, startTime, endTime, 2000)); + + // Optional2 streams + System.out.println(filteredByFlatMap(meals, startTime, endTime, 2000)); + System.out.println(filteredByCollector(meals, startTime, endTime, 2000)); } public static List filteredByStreams(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { Map caloriesSumByDate = meals.stream() .collect( Collectors.groupingBy(Meal::getDate, Collectors.summingInt(Meal::getCalories)) -// Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) + // Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum) ); return meals.stream() - .filter(meal -> TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .filter(meal -> isBetweenHalfOpen(meal.getTime(), startTime, endTime)) .map(meal -> createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)) .collect(Collectors.toList()); } + public static List filteredByCycles(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + + final Map caloriesSumByDate = new HashMap<>(); + meals.forEach(meal -> caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum)); + + final List mealsTo = new ArrayList<>(); + meals.forEach(meal -> { + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + }); + return mealsTo; + } + + private static List filteredByRecursion(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + ArrayList result = new ArrayList<>(); + filterWithRecursion(new LinkedList<>(meals), startTime, endTime, caloriesPerDay, new HashMap<>(), result); + return result; + } + + private static void filterWithRecursion(LinkedList meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay, + Map dailyCaloriesMap, List result) { + if (meals.isEmpty()) return; + + Meal meal = meals.pop(); + dailyCaloriesMap.merge(meal.getDate(), meal.getCalories(), Integer::sum); + filterWithRecursion(meals, startTime, endTime, caloriesPerDay, dailyCaloriesMap, result); + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + result.add(createTo(meal, dailyCaloriesMap.get(meal.getDate()) > caloriesPerDay)); + } + } + + private static List filteredBySetterRecursion(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + class MealNode { + private final MealNode prev; + private final MealTo mealTo; + + public MealNode(MealTo mealTo, MealNode prev) { + this.mealTo = mealTo; + this.prev = prev; + } + + public void setExcess() { + mealTo.setExcess(true); + if (prev != null) { + prev.setExcess(); + } + } + } + + Map caloriesSumByDate = new HashMap<>(); + Map mealNodeByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + meals.forEach(meal -> { + LocalDate localDate = meal.getDate(); + boolean excess = caloriesSumByDate.merge(localDate, meal.getCalories(), Integer::sum) > caloriesPerDay; + if (TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + MealTo mealTo = createTo(meal, excess); + mealsTo.add(mealTo); + if (!excess) { + MealNode prevNode = mealNodeByDate.get(localDate); + mealNodeByDate.put(localDate, new MealNode(mealTo, prevNode)); + } + } + if (excess) { + MealNode mealNode = mealNodeByDate.remove(localDate); + if (mealNode != null) { + // recursive set for all interval day meals + mealNode.setExcess(); + } + } + }); + return mealsTo; + } + + public static List filteredByRecursionWithCycleAndRunnable(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + Iterator iterator = meals.iterator(); + + new Runnable() { + @Override + public void run() { + while (iterator.hasNext()) { + Meal meal = iterator.next(); + caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + run(); + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + } + } + } + }.run(); + return mealsTo; + } + + /* + private static List filteredByAtomic(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = new HashMap<>(); + Map exceededMap = new HashMap<>(); + + List mealsTo = new ArrayList<>(); + meals.forEach(meal -> { + AtomicBoolean wrapBoolean = exceededMap.computeIfAbsent(meal.getDate(), date -> new AtomicBoolean()); + Integer dailyCalories = caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (dailyCalories > caloriesPerDay) { + wrapBoolean.set(true); + } + if (isBetween(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, wrapBoolean)); // also change createTo and MealTo.excess + } + }); + return mealsTo; + } + + private static List filteredByReflection(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws NoSuchFieldException, IllegalAccessException { + Map caloriesSumByDate = new HashMap<>(); + Map exceededMap = new HashMap<>(); + Field field = Boolean.class.getDeclaredField("value"); + field.setAccessible(true); + + List mealsTo = new ArrayList<>(); + for (Meal meal : meals) { + Boolean mutableBoolean = exceededMap.computeIfAbsent(meal.getDate(), date -> new Boolean(false)); + Integer dailyCalories = caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (dailyCalories > caloriesPerDay) { + field.setBoolean(mutableBoolean, true); + } + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, mutableBoolean)); // also change createTo and MealTo.excess + } + } + return mealsTo; + } + + private static List filteredByClosure(List mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final Map caloriesSumByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + mealList.forEach(meal -> { + caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (isBetween(meal.getTime(), startTime, endTime)) { + mealsTo.add(createTo(meal, () -> (caloriesSumByDate.get(meal.getDate()) > caloriesPerDay))); // also change createTo and MealTo.excess + } + } + ); + return mealsTo; + } + */ + + private static List filteredByExecutor(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws InterruptedException { + Map caloriesSumByDate = new HashMap<>(); + List> tasks = new ArrayList<>(); + final List mealsTo = Collections.synchronizedList(new ArrayList<>()); + + meals.forEach(meal -> { + caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + tasks.add(() -> { + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDate()) > caloriesPerDay)); + return null; + }); + } + }); + ExecutorService executorService = Executors.newCachedThreadPool(); + // https://stackoverflow.com/a/1250668/548473 + executorService.invokeAll(tasks); + executorService.shutdown(); + return mealsTo; + } + + public static List filteredByLock(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws InterruptedException { + Map caloriesSumByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + ExecutorService executor = Executors.newCachedThreadPool(); + ReentrantLock lock = new ReentrantLock(); + lock.lock(); + for (Meal meal : meals) { + caloriesSumByDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); + if (isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) + executor.submit(() -> { + lock.lock(); + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDateTime().toLocalDate()) > caloriesPerDay)); + lock.unlock(); + }); + } + lock.unlock(); + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + return mealsTo; + } + + private static List filteredByCountDownLatch(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws InterruptedException { + Map caloriesSumByDate = new HashMap<>(); + List mealsTo = Collections.synchronizedList(new ArrayList<>()); + CountDownLatch latchCycles = new CountDownLatch(meals.size()); + CountDownLatch latchTasks = new CountDownLatch(meals.size()); + for (Meal meal : meals) { + caloriesSumByDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); + if (isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) { + new Thread(() -> { + try { + latchCycles.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDateTime().toLocalDate()) > caloriesPerDay)); + latchTasks.countDown(); + }).start(); + } else { + latchTasks.countDown(); + } + latchCycles.countDown(); + } + latchTasks.await(); + return mealsTo; + } + + public static List filteredByPredicate(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesSumByDate = new HashMap<>(); + List mealsTo = new ArrayList<>(); + + Predicate predicate = b -> true; + for (Meal meal : meals) { + caloriesSumByDate.merge(meal.getDateTime().toLocalDate(), meal.getCalories(), Integer::sum); + if (TimeUtil.isBetweenHalfOpen(meal.getDateTime().toLocalTime(), startTime, endTime)) { + predicate = predicate.and(b -> mealsTo.add(createTo(meal, caloriesSumByDate.get(meal.getDateTime().toLocalDate()) > caloriesPerDay))); + } + } + predicate.test(true); + return mealsTo; + } + + public static List filteredByConsumerChain(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Map caloriesPerDays = new HashMap<>(); + List result = new ArrayList<>(); + Consumer consumer = dummy -> { + }; + + for (Meal meal : meals) { + caloriesPerDays.merge(meal.getDate(), meal.getCalories(), Integer::sum); + if (TimeUtil.isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + consumer = consumer.andThen(dummy -> result.add(createTo(meal, caloriesPerDays.get(meal.getDateTime().toLocalDate()) > caloriesPerDay))); + } + } + consumer.accept(null); + return result; + } + + private static List filteredByFlatMap(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + Collection> list = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate)).values(); + + return list.stream() + .flatMap(dayMeals -> { + boolean excess = dayMeals.stream().mapToInt(Meal::getCalories).sum() > caloriesPerDay; + return dayMeals.stream().filter(meal -> + isBetweenHalfOpen(meal.getTime(), startTime, endTime)) + .map(meal -> createTo(meal, excess)); + }).collect(toList()); + } + + private static List filteredByCollector(List meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) { + final class Aggregate { + private final List dailyMeals = new ArrayList<>(); + private int dailySumOfCalories; + + private void accumulate(Meal meal) { + dailySumOfCalories += meal.getCalories(); + if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) { + dailyMeals.add(meal); + } + } + + // never invoked if the upstream is sequential + private Aggregate combine(Aggregate that) { + this.dailySumOfCalories += that.dailySumOfCalories; + this.dailyMeals.addAll(that.dailyMeals); + return this; + } + + private Stream finisher() { + final boolean excess = dailySumOfCalories > caloriesPerDay; + return dailyMeals.stream().map(meal -> createTo(meal, excess)); + } + } + + Collection> values = meals.stream() + .collect(Collectors.groupingBy(Meal::getDate, + Collector.of(Aggregate::new, Aggregate::accumulate, Aggregate::combine, Aggregate::finisher)) + ).values(); + + return values.stream().flatMap(identity()).collect(toList()); + } + private static MealTo createTo(Meal meal, boolean excess) { return new MealTo(meal.getDateTime(), meal.getDescription(), meal.getCalories(), excess); }