88import java .time .LocalTime ;
99import java .time .Month ;
1010import java .util .*;
11+ import java .util .concurrent .*;
12+ import java .util .concurrent .locks .ReentrantLock ;
13+ import java .util .function .Consumer ;
14+ import java .util .function .Predicate ;
15+ import java .util .stream .Collector ;
1116import java .util .stream .Collectors ;
17+ import java .util .stream .Stream ;
1218
19+ import static java .util .function .Function .identity ;
20+ import static java .util .stream .Collectors .toList ;
1321import static ru .javawebinar .topjava .util .TimeUtil .isBetweenHalfOpen ;
1422
1523public class MealsUtil {
16- public static void main (String [] args ) {
24+ public static void main (String [] args ) throws InterruptedException {
1725 List <Meal > meals = Arrays .asList (
1826 new Meal (LocalDateTime .of (2020 , Month .JANUARY , 30 , 10 , 0 ), "Завтрак" , 500 ),
1927 new Meal (LocalDateTime .of (2020 , Month .JANUARY , 30 , 13 , 0 ), "Обед" , 1000 ),
@@ -31,13 +39,34 @@ public static void main(String[] args) {
3139 mealsTo .forEach (System .out ::println );
3240
3341 System .out .println (filteredByCycles (meals , startTime , endTime , 2000 ));
42+
43+ // Optional2 recursion
44+ System .out .println (filteredByRecursion (meals , startTime , endTime , 2000 ));
45+ System .out .println (filteredBySetterRecursion (meals , startTime , endTime , 2000 ));
46+ System .out .println (filteredByRecursionWithCycleAndRunnable (meals , startTime , endTime , 2000 ));
47+
48+ // Optional2 reference type
49+ // System.out.println(filteredByAtomic(meals, startTime, endTime, 2000)); // or boolean[1]
50+ // System.out.println(filteredByReflection(meals, startTime, endTime, 2000));
51+
52+ // Optional2 delayed execution
53+ // System.out.println(filteredByClosure(meals, startTime, endTime, 2000));
54+ System .out .println (filteredByExecutor (meals , startTime , endTime , 2000 ));
55+ System .out .println (filteredByLock (meals , startTime , endTime , 2000 ));
56+ System .out .println (filteredByCountDownLatch (meals , startTime , endTime , 2000 ));
57+ System .out .println (filteredByPredicate (meals , startTime , endTime , 2000 ));
58+ System .out .println (filteredByConsumerChain (meals , startTime , endTime , 2000 ));
59+
60+ // Optional2 streams
61+ System .out .println (filteredByFlatMap (meals , startTime , endTime , 2000 ));
62+ System .out .println (filteredByCollector (meals , startTime , endTime , 2000 ));
3463 }
3564
3665 public static List <MealTo > filteredByStreams (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
3766 Map <LocalDate , Integer > caloriesSumByDate = meals .stream ()
3867 .collect (
3968 Collectors .groupingBy (Meal ::getDate , Collectors .summingInt (Meal ::getCalories ))
40- // Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum)
69+ // Collectors.toMap(Meal::getDate, Meal::getCalories, Integer::sum)
4170 );
4271
4372 return meals .stream ()
@@ -60,6 +89,286 @@ public static List<MealTo> filteredByCycles(List<Meal> meals, LocalTime startTim
6089 return mealsTo ;
6190 }
6291
92+ private static List <MealTo > filteredByRecursion (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
93+ ArrayList <MealTo > result = new ArrayList <>();
94+ filterWithRecursion (new LinkedList <>(meals ), startTime , endTime , caloriesPerDay , new HashMap <>(), result );
95+ return result ;
96+ }
97+
98+ private static void filterWithRecursion (LinkedList <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ,
99+ Map <LocalDate , Integer > dailyCaloriesMap , List <MealTo > result ) {
100+ if (meals .isEmpty ()) return ;
101+
102+ Meal meal = meals .pop ();
103+ dailyCaloriesMap .merge (meal .getDate (), meal .getCalories (), Integer ::sum );
104+ filterWithRecursion (meals , startTime , endTime , caloriesPerDay , dailyCaloriesMap , result );
105+ if (isBetweenHalfOpen (meal .getTime (), startTime , endTime )) {
106+ result .add (createTo (meal , dailyCaloriesMap .get (meal .getDate ()) > caloriesPerDay ));
107+ }
108+ }
109+
110+ private static List <MealTo > filteredBySetterRecursion (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
111+ class MealNode {
112+ private final MealNode prev ;
113+ private final MealTo mealTo ;
114+
115+ public MealNode (MealTo mealTo , MealNode prev ) {
116+ this .mealTo = mealTo ;
117+ this .prev = prev ;
118+ }
119+
120+ public void setExcess () {
121+ mealTo .setExcess (true );
122+ if (prev != null ) {
123+ prev .setExcess ();
124+ }
125+ }
126+ }
127+
128+ Map <LocalDate , Integer > caloriesSumByDate = new HashMap <>();
129+ Map <LocalDate , MealNode > mealNodeByDate = new HashMap <>();
130+ List <MealTo > mealsTo = new ArrayList <>();
131+ meals .forEach (meal -> {
132+ LocalDate localDate = meal .getDate ();
133+ boolean excess = caloriesSumByDate .merge (localDate , meal .getCalories (), Integer ::sum ) > caloriesPerDay ;
134+ if (TimeUtil .isBetweenHalfOpen (meal .getTime (), startTime , endTime )) {
135+ MealTo mealTo = createTo (meal , excess );
136+ mealsTo .add (mealTo );
137+ if (!excess ) {
138+ MealNode prevNode = mealNodeByDate .get (localDate );
139+ mealNodeByDate .put (localDate , new MealNode (mealTo , prevNode ));
140+ }
141+ }
142+ if (excess ) {
143+ MealNode mealNode = mealNodeByDate .remove (localDate );
144+ if (mealNode != null ) {
145+ // recursive set for all interval day meals
146+ mealNode .setExcess ();
147+ }
148+ }
149+ });
150+ return mealsTo ;
151+ }
152+
153+ public static List <MealTo > filteredByRecursionWithCycleAndRunnable (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
154+ Map <LocalDate , Integer > caloriesSumByDate = new HashMap <>();
155+ List <MealTo > mealsTo = new ArrayList <>();
156+ Iterator <Meal > iterator = meals .iterator ();
157+
158+ new Runnable () {
159+ @ Override
160+ public void run () {
161+ while (iterator .hasNext ()) {
162+ Meal meal = iterator .next ();
163+ caloriesSumByDate .merge (meal .getDate (), meal .getCalories (), Integer ::sum );
164+ if (isBetweenHalfOpen (meal .getTime (), startTime , endTime )) {
165+ run ();
166+ mealsTo .add (createTo (meal , caloriesSumByDate .get (meal .getDate ()) > caloriesPerDay ));
167+ }
168+ }
169+ }
170+ }.run ();
171+ return mealsTo ;
172+ }
173+
174+ /*
175+ private static List<MealTo> filteredByAtomic(List<Meal> meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
176+ Map<LocalDate, Integer> caloriesSumByDate = new HashMap<>();
177+ Map<LocalDate, AtomicBoolean> exceededMap = new HashMap<>();
178+
179+ List<MealTo> mealsTo = new ArrayList<>();
180+ meals.forEach(meal -> {
181+ AtomicBoolean wrapBoolean = exceededMap.computeIfAbsent(meal.getDate(), date -> new AtomicBoolean());
182+ Integer dailyCalories = caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum);
183+ if (dailyCalories > caloriesPerDay) {
184+ wrapBoolean.set(true);
185+ }
186+ if (isBetween(meal.getTime(), startTime, endTime)) {
187+ mealsTo.add(createTo(meal, wrapBoolean)); // also change createTo and MealTo.excess
188+ }
189+ });
190+ return mealsTo;
191+ }
192+
193+ private static List<MealTo> filteredByReflection(List<Meal> meals, LocalTime startTime, LocalTime endTime, int caloriesPerDay) throws NoSuchFieldException, IllegalAccessException {
194+ Map<LocalDate, Integer> caloriesSumByDate = new HashMap<>();
195+ Map<LocalDate, Boolean> exceededMap = new HashMap<>();
196+ Field field = Boolean.class.getDeclaredField("value");
197+ field.setAccessible(true);
198+
199+ List<MealTo> mealsTo = new ArrayList<>();
200+ for (Meal meal : meals) {
201+ Boolean mutableBoolean = exceededMap.computeIfAbsent(meal.getDate(), date -> new Boolean(false));
202+ Integer dailyCalories = caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum);
203+ if (dailyCalories > caloriesPerDay) {
204+ field.setBoolean(mutableBoolean, true);
205+ }
206+ if (isBetweenHalfOpen(meal.getTime(), startTime, endTime)) {
207+ mealsTo.add(createTo(meal, mutableBoolean)); // also change createTo and MealTo.excess
208+ }
209+ }
210+ return mealsTo;
211+ }
212+
213+ private static List<MealTo> filteredByClosure(List<Meal> mealList, LocalTime startTime, LocalTime endTime, int caloriesPerDay) {
214+ final Map<LocalDate, Integer> caloriesSumByDate = new HashMap<>();
215+ List<MealTo> mealsTo = new ArrayList<>();
216+ mealList.forEach(meal -> {
217+ caloriesSumByDate.merge(meal.getDate(), meal.getCalories(), Integer::sum);
218+ if (isBetween(meal.getTime(), startTime, endTime)) {
219+ mealsTo.add(createTo(meal, () -> (caloriesSumByDate.get(meal.getDate()) > caloriesPerDay))); // also change createTo and MealTo.excess
220+ }
221+ }
222+ );
223+ return mealsTo;
224+ }
225+ */
226+
227+ private static List <MealTo > filteredByExecutor (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) throws InterruptedException {
228+ Map <LocalDate , Integer > caloriesSumByDate = new HashMap <>();
229+ List <Callable <Void >> tasks = new ArrayList <>();
230+ final List <MealTo > mealsTo = Collections .synchronizedList (new ArrayList <>());
231+
232+ meals .forEach (meal -> {
233+ caloriesSumByDate .merge (meal .getDate (), meal .getCalories (), Integer ::sum );
234+ if (isBetweenHalfOpen (meal .getTime (), startTime , endTime )) {
235+ tasks .add (() -> {
236+ mealsTo .add (createTo (meal , caloriesSumByDate .get (meal .getDate ()) > caloriesPerDay ));
237+ return null ;
238+ });
239+ }
240+ });
241+ ExecutorService executorService = Executors .newCachedThreadPool ();
242+ // https://stackoverflow.com/a/1250668/548473
243+ executorService .invokeAll (tasks );
244+ executorService .shutdown ();
245+ return mealsTo ;
246+ }
247+
248+ public static List <MealTo > filteredByLock (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) throws InterruptedException {
249+ Map <LocalDate , Integer > caloriesSumByDate = new HashMap <>();
250+ List <MealTo > mealsTo = new ArrayList <>();
251+ ExecutorService executor = Executors .newCachedThreadPool ();
252+ ReentrantLock lock = new ReentrantLock ();
253+ lock .lock ();
254+ for (Meal meal : meals ) {
255+ caloriesSumByDate .merge (meal .getDateTime ().toLocalDate (), meal .getCalories (), Integer ::sum );
256+ if (isBetweenHalfOpen (meal .getDateTime ().toLocalTime (), startTime , endTime ))
257+ executor .submit (() -> {
258+ lock .lock ();
259+ mealsTo .add (createTo (meal , caloriesSumByDate .get (meal .getDateTime ().toLocalDate ()) > caloriesPerDay ));
260+ lock .unlock ();
261+ });
262+ }
263+ lock .unlock ();
264+ executor .shutdown ();
265+ executor .awaitTermination (5 , TimeUnit .SECONDS );
266+ return mealsTo ;
267+ }
268+
269+ private static List <MealTo > filteredByCountDownLatch (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) throws InterruptedException {
270+ Map <LocalDate , Integer > caloriesSumByDate = new HashMap <>();
271+ List <MealTo > mealsTo = Collections .synchronizedList (new ArrayList <>());
272+ CountDownLatch latchCycles = new CountDownLatch (meals .size ());
273+ CountDownLatch latchTasks = new CountDownLatch (meals .size ());
274+ for (Meal meal : meals ) {
275+ caloriesSumByDate .merge (meal .getDateTime ().toLocalDate (), meal .getCalories (), Integer ::sum );
276+ if (isBetweenHalfOpen (meal .getDateTime ().toLocalTime (), startTime , endTime )) {
277+ new Thread (() -> {
278+ try {
279+ latchCycles .await ();
280+ } catch (InterruptedException e ) {
281+ e .printStackTrace ();
282+ }
283+ mealsTo .add (createTo (meal , caloriesSumByDate .get (meal .getDateTime ().toLocalDate ()) > caloriesPerDay ));
284+ latchTasks .countDown ();
285+ }).start ();
286+ } else {
287+ latchTasks .countDown ();
288+ }
289+ latchCycles .countDown ();
290+ }
291+ latchTasks .await ();
292+ return mealsTo ;
293+ }
294+
295+ public static List <MealTo > filteredByPredicate (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
296+ Map <LocalDate , Integer > caloriesSumByDate = new HashMap <>();
297+ List <MealTo > mealsTo = new ArrayList <>();
298+
299+ Predicate <Boolean > predicate = b -> true ;
300+ for (Meal meal : meals ) {
301+ caloriesSumByDate .merge (meal .getDateTime ().toLocalDate (), meal .getCalories (), Integer ::sum );
302+ if (TimeUtil .isBetweenHalfOpen (meal .getDateTime ().toLocalTime (), startTime , endTime )) {
303+ predicate = predicate .and (b -> mealsTo .add (createTo (meal , caloriesSumByDate .get (meal .getDateTime ().toLocalDate ()) > caloriesPerDay )));
304+ }
305+ }
306+ predicate .test (true );
307+ return mealsTo ;
308+ }
309+
310+ public static List <MealTo > filteredByConsumerChain (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
311+ Map <LocalDate , Integer > caloriesPerDays = new HashMap <>();
312+ List <MealTo > result = new ArrayList <>();
313+ Consumer <Void > consumer = dummy -> {
314+ };
315+
316+ for (Meal meal : meals ) {
317+ caloriesPerDays .merge (meal .getDate (), meal .getCalories (), Integer ::sum );
318+ if (TimeUtil .isBetweenHalfOpen (meal .getTime (), startTime , endTime )) {
319+ consumer = consumer .andThen (dummy -> result .add (createTo (meal , caloriesPerDays .get (meal .getDateTime ().toLocalDate ()) > caloriesPerDay )));
320+ }
321+ }
322+ consumer .accept (null );
323+ return result ;
324+ }
325+
326+ private static List <MealTo > filteredByFlatMap (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
327+ Collection <List <Meal >> list = meals .stream ()
328+ .collect (Collectors .groupingBy (Meal ::getDate )).values ();
329+
330+ return list .stream ()
331+ .flatMap (dayMeals -> {
332+ boolean excess = dayMeals .stream ().mapToInt (Meal ::getCalories ).sum () > caloriesPerDay ;
333+ return dayMeals .stream ().filter (meal ->
334+ isBetweenHalfOpen (meal .getTime (), startTime , endTime ))
335+ .map (meal -> createTo (meal , excess ));
336+ }).collect (toList ());
337+ }
338+
339+ private static List <MealTo > filteredByCollector (List <Meal > meals , LocalTime startTime , LocalTime endTime , int caloriesPerDay ) {
340+ final class Aggregate {
341+ private final List <Meal > dailyMeals = new ArrayList <>();
342+ private int dailySumOfCalories ;
343+
344+ private void accumulate (Meal meal ) {
345+ dailySumOfCalories += meal .getCalories ();
346+ if (isBetweenHalfOpen (meal .getTime (), startTime , endTime )) {
347+ dailyMeals .add (meal );
348+ }
349+ }
350+
351+ // never invoked if the upstream is sequential
352+ private Aggregate combine (Aggregate that ) {
353+ this .dailySumOfCalories += that .dailySumOfCalories ;
354+ this .dailyMeals .addAll (that .dailyMeals );
355+ return this ;
356+ }
357+
358+ private Stream <MealTo > finisher () {
359+ final boolean excess = dailySumOfCalories > caloriesPerDay ;
360+ return dailyMeals .stream ().map (meal -> createTo (meal , excess ));
361+ }
362+ }
363+
364+ Collection <Stream <MealTo >> values = meals .stream ()
365+ .collect (Collectors .groupingBy (Meal ::getDate ,
366+ Collector .of (Aggregate ::new , Aggregate ::accumulate , Aggregate ::combine , Aggregate ::finisher ))
367+ ).values ();
368+
369+ return values .stream ().flatMap (identity ()).collect (toList ());
370+ }
371+
63372 private static MealTo createTo (Meal meal , boolean excess ) {
64373 return new MealTo (meal .getDateTime (), meal .getDescription (), meal .getCalories (), excess );
65374 }
0 commit comments