This is an automated email from the ASF dual-hosted git repository.
git-site-role pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-dev-site.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 2030149 2023/12/09 06:04:43: Generated dev website from
groovy-website@6915e8c
2030149 is described below
commit 2030149cd5d27ef840c91186903b9eb175e4463c
Author: jenkins <[email protected]>
AuthorDate: Sat Dec 9 06:04:43 2023 +0000
2023/12/09 06:04:43: Generated dev website from groovy-website@6915e8c
---
blog/feed.atom | 10 ++
blog/groovy-gatherers.html | 418 +++++++++++++++++++++++++++++++++++++++++----
blog/index.html | 4 +-
3 files changed, 401 insertions(+), 31 deletions(-)
diff --git a/blog/feed.atom b/blog/feed.atom
index 35f1d64..779e71f 100644
--- a/blog/feed.atom
+++ b/blog/feed.atom
@@ -744,4 +744,14 @@
<published>2023-11-14T15:22:57+00:00</published>
<summary>This blog looks at union, intersection, difference &amp;
symmetric difference operators in Groovy.</summary>
</entry>
+ <entry>
+ <author>
+ <name>Paul King</name>
+ </author>
+ <title>Using Gatherers with Groovy</title>
+ <link href="http://groovy.apache.org/blog/groovy-gatherers"/>
+ <updated>2023-12-09T15:30:00+00:00</updated>
+ <published>2023-12-09T15:30:00+00:00</published>
+ <summary>This post looks at using Gatherers (JEP 461) with
Groovy.</summary>
+ </entry>
</feed>
diff --git a/blog/groovy-gatherers.html b/blog/groovy-gatherers.html
index 76f6684..4986220 100644
--- a/blog/groovy-gatherers.html
+++ b/blog/groovy-gatherers.html
@@ -3,7 +3,7 @@
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head>
- <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible'
content='IE=edge'/><meta name='viewport' content='width=device-width,
initial-scale=1'/><meta name='keywords' content='gatherers, jdk22, chop,
collate, ginq, jep461'/><meta name='description' content='This post looks at
using Gatherers (JEP 461) with Groovy.'/><title>The Apache Groovy programming
language - Blogs - Using Gatherers with Groovy</title><link
href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link rel='styl [...]
+ <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible'
content='IE=edge'/><meta name='viewport' content='width=device-width,
initial-scale=1'/><meta name='keywords' content='gatherers, jdk22, chop,
collate, inject, ginq, jep461'/><meta name='description' content='This post
looks at using Gatherers (JEP 461) with Groovy.'/><title>The Apache Groovy
programming language - Blogs - Using Gatherers with Groovy</title><link
href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link r [...]
</head><body>
<div id='fork-me'>
<a href='https://github.com/apache/groovy'>
@@ -53,17 +53,18 @@
</ul>
</div>
</div>
- </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a
href='#doc'>Using Gatherers with Groovy</a></li><li><a
href='#_understanding_gatherers' class='anchor-link'>Understanding
Gatherers</a></li><li><a href='#_accessing_parts_of_a_collection'
class='anchor-link'>Accessing parts of a collection</a></li><li><a
href='#_collate' class='anchor [...]
+ </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a
href='#doc'>Using Gatherers with Groovy</a></li><li><a
href='#_understanding_gatherers' class='anchor-link'>Understanding
Gatherers</a></li><li><a href='#_accessing_parts_of_a_collection'
class='anchor-link'>Accessing parts of a collection</a></li><li><a
href='#_collate' class='anchor [...]
<div class="sectionbody">
<div class="paragraph">
<p>An interesting feature being previewed in JDK22 is <em>Gatherers</em>
(<a href="https://openjdk.java.net/jeps/461">JEP 461</a>).
This blog looks at using that feature with Groovy.
The examples in this blog were tested with Groovy 4.0.16 using JDK version
22-ea+27-2262.
-As this JDK version is still in early access,
+As the JDK version we used is still in early access status,
you should read the disclaimers to understand that this JDK feature
is subject to change before final release. If and when the feature becomes
-final, Groovy supports it without needing any additional support.</p>
+final, it looks like Groovy will automatically support it without needing
+any additional tweaks.</p>
</div>
</div>
</div>
@@ -85,11 +86,12 @@ some complex tasks cannot easily be expressed as stream
pipelines.
Enter <em>gatherers</em>. Gatherers provide the ability to customize
intermediate operations.</p>
</div>
<div class="paragraph">
-<p>The stream API is updated to support a <code>gather</code> intermediate
operation which takes a gatherer
-and returns a transformed stream. Let’s dive into a few more details of
gatherers.</p>
+<p>With gatherers, the stream API is updated to support a <code>gather</code>
intermediate operation
+which takes a gatherer and returns a transformed stream.
+Let’s dive into a few more details of gatherers.</p>
</div>
<div class="paragraph">
-<p>A gatherer has four functions:</p>
+<p>A gatherer is defined by four pieces of functionality:</p>
</div>
<div class="ulist">
<ul>
@@ -107,7 +109,7 @@ and returns a transformed stream. Let’s dive into a
few more details of ga
</div>
<div class="paragraph">
<p>where <code>state</code> is some state — we’ll use
a list as state in a few of the upcoming
-examples, but it could just as easily be an instance of a class or record,
<code>element</code>
+examples, but it could just as easily be an instance of some other class or
record, <code>element</code>
is the next element in the current stream to be processed, and
<code>downstream</code> is
a hook for creating the elements that will be processed in the next stage of
the stream pipeline.</p>
</div>
@@ -124,7 +126,13 @@ and thus cannot be parallelized, so we won’t discuss
this aspect further h
</div>
<div class="paragraph">
<p>Over and above, the Gatherer API, there are a number of built-in gathers
-like <code>windowFixed</code> and <code>windowSliding</code>, among others.</p>
+like <code>windowFixed</code>, <code>windowSliding</code>, and
<code>fold</code>, among others.</p>
+</div>
+<div class="paragraph">
+<p>Before getting into functionality where gatherers will become essential,
+let’s start off by looking at accessing collections where functionality
+is well provided for in both the collection and stream APIs and related
+extension methods.</p>
</div>
</div>
</div>
@@ -164,8 +172,9 @@ assert (1..8).stream().skip(2).limit(3).toList() == [3, 4,
5]</code></pre>
</div>
</div>
<div class="paragraph">
-<p>But what about some of Groovy’s more elaborate mechanisms for
manipulating collections?
-I’m glad you asked. Let’s look at <code>collate</code> and
<code>chop</code>.</p>
+<p>We can see here there are stream equivalents for <code>drop</code> and
<code>take</code>,
+but what about some of Groovy’s more elaborate mechanisms for
manipulating collections?
+I’m glad you asked. Let’s look at stream equivalents for
<code>collate</code> and <code>chop</code>.</p>
</div>
</div>
</div>
@@ -214,8 +223,8 @@ the leftover elements, but it’s easy enough to write
our own:</p>
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy"><T>
Gatherer<T, ?, List<T>> windowFixedTruncating(int windowSize) {
Gatherer.ofSequential(
- () -> [],
- Gatherer.Integrator.ofGreedy { window, element, downstream ->
+ () -> [], //
initializer
+ Gatherer.Integrator.ofGreedy { window, element, downstream -> //
integrator
window << element
if (window.size() < windowSize) return true
var result = List.copyOf(window)
@@ -228,18 +237,36 @@ the leftover elements, but it’s easy enough to
write our own:</p>
</div>
<div class="paragraph">
<p>We have an initializer which just returns an empty list as our initial
state.
-The gatherer keeps adding elements to the state (our list or window). Once the
+The integrator keeps adding elements to the state (our list or window). Once
the
list is filled to the window size, we’ll output it to the downstream,
-and then clear the list ready for the next window.
-The code here is essentially a simplified version of <code>windowFixed</code>,
we can
-just leave out the finalizer that <code>windowFixed</code> would require to
potentially
+and then clear the list ready for the next window.</p>
+</div>
+<div class="paragraph">
+<p>The code here is essentially a simplified version of
<code>windowFixed</code>, we can
+just leave out the finisher that <code>windowFixed</code> would require to
potentially
output the partially-filled window at the end.</p>
</div>
<div class="paragraph">
-<p>A few details. Our operation is sequential since it is inherently ordered,
-hence we used <code>ofSequential</code> to mark it so. We will also always
process all
+<p>A few details:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Our operation is sequential since it is inherently ordered,
+hence we used <code>ofSequential</code> to mark it so.</p>
+</li>
+<li>
+<p>We will also always process all
elements, so we create a greedy gatherer using <code>ofGreedy</code>. While
not strictly
necessary, this allows for optimisation of the pipeline.</p>
+</li>
+<li>
+<p>We have specifically left out some validation logic out of this example
+to focus on the new gatherer functionality. Check out how
<code>windowFixed</code>
+throws <code>IllegalArgumentException</code> for window sizes less than 1 to
see what
+should really also be added here if you were using this in production.</p>
+</li>
+</ul>
</div>
<div class="paragraph">
<p>We’d use it like this:</p>
@@ -310,7 +337,7 @@ assert (1..8).collate(3, 3) == [[1, 2, 3], [4, 5, 6], [7,
8]] // same as collat
[stepSize, windowSize].min().times { window.removeFirst() }
downstream.push(result)
},
- (window, downstream) -> { //
finalizer
+ (window, downstream) -> { //
finisher
if (keepRemaining) {
while(window.size() > stepSize) {
downstream.push(List.copyOf(window))
@@ -324,14 +351,29 @@ assert (1..8).collate(3, 3) == [[1, 2, 3], [4, 5, 6], [7,
8]] // same as collat
</div>
</div>
<div class="paragraph">
-<p>Some points. Our gatherer is still sequential for the same reasons as
previously.
-We are still processing every element, so we again created a greedy gatherer.
-We have a little bit of optimization baked into the code. If our step size
+<p>Some points:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Our gatherer is still sequential for the same reasons as previously.
+We are still processing every element, so we again created a greedy
gatherer.</p>
+</li>
+<li>
+<p>We have a little bit of optimization baked into the code. If our step size
is bigger than the window size, we can do no further processing in our gatherer
for the elements in between our windows. We could simplify the code and store
those
elements only to throw them away later, but it’s not too much effort to
make
-the algorithm as efficient as possible. We also need a finalizer here which
-handles the leftover chunk(s).</p>
+the algorithm as efficient as possible.</p>
+</li>
+<li>
+<p>We also need a finisher here which
+handles the leftover chunk(s) when required.</p>
+</li>
+<li>
+<p>As per the previous example, we chose to elide some argument validation
logic.</p>
+</li>
+</ul>
</div>
<div class="paragraph">
<p>And we’d use it like this:</p>
@@ -350,26 +392,344 @@ assert (1..8).stream().gather(windowSlidingByStep(3,
3)).toList() ==
[[1, 2, 3], [4, 5, 6], [7, 8]]</code></pre>
</div>
</div>
+<div class="paragraph">
+<p>Before leaving this section, let’s look at a few examples using
Groovy’s
+language integrated query capabilities as an alternative way to manipulate
+collections.</p>
+</div>
+<div class="paragraph">
+<p>Firstly, the equivalent of what we saw with <code>take</code> /
<code>limit</code>:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert GQL {
+ from n in 1..8
+ limit 3
+ select n
+} == [1, 2, 3]</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Then, the equivalent if we added in <code>drop</code> /
<code>skip</code>:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert GQL {
+ from n in 1..8
+ limit 2, 3
+ select n
+} == [3, 4, 5]</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Finally, a sliding window equivalent:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert GQL {
+ from ns in (
+ from n in 1..8
+ select n, (lead(n) over(orderby n)), (lead(n, 2) over(orderby n))
+ )
+ limit 3
+ select ns
+}*.toList() == [[1, 2, 3], [2, 3, 4], [3, 4, 5]]</code></pre>
+</div>
+</div>
</div>
</div>
<div class="sect1">
<h2 id="_chop">Chop</h2>
<div class="sectionbody">
-
+<div class="paragraph">
+<p>A related collection method is <code>chop</code>. For this method, we also
create chunks
+from the original collection but rather than specifying a fixed size that
applies to
+all chunks, we specify the size we want for each chunk. Each size is only used
once.
+The special size of <code>-1</code> indicates that we want the remainder of
the collection as
+the last chunk.</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
(1..8).chop(3) == [[1, 2, 3]]
+assert (1..8).chop(3, 2, 1) == [[1, 2, 3], [4, 5], [6]]
+assert (1..8).chop(3, -1) == [[1, 2, 3], [4, 5, 6, 7, 8]]</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>There is no original stream or pre-built gatherer for this functionality.
+We’ll write our own:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy"><T>
Gatherer<T, ?, List<T>> windowMultiple(int... steps) {
+ var remaining = steps.toList()
+ Gatherer.ofSequential(
+ () -> [],
+ Gatherer.Integrator.of { window, element, downstream ->
+ if (!remaining) {
+ return false
+ }
+ window << element
+ if (remaining[0] != -1) remaining[0]--
+ if (remaining[0]) return true
+ remaining.removeFirst()
+ var result = List.copyOf(window)
+ window.clear()
+ downstream.push(result)
+ },
+ (window, downstream) -> {
+ if (window) {
+ var result = List.copyOf(window)
+ downstream.push(result)
+ }
+ }
+ )
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Some points:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>This is also an ordered algorithm, so we use <code>ofSequential</code>
again.</p>
+</li>
+<li>
+<p>This is similar to what we used for collate, but we have a different sized
+window for each chunk size as we process the elements.</p>
+</li>
+<li>
+<p>Once we hit the last chunk, we don’t want to process further
+elements unless we see the special -1 marker, so we won’t create a
greedy gatherer.</p>
+</li>
+<li>
+<p>We do need a finisher to potentially output elements that have been stored
but not yet
+pushed downstream.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>We’d use <code>windowMultiple</code> like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
(1..8).stream().gather(windowMultiple(3)).toList() ==
+ [[1, 2, 3]]
+assert (1..8).stream().gather(windowMultiple(3, 2, 1)).toList() ==
+ [[1, 2, 3], [4, 5], [6]]
+assert (1..8).stream().gather(windowMultiple(3, -1)).toList() ==
+ [[1, 2, 3], [4, 5, 6, 7, 8]]</code></pre>
+</div>
+</div>
</div>
</div>
<div class="sect1">
-<h2 id="_testing_for_a_subsequence">Testing for a subsequence</h2>
+<h2 id="_inject_and_fold">Inject and fold</h2>
<div class="sectionbody">
+<div class="paragraph">
+<p>Groovy’s <code>inject</code> is a little different to the stream
<code>reduce</code> intermediate operator.
+The latter expects a binary operator which restricts the types of the elements
+being consumed and produced.</p>
+</div>
+<div class="paragraph">
+<p>The <code>inject</code> method can have different types for its arguments
as shown here:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
(1..5).inject(''){ string, number -> string + number } ==
'12345'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The <code>fold</code> built-in gatherer provides the equivalent
functionality:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
(1..5).stream().gather(fold(() -> '', (string, number) -> string +
number)).findFirst().get() == '12345'</code></pre>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_testing_for_a_subsequence_fun_with_inits_and_tails">Testing for a
subsequence (fun with <code>inits</code> and <code>tails</code>)</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>As a final example, let’s have a look at how we might test
+if one list is a subset of another.</p>
+</div>
+<div class="paragraph">
+<p>We’ll start with a list of words, and a list containing the search
terms:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">var words = 'the
quick brown fox jumped over the lazy dog'.split().toList()
+var search = 'brown fox'.split().toList()</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>It turns out that this is solved already in the JDK for collections:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
Collections.indexOfSubList(words, search) != -1</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Let’s have a look at some possible stream implementations.
+But first a diversion. For any functional programmers who might have dabbled
+with Haskell, you may have seen the book <a
href="http://learnyouahaskell.com/">Learn You a Haskell for Great Good!</a>. It
sets an interesting exercise for finding a "Needle in the Haystack"
+using <code>inits</code> and <code>tails</code>. So what are
<code>inits</code> and <code>tails</code>? They are built-in fuctions
+in Haskell and Groovy:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
(1..6).inits() == [[1, 2, 3, 4, 5, 6],
+ [1, 2, 3, 4, 5],
+ [1, 2, 3, 4],
+ [1, 2, 3],
+ [1, 2],
+ [1],
+ []]
+assert (1..6).tails() == [[1, 2, 3, 4, 5, 6],
+ [2, 3, 4, 5, 6],
+ [3, 4, 5, 6],
+ [4, 5, 6],
+ [5, 6],
+ [6],
+ []]</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Once we know about these methods, we can paraphrase the "Needle in the
Haystack"
+solution for collections in Groovy as follows:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">var found =
words.tails().any{ subseq -> subseq.inits().contains(search) }
+assert found</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>It may not be the most efficient implementation of this functionality,
+but it has a nice symmetry. Let’s now explore some stream-based
solutions.</p>
+</div>
+<div class="paragraph">
+<p>We can start off with a <code>tails</code> gatherer:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy"><T>
Gatherer<T, ?, List<T>> tails() {
+ Gatherer.ofSequential(
+ () -> [],
+ Gatherer.Integrator.ofGreedy { state, element, downstream ->
+ state << element
+ return true
+ },
+ (state, downstream) -> {
+ state.tails().each(downstream::push)
+ }
+ )
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>In the integrator, we just store away all the elements,
+and in the finisher we do all the work. This works but isn’t really
+properly leveraging the stream pipeline nature.</p>
+</div>
+<div class="paragraph">
+<p>We can check it works as follows:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
search.stream().gather(tails()).toList() ==
+ [['brown', 'fox'], ['fox'], []]</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>We could continue with this approach to create an <code>initsOfTails</code>
gatherer:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy"><T>
Gatherer<T, ?, List<T>> initsOfTails() {
+ Gatherer.ofSequential(
+ () -> [],
+ Gatherer.Integrator.ofGreedy { state, element, downstream ->
+ state << element
+ return true
+ },
+ (state, downstream) -> {
+ state.tails()*.inits().sum().each(downstream::push)
+ }
+ )
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Again, all the work is in the finisher, and we haven’t really made use
+of the power of the stream pipeline.</p>
+</div>
+<div class="paragraph">
+<p>It still works of course:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
words.stream().gather(initsOfTails()).anyMatch { it == search }</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>But it might have been more efficient to have collected
+the stream as a list and used Groovy’s built-in <code>inits</code> and
<code>tails</code> on that.</p>
+</div>
+<div class="paragraph">
+<p>But all is not lost. If we were willing to tweak the algorithm slightly,
+we could make better use of the stream pipeline. For example, if we don’t
+mind getting the <code>inits</code> results in the reverse order, we could
define the following
+gatherer for <code>inits</code>:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy"><T>
Gatherer<T, ?, List<T>> inits() {
+ Gatherer.ofSequential(
+ () -> [],
+ Gatherer.Integrator.ofGreedy { state, element, downstream ->
+ downstream.push(List.copyOf(state))
+ state << element
+ return true
+ },
+ (state, downstream) -> {
+ downstream.push(state)
+ }
+ )
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Which we’d use like this:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">assert
search.stream().gather(inits()).toList() ==
+ [[], ['brown'], ['brown', 'fox']]</code></pre>
+</div>
+</div>
</div>
</div>
<div class="sect1">
<h2 id="_conclusion">Conclusion</h2>
<div class="sectionbody">
<div class="paragraph">
-<p>We have had a quick glimpse at using gatherers with Groovy.
-We are still in the early days of gatherers being available,
+<p>It is great that Groovy has a rich set of methods that
+work with collections. Some of these methods have stream
+equivalents, and now we see that using gatherers with Groovy,
+we can emulate more of the methods.
+Not all algorithms need or benefit from using streams,
+but it’s great to know that with gatherers, we can
+likely pick whichever style makes sense.</p>
+</div>
+<div class="paragraph">
+<p>We are still in the early days of gatherers being available,
so expect much more to emerge as this feature becomes more mainstream.
We look forward to it advancing past preview status.</p>
</div>
diff --git a/blog/index.html b/blog/index.html
index ee085d8..203b3f5 100644
--- a/blog/index.html
+++ b/blog/index.html
@@ -53,7 +53,7 @@
</ul>
</div>
</div>
- </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3' id='blog-index'><ul
class='nav-sidebar list'><li class='active'><a
href='/blog/'>Blogs</a></li><li><a href='set-operations-with-groovy'>Set
Operators with Groovy</a></li><li><a
href='community-over-code-na-2023'>Community Over Code (North America)
2023</a></li><li><a href='chatgpt-one-liners'>ChatGPT meets Groovy
one-liners</a></li><li><a href='groovy-pekko-gpars'> [...]
+ </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3' id='blog-index'><ul
class='nav-sidebar list'><li class='active'><a
href='/blog/'>Blogs</a></li><li><a href='groovy-gatherers'>Using Gatherers with
Groovy</a></li><li><a href='set-operations-with-groovy'>Set Operators with
Groovy</a></li><li><a href='community-over-code-na-2023'>Community Over Code
(North America) 2023</a></li><li><a href='chatgpt-one-liners'>ChatGP [...]
<div class='row'>
<div class='colset-3-footer'>
<div class='col-1'>
@@ -97,7 +97,7 @@
colors: am5.ColorSet.new(root, {})
}));
wc.data.setAll([
- { category: "groovy", value: 74 }, { category: "emoji", value:
6 }, { category: "set", value: 1 }, { category: "constraint programming",
value: 1 }, { category: "jacop", value: 1 }, { category: "or-tools", value: 1
}, { category: "choco", value: 1 }, { category: "jsr331", value: 1 }, {
category: "bytecode", value: 1 }, { category: "byte buddy", value: 1 }, {
category: "proguardcore", value: 1 }, { category: "asm", value: 1 }, {
category: "jvmadvent", value: 1 }, { categor [...]
+ { category: "groovy", value: 74 }, { category: "emoji", value:
6 }, { category: "set", value: 1 }, { category: "constraint programming",
value: 1 }, { category: "jacop", value: 1 }, { category: "or-tools", value: 1
}, { category: "choco", value: 1 }, { category: "jsr331", value: 1 }, {
category: "bytecode", value: 1 }, { category: "byte buddy", value: 1 }, {
category: "proguardcore", value: 1 }, { category: "asm", value: 1 }, {
category: "jvmadvent", value: 1 }, { categor [...]
]);
wc.labels.template.setAll({
paddingTop: 5,