Pierre-Yves Saumont

Author
+ Follow
since Oct 30, 2015
Merit badge: grant badges
For More
Cows and Likes
Cows
Total received
In last 30 days
0
Forums and Threads

Recent posts by Pierre-Yves Saumont

A stream is actually a generator. It generates a series of data. For this, it can use:

- a collection. Each step will generate the next element By reading it from the collection until there is no more element.

- a function of the position in the stream. For example, a random number generator. Generally, however, the generator is not a seen as a function. Instead, it memorizes its state, so it does not actually take the position as an argument.

- a function of the previous element. In such a case, it needs a seed In addition to the function.

Java streams often use a collection to generate data, which is why many programmers see them as “lazy” collections.

Laziness is essential to streams, which allows composing operations without executing them. It also allows working with infinite streams.

Basically, streams are not different from loopa generating values. It is in fact the functional equivalent of a loop generating a value on each iteration. The main benefit is that there is an agreed upon idiom to compose streams with operations, instead of manually composing loops an operations.

Saying that streams can only be used once makes no more sense than saying that a loop can only be executed once.

Java streams add parallelism, but this is not something specific to streams. Or maybe, they were called “streams“ in order to allow for this specificity. The correct name is “sequence”.
4 years ago
You're right. It's a very bad name  

Collecting strings by their length would not be a simplification. What I should have done is grouping strings by length AND set of characters (and not CharSet!). In fact, this might not be enough,  because "aabc" would then be equals to "abbc".  If true anagrams are to be considered, we can use something like:



6 years ago
Here is what I would do:



(You can devise a better implementation of the getCharset method!)
6 years ago
Hi,

Why not write a program to do the job, and then simply call this program with the variable arguments? Here is an example. The Consumer<String> process is your actual program. The rest is just testing environment:

6 years ago
Hi Marco,

Yes, it is because although the version using two accumulators is tail recursive, Java does not implement TCE (Tail Call Elimination). So to use recursion in Java, you have the following solutions (some may be combined):

- Ensure that the number of steps will be low. This can be the result of some business condition. It could theoretically blow the stack, but you believe it won't happen with normal data.

- Use recursion for cases that can't blow the stack. For example on a balanced binary tree. As the number of objects will be equal to 2 at the power of the recursion depth (something like 2^ 3000), it'll blow the heap long before it exhausts the stack.

- Configure a larger stack. But this is a waste of space in most cases, and anyway, you can go very far in this direction.

- Use the trampoline implementation and tail recursion as I explained. But some recursive computations may not be replaced with tail recursive one.

- Use a language that implements TCO/TCE. This does not mean you have to switch to another language. You could put you tail recursive functions in a library written in Scala and call it from your Java code. Or (much) better, you can write your recursive code in Kotlin. This is not to say that Kotlin is better than Scala, but Kotlin code may be mixed with Java code in the same module. Using IntelliJ for editing and Gradle for Building, you can have Kotlin source files along with Java files in your project. Using Kotlin that way is no different from using any library (besides the fact that you have to learn a different syntax).

- Apply recursion to lazily evaluated collections. There is a strong relationship between the trampoline implementation and laziness. The trampoline is, in fact, a lazy computation. The trampoline can't be used for right-folding a list. However, under some conditions, a lazy-list, may be right-folded without any problem as I described in chapter 9 of my book due to the fact that no actual computation occurs. It will still blow the stack if the full lazy list is eventually evaluated. But often, it will not be the case because the number of elements will have been reduced before the data is evaluated.
6 years ago
What you get is the correct result given the fact that negative numbers are represented in Two's complement:



What result do you want to get?
7 years ago
Very good idea. In a previous example, I had implemented a system consisting of starting from the highest value present in the cache in case the needed value was not found, but it  would not work with a WeakHashMap. (Or should we implement a WeakTreeMap?   )
Here is an example of a very simple memoized recursive Fibonacci function. It works without any loop (not counting the loop in the main method, of course) for input values up to 100_000_000, without causing stack overflow nor heap overflow. It is very easy to alter to make it work for any long value (since this will, in any case, be limited to the maximum long value in Java).

It is not the fastest way to compute Fibonacci numbers (corecursion is not fast!). Only an example of handling recursion in a stack safe manner. Note that a maximum depth of 10_000 might not work for you. In such case, just use a lower value. Using 3_000 should work in all cases and allow calling the function with values up to 9_000_000. Also note that the fib(0 ) = 1. This is consistent with the definition, as the first Fibonacci number is 1.

@Piet: Your solution does not really consist in building up the cache slowly, but in precomputing the cache in increments of 1000 elements, which allows using recursion inside each increment. As Java is able to handle a few thousand recursion steps with the default stack size, you could use a value higher than 1000.

Stream.foEach is actually a loop. You don't need to display the precomputed values, so you could simply write:



These parameters allow for relatively fast precomputation and a function working up to n = 34 * 3000, so you can, for example, compute fib(100_000).

However, it is still limited. You can't go any further because you will be out of memory. Memoized functions should generally use some way of invalidating cache entries in order to recover memory space when necessary.


@Sean Fibonacci in Kotlin:

@Piet  The cost is not very high but the benefit is null.  Of course, here you have to populate the map with three values. But this could be encapsulated in a FibCache class. So the only cost would be to get the initial FibCache and store it between successive calls. What are the benefits? In fact, there are none because either you share the cache, allowing other threads to benefit from the already computed values, but requiring synchronization of the cache, or you don't share the cache and you get the same result as when using a mutable map and not sharing the AbsentFibonacciDemo instance.

It is a common mistake to think that using immutable data structures makes sharing mutable state safer or easier. Immutable data structures prevent unintentional sharing of mutable state. This is fine when you don't need to share state. It makes sure you don't accidentally share mutable state. But it make sharing mutable state much more difficult. One solution, although not a functional one, is to use an actor framework which encapsulates access serialization and safe state mutation. It is not functional, but it fits nicely with functional programming. (This is chapter 14 of my book.)
@Sean Using an immutable map, you would have to store it somewhere, which means a mutable reference to a map. Or you can pass the map along with the number, and get back a map. Then you can extract the value from the map and ask for a new value passing the map along.

This would either mean that your successive calls are chained, or that they are made from a non functional program. Here his an example where the program is functional (although it is a simulation with a mutable map, but it would work just the same with an immutable one). Only the user (here the main method) is not functional:

@Piet Sorry, I did not read the code fully. You example is no longer recursive. Using forEach is like using a loop. I was thinking of the recursive example, if you ask for values incremented by one, the StackOverflowException may occur much later.
@Piet Yes, this effect may be observed with some recursive calls. But it will not work for really high values.


By the way, I fixed a bug in my code. (I was not using the map!)