Odnoklassniki parsing at Joker 2019





From October 28 to 29, Joker 2019 was held in St. Petersburg - the largest and most hardcore conference in the vastness of Russia dedicated to Java development. The event was held for the seventh time and, as always, broke the record for attendance, this time the event attracted more than 2000 specialists.



Classmates traditionally take part in Joker as event partners. This year, at our booth, one could try to cope with the famous "unsolvable" tasks from leading OK.RU engineers. Conference participants who answered the questions correctly received prizes.



In fairness, I must say that out of 1,000 leaflets with the tasks that we handed out, less than 100 were returned back. The best was the solution, which scored 4.5 points out of 5 possible.



We publish tasks and their solutions so that you can test your strengths.



1. Heroic enum



In the source code of one little-known game, such a code was discovered. What is the bad implementation of Group.of



, and how to fix it?



 enum Group { Few(1, 4), Several(5, 9), Pack(10, 19), Lots(20, 49), Swarm(50, Integer.MAX_VALUE); Group(int min, int max) { ... } public static Group of(int count) { for (Group group : Group.values()) { if (count >= group.min && count <= group.max) { return group; } } throw new IllegalArgumentException(); } }
      
      





Decision
If you don’t talk about coding styles, this fragment has an objective drawback - a potential performance problem. Although linear search often turns out to be a bottleneck, in this case it is not the point, because this enum has only five elements. And what really can negatively affect performance is the excessive allocation of memory when calling Group.values()



. The problem is that the values()



method of enum every time returns a new copy of the array, and HotSpot is not yet able to optimize it. A simple solution is to make your own copy of the values()



array and iterate over it:



 private static final Group[] ALL_GROUPS = Group.values(); public static Group of(int count) { for (Group group : ALL_GROUPS) { .... }
      
      







2. Dreams



Java 13 has already been released, and Nikolai still only comprehends streams. Indicate errors in the method that calculates the difference between the maximum and minimum stream elements.



 int getDiameter(Stream<Integer> stream) { int min = stream.min(Integer::compare).get(); int max = stream.max(Integer::compare).get(); return max - min; }
      
      





Decision
Streams in Java are usually one-time: calling the second terminal operation (in this case, max



) will fail:



 java.lang.IllegalStateException: stream has already been operated upon or closed
      
      





In addition, min



and max



return Optional



, the get()



operation on which will throw a NoSuchElementException



for an empty stream. Therefore, it is more correct to check isPresent()



before calling get()



or use other Optional



methods: orElse , orElseThrow , etc.



Finally, the fact that the difference between the two int



can no longer fit into the int



will not escape the careful developer, and it would be worth changing the type of the return value to long



.



3. Secure buffer



ByteBuffer



Java synchronization primitive can make put



and get



operations thread safe on a generic ByteBuffer



?



 final ByteBuffer buf = ByteBuffer.allocate(SIZE); int get(int offset) { return buf.get(offset); } void put(int offset, int value) { buf.putInt(offset, value); }
      
      





Choose the most effective option if you know that there are a lot of threads, and get is executed much more often than put.





Decision
ReentrantReadWriteLock



begs for reader and writer ReentrantReadWriteLock



, and often this will be an effective solution. But note that in this case, the get and put operations are very simple - the probability that a competitive put can interfere with get is small, moreover, put conditions are less likely to occur with put operations. So, you can apply the optimistic locking mechanism that StampedLock provides.



StampedLock



will be more efficient than ReentrantReadWriteLock



due to the fact that in case of an optimistic fast path success, shared variables are not updated at all, while ReentrantReadWriteLock



performs at least one CAS at best.



4. Gifts



Ilya is developing a showcase of gifts on a social network. Help him write the add



method for a structure that holds no more than N of the newest gifts. A gift should not be added if it is already present, or if it is older than the rest of N.



 interface Present { long getId(); Date getCreated(); } void add(Present p) { // Implement me }
      
      





Decision
TreeSet



or PriorityQueue



naturally suitable as a data structure in order to effectively add gifts and delete the oldest no worse than for O (log N). All the trick is only in the comparator: it is not enough to compare gifts only with getCreated()



, because the creation date does not have to be unique. Therefore, you need to compare first by getCreated()



, then by getId()



. Such a comparator will ensure both the uniqueness of the elements and the ordering by date.



 TreeSet<Present> tree = new TreeSet<>( Comparator.comparing(Present::getCreated) .thenComparing(Present::getId));
      
      





It remains a small matter: when adding a gift, check that the size does not exceed N, and if necessary, delete the first, oldest, element of the collection.



 void add(Present p) { if (tree.add(p) && tree.size() > N) { tree.pollFirst(); } }
      
      







5. You will not wait



Why Julia will never wait for the end of this program?



 var executor = Executors.newFixedThreadPool(4); for (File f : File.listRoots()) { executor.submit(() -> f.delete()); } executor.awaitTermination(2, TimeUnit.HOURS);
      
      





Decision
The awaitTermination documentation suggests that shutdown request should precede execution. It's simple: Julia forgot to call executor.shutdown () .




All Articles