{"id":13374,"date":"2017-08-08T19:24:49","date_gmt":"2017-08-08T19:24:49","guid":{"rendered":"http:\/\/www.doanduyhai.com\/blog\/?p=13374"},"modified":"2017-08-09T13:51:31","modified_gmt":"2017-08-09T13:51:31","slug":"gremlin-recipes-7-variables-handling","status":"publish","type":"post","link":"https:\/\/www.doanduyhai.com\/blog\/?p=13374","title":{"rendered":"Gremlin recipes: 7 &#8211; Variables handling"},"content":{"rendered":"<p>This blog post is the 7<sup>th<\/sup> from the series <strong>Gremlin Recipes<\/strong>. It is recommended to read the previous blog posts first: <\/p>\n<ol>\n<li><a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13224\" target=\"_blank\"><strong>Gremlin as a Stream<\/strong><\/a><\/li>\n<li><a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13260\" target=\"_blank\"><strong>SQL to Gremlin<\/strong><\/a><\/li>\n<li><a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13285\" target=\"_blank\"><strong>Recommendation Engine traversal<\/strong><\/a><\/li>\n<li><a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13301\" target=\"_blank\"><strong>Recursive traversals<\/strong><\/a><\/li>\n<li><a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13320\" target=\"_blank\"><strong>Path object<\/strong><\/a><\/li>\n<li><a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13352\" target=\"_blank\"><strong>Projection and selection<\/strong><\/a><\/li>\n<\/ol>\n<p><!--more--><\/p>\n<h1>I KillrVideo dataset<\/h1>\n<p>To illustrate this series of recipes, you need first to create the schema for <strong>KillrVideo<\/strong> and import the data. See <a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13224#killrvideo_dataset\" target=\"_blank\"><strong>here<\/strong><\/a> for more details.<\/p>\n<p>The graph schema of this dataset is :<\/p>\n<p><iframe loading=\"lazy\" src=\"https:\/\/s3.amazonaws.com\/datastax-graph-schema-viewer\/index.html#\/?schema=killr_video_small.json\" height=\"600px\" width=\"100%\"><\/iframe><\/p>\n<h1>II Variable handling operators<\/h1>\n<p>In <strong>Gremlin<\/strong> we distinguish 2 types of operators:<\/p>\n<ol>\n<li>for setting variables\n<ul>\n<li>as <strong>side effect<\/strong><\/li>\n<li>as <strong>label<\/strong> in <strong>path history<\/strong> (path object)<\/li>\n<\/ul>\n<\/li>\n<li>for accessing variables<\/li>\n<\/ol>\n<p>Below are the list of these operators<\/p>\n<table>\n<thead>\n<tr>\n<th>Operator<\/th>\n<th>Side-Effect\/Label<\/th>\n<th>Setting\/reading variable<\/th>\n<th>Data type<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code><strong>store(\"variable\")<\/strong><\/code><\/td>\n<td>side effect<\/td>\n<td>setting<\/td>\n<td><code><strong>BulkSet implements Set&lt;X&gt;<\/strong><\/code><\/td>\n<\/tr>\n<tr>\n<td><code><strong>aggregate(\"variable\")<\/strong><\/code><\/td>\n<td>side effect<\/td>\n<td>setting<\/td>\n<td><code><strong>BulkSet implements Set&lt;X&gt;<\/strong><\/code><\/td>\n<\/tr>\n<tr>\n<td><code><strong>group(\"variable\").by(grouping_axis).by(projection_axis)<\/strong><\/code><\/td>\n<td>side effect<\/td>\n<td>setting<\/td>\n<td><code><strong>Map&lt;X,Y&gt;<\/strong><\/code><\/td>\n<\/tr>\n<tr>\n<td><code><strong>groupCount(\"variable\").by(projection_axis)<\/strong><\/code><\/td>\n<td>side effect<\/td>\n<td>setting<\/td>\n<td><code><strong>Map&lt;X,Long&gt;<\/strong><\/code><\/td>\n<\/tr>\n<tr>\n<td><code><strong>as(\"label\")<\/strong><\/code><\/td>\n<td>label<\/td>\n<td>setting<\/td>\n<td><code><strong>Traversal<\/strong><\/code><\/td>\n<\/tr>\n<tr>\n<td><code><strong>subgraph(\"variable\")<\/strong><\/code><\/td>\n<td>side effect<\/td>\n<td>setting<\/td>\n<td><code><strong>TinkerGraph<\/strong><\/code> or <code><strong>Traversal<\/strong><\/code> depending on how you access it<\/td>\n<\/tr>\n<tr>\n<td><code><strong>select(\"variable_or_label\")<\/strong><\/code><\/td>\n<td>side effect or label<\/td>\n<td>reading<\/td>\n<td>depends on the variable<\/td>\n<\/tr>\n<tr>\n<td><code><strong>cap(\"variable\")<\/strong><\/code><\/td>\n<td>side effect or label<\/td>\n<td>reading<\/td>\n<td>depends on the variable<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>A. store(variable)<\/h3>\n<p>First let&#8217;s see how many users have rated <strong>Blade Runner<\/strong> more than 9\/10:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).\r\n  count()\r\n==&gt;45\r\n<\/pre>\n<p>Ok, now let&#8217;s save all those 45 ratings using <code><strong>store()<\/strong><\/code> operator:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;). \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).    \/\/ Iterator&lt;Rated_Edge&gt;\r\n  store(&quot;rated_edges&quot;).                  \/\/ Store rated edges inside a Collection\r\n  outV().                                \/\/ Iterator&lt;User&gt;\r\n  select(&quot;rated_edges&quot;).                 \/\/ Iterator&lt;BulkSet&lt;Rated_Edge&gt;&gt;   \r\n  next().                                \/\/ BulkSet&lt;Rated_Edge&gt;\r\n  size()                          \r\n==&gt;5\r\n<\/pre>\n<p>Strangely we only have 5 edges stored in the <em>&#8220;rated_edges&#8221;<\/em> collection. Why ? The reason is that <code><strong>store()<\/strong><\/code> is a lazy operator and only appends the traversed edges lazily into the <em>&#8220;rated_edges&#8221;<\/em> collection. The time the traverser reaches the <code><strong>next().size()<\/strong><\/code> step, only 5 edges have been accumulated into the <em>&#8220;rated_edges&#8221;<\/em> collection so far.<\/p>\n<p>To force <strong>Gremlin<\/strong> to fill up completely the <em>&#8220;rated_edges&#8221;<\/em> collection so that <code><strong>next().size()<\/strong><\/code> returns the expected 45 count, we can use the <code><strong>barrier()<\/strong><\/code> instruction. In this case <strong>Gremlin<\/strong> will perform a breadth-first evaluation of all rated edges before moving to the next step<\/p>\n<blockquote><p>The concept of depth-first and breadth-first will be discussed in a future blog post<\/p><\/blockquote>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;). \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).    \/\/ Iterator&lt;Rated_Edge&gt;\r\n  store(&quot;rated_edges&quot;).                  \/\/ Store rated edges inside a Collection\r\n  outV().                                \/\/ Iterator&lt;User&gt;\r\n  barrier().                             \/\/ Force eager evaluation of all previous steps \r\n  select(&quot;rated_edges&quot;).                 \/\/ Iterator&lt;BulkSet&lt;Rated_Edge&gt;&gt;     \r\n  next().                                \/\/ BulkSet&lt;Rated_Edge&gt;\r\n  size()                          \r\n==&gt;45\r\n<\/pre>\n<p>Optionally, instead of using <code><strong>select(\"rated_edges\")<\/strong><\/code> we could use <code><strong>cap(\"rated_edges\")<\/strong><\/code> instead. <code><strong>cap()<\/strong><\/code> will force an eager evaluation of all previous step, pretty similar to <code><strong>barrier()<\/strong><\/code>. Indeed you can see <code><strong>cap(xxx)<\/strong><\/code> as a shorthand for <code><strong>barrier().select(xxx)<\/strong><\/code><\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;). \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).    \/\/ Iterator&lt;Rated_Edge&gt;\r\n  store(&quot;rated_edges&quot;).                  \/\/ Store rated edges inside a Collection\r\n  outV().                                \/\/ Iterator&lt;User&gt;\r\n  cap(&quot;rated_edges&quot;).                    \/\/ Iterator&lt;BulkSet&lt;Rated_Edge&gt;&gt;     \r\n  next().                                \/\/ BulkSet&lt;Rated_Edge&gt;\r\n  size()                          \r\n==&gt;45\r\n<\/pre>\n<h3>B. aggregate(variable)<\/h3>\n<p>Aggregate works like <code><strong>store()<\/strong><\/code> but in an eager fashion. Consequently with <code><strong>aggregate()<\/strong><\/code> you don&#8217;t need to resort to neither <code><strong>barrier()<\/strong><\/code> nor to <code><strong>cap()<\/strong><\/code><\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;). \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).    \/\/ Iterator&lt;Rated_Edge&gt;\r\n  aggregate(&quot;rated_edges&quot;).              \/\/ Store rated edges inside a Collection\r\n  outV().                                \/\/ Iterator&lt;User&gt;\r\n  select(&quot;rated_edges&quot;).                 \/\/ Iterator&lt;BulkSet&lt;Rated_Edge&gt;&gt;     \r\n  next().                                \/\/ BulkSet&lt;Rated_Edge&gt;\r\n  size()                          \r\n==&gt;45\r\n<\/pre>\n<blockquote><p><code><strong>aggregate(x).select(x)<\/strong><\/code> == <code><strong>store(x).cap(x)<\/strong><\/code> == <code><strong>store(x).barrier().select(x)<\/strong><\/code><\/p><\/blockquote>\n<h3>C. group(variable).by(grouping_axis).by(projection_axis)<\/h3>\n<p>The <code><strong>group(variable)<\/strong><\/code> allows you to store the partial grouping result (which is a <code><strong>Map&lt;X,Y&gt;<\/strong><\/code> structure) as a side effect of your traversal.<\/p>\n<p>Let&#8217;s say we want to save all fans of <strong>Blade Runner<\/strong> and <strong>Inception<\/strong> and group them by their <em>age<\/em> and project on their <em>userId<\/em>:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).     \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Blade Runner\r\n    by(&quot;age&quot;).                               \/\/ By age\r\n    by(&quot;userId&quot;).                            \/\/ Project on userId\r\n  V().                                       \/\/ Restart a new traversal   \r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Inception&quot;).        \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Inception \r\n    by(&quot;age&quot;).                               \/\/ By age \r\n    by(&quot;userId&quot;).                            \/\/ Project on userId\r\n  select(&quot;fans_scifi&quot;)                       \/\/ Recall the Map&lt;Int==age, Collection&lt;String==userId&gt;&gt;\r\nRequest timed out while processing - increase the timeout with the :remote command\r\nType ':help' or ':h' for help.\r\nDisplay stack trace? [yN]\r\n<\/pre>\n<p>Strangely the query timed out. Is it related to any lazy evaluation rule or something like that ? Not at all. The explanation is given inside the 1<sup>st<\/sup> comment on <a href=\"https:\/\/issues.apache.org\/jira\/browse\/TINKERPOP-1741\" target=\"_blank\"><strong>TINKERPOP-1741<\/strong><\/a>:<\/p>\n<blockquote><p><strong>You can&#8217;t <code>select('a')<\/code>. You have to <code>cap('a')<\/code>. This is because GroupStep requires a &#8220;on completion&#8221; computation. Why is it like this? When should reduction happen? <code>select()<\/code> just grabs the side-effect and if it hasn&#8217;t been reduced (because it might be reduced later), then that&#8217;s that. Why not have it reduced at <code>group('a')<\/code> \u2013 nope, you can&#8217;t cause you typically use side-effects repeatedly (e.g. in a <code>repeat()<\/code>). If you wanted it reduced after <code>group('a')<\/code>, you would use <code>group().store('a')<\/code>. Thus, the only step that we have the forces reduction on a side-effect is <code>cap()<\/code><\/strong><\/p><\/blockquote>\n<p>Long story short, when you&#8217;re doing <code><strong>group(variable)<\/strong><\/code> the map structure is not closed and not available for reading yet until you call <code><strong>cap(variable)<\/strong><\/code>. The reason not to close the map structure and let it open is because we may accumulate more data into it later in the traversal and that&#8217;s what exactly happens with the above traversal. <\/p>\n<p>First we cumulate fans of <strong>Blade Runner<\/strong> into <em>&#8220;scifi_fans&#8221;<\/em> map and we re-use it later to store <strong>Inception<\/strong> fans.<\/p>\n<p>Please notice the use of <code><strong>V().<\/strong><\/code> in the middle of our traversal. Once we have grouped all fans of <strong>Blade Runner<\/strong>, we restart a new traversal by jumping back to the original <code><strong>V().<\/strong><\/code> iterator on vertices.<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).     \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Blade Runner\r\n    by(&quot;age&quot;).                               \/\/ By age\r\n    by(&quot;userId&quot;).                            \/\/ Project on userId\r\n  V().                                       \/\/ Restart a new traversal   \r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Inception&quot;).        \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Inception \r\n    by(&quot;age&quot;).                               \/\/ By age \r\n    by(&quot;userId&quot;).                            \/\/ Project on userId\r\n  cap(&quot;scifi_fans&quot;)                       \/\/ Recall the Map&lt;Int==age, Collection&lt;String==userId&gt;&gt;\r\n==&gt;{64=[u408, u476, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u411, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452, u452], 18=[u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978, u978], \r\n...\r\n<\/pre>\n<p>The result is ugly and there are a lot of userId duplicate for each age. We need a de-duplication:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).     \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Blade Runner\r\n    by(&quot;age&quot;).                               \/\/ By age\r\n    by(values(&quot;userId&quot;).dedup()).            \/\/ Project on userId\r\n  V().                                       \/\/ Restart a new traversal   \r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Inception&quot;).        \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Inception \r\n    by(&quot;age&quot;).                               \/\/ By age \r\n    by(values(&quot;userId&quot;).dedup()).            \/\/ Project on userId\r\n  cap(&quot;scifi_fans&quot;)                       \/\/ Recall the Map&lt;Int==age, Collection&lt;String==userId&gt;&gt;\r\n==&gt;{64=u452, 18=u978, 19=u1031, 20=u637, 21=u1070, 22=u356, 23=u390, 24=u355, 25=u255, 26=u628, 27=u685, 28=u979, 29=u420, 32=u473, 33=u548, 34=u262, 35=u785, 36=u744, 37=u398, 39=u365, 40=u693, 41=u513, 42=u347, 43=u287, 44=u442, 45=u565, 46=u335, 47=u591, 48=u622, 49=u455, 50=u555, 51=u507, 52=u416, 53=u328, 54=u268, 55=u332, 56=u530, 57=u522, 58=u551, 59=u349, 60=u630, 61=u617}\r\n<\/pre>\n<p>The result is shorter but also wrong. There is only 1 user for each age group. Why ? This is again a classic caveat. For all inner traversals Gremlin will call by default <code><strong>next()<\/strong><\/code> to get the result\/output of this inner traversal. Since <code><strong>values(\"userId\").dedup()<\/strong><\/code> is a traversal, we need to use <code><strong>fold()<\/strong><\/code> to collect all the userId into a collection structure:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).     \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Blade Runner\r\n    by(&quot;age&quot;).                               \/\/ By age\r\n    by(values(&quot;userId&quot;).dedup().fold()).     \/\/ Project on userId\r\n  V().                                       \/\/ Restart a new traversal   \r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Inception&quot;).        \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  group(&quot;scifi_fans&quot;).                       \/\/ Group fans of Inception \r\n    by(&quot;age&quot;).                               \/\/ By age \r\n    by(values(&quot;userId&quot;).dedup().fold()).     \/\/ Project on userId\r\n  cap(&quot;scifi_fans&quot;)                          \/\/ Recall the Map&lt;Int==age, Collection&lt;String==userId&gt;&gt;\r\n==&gt;{64=[u452, u408, u411, u476], 18=[u978], 19=[u1031, u292], 20=[u637, u483], 21=[u1070, u689, u239, u305], 22=[u356, u501, u456], 23=[u390, u678, u841], 24=[u355, u692, u718], 25=[u255, u724, u963], 26=[u628, u848, u598], 27=[u685, u339, u240, u445], 28=[u979, u497], 29=[u420, u861, u474, u1000], 32=[u473], 33=[u548, u547], 34=[u262], 35=[u785, u603, u223, u511], 36=[u744, u267], 37=[u398, u386, u434, u646], 39=[u365, u896], 40=[u693, u764], 41=[u513, u498], 42=[u347, u536], 43=[u287, u389, u251, u327], 44=[u442], 45=[u565], 46=[u335], 47=[u591, u466], 48=[u622], 49=[u455], 50=[u555, u252], 51=[u507], 52=[u416, u263], 53=[u328], 54=[u268, u524], 55=[u332], 56=[u530, u477, u396], 57=[u522, u210], 58=[u551], 59=[u349, u202], 60=[u630, u218, u491, u378], 61=[u617]}\r\n<\/pre>\n<p>So far so good.<\/p>\n<h3>D. groupCount(variable).by(grouping_axis)<\/h3>\n<p><code><strong>groupCount()<\/strong><\/code> is just a special case of <code><strong>group()<\/strong><\/code> so it works exactly the same:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).     \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  groupCount(&quot;scifi_fans&quot;).                  \/\/ Group fans of Blade Runner\r\n    by(&quot;age&quot;).                               \/\/ By age\r\n  V().                                       \/\/ Restart a new traversal   \r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Inception&quot;).        \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  groupCount(&quot;scifi_fans&quot;).                  \/\/ Group fans of Inception \r\n    by(&quot;age&quot;).                               \/\/ By age \r\n  cap(&quot;scifi_fans&quot;)                          \/\/ Recall the Map&lt;Int==age, Long==user count&gt;\r\n==&gt;{64=92, 18=45, 19=46, 20=90, 21=92, 22=92, 23=91, 24=47, 25=135, 26=47, 27=92, 28=90, 29=48, 32=45, 33=46, 34=1, 35=92, 36=91, 37=92, 39=47, 40=46, 41=46, 42=2, 43=92, 44=45, 45=1, 46=1, 47=46, 48=1, 49=45, 50=90, 51=45, 52=90, 53=1, 54=47, 55=45, 56=91, 57=46, 58=1, 59=46, 60=92, 61=45}\r\n<\/pre>\n<h3>E. as(label)<\/h3>\n<p>The case of <code><strong>as(label)<\/strong><\/code> is interesting. It allows you to label any step in your traversal to refer to it later. Used in combination with <code><strong>select()<\/strong><\/code> you can jump back and forth inside a traversal.<\/p>\n<p>Taking the previous examples of Blade Runner fans:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;). \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).    \/\/ Iterator&lt;Rated_Edge&gt;\r\n  as(&quot;rated_edges&quot;).                     \/\/ Label this step &quot;rated_edges&quot;\r\n  outV().                                \/\/ Iterator&lt;User&gt;\r\n  select(&quot;rated_edges&quot;).                 \/\/ should be Iterator&lt;Rated_Edge&gt;\r\n  next()                                 \/\/ Rated_Edge\r\n==&gt;e[{~label=rated, ~out_vertex={~label=user, community_id=227508608, member_id=635}, ~in_vertex={~label=movie, community_id=285248128, member_id=2}, ~local_id=c2ef7ea4-7066-11e7-897a-edc6e0130fc0}][{~label=user, community_id=227508608, member_id=635}-rated-&gt;{~label=movie, community_id=285248128, member_id=2}]\r\n<\/pre>\n<p>It is also possible to assign multiple labels and recall them together. In this traversal we display the fans of <strong>Blade Runner<\/strong> as well as their rating:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;). \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).    \/\/ Iterator&lt;Rated_Edge&gt;\r\n  as(&quot;rated_edges&quot;).                     \/\/ Label this step &quot;rated_edges&quot;\r\n  outV().                                \/\/ Iterator&lt;User&gt;\r\n  as(&quot;fans&quot;).                            \/\/ Label this step &quot;fans&quot;\r\n  select(&quot;fans&quot;,&quot;rated_edges&quot;).          \/\/ Select &quot;fans&quot; and &quot;rated_edges&quot;\r\n    by(&quot;userId&quot;).                        \/\/   project &quot;fans&quot; on userId\r\n    by(&quot;rating&quot;).                        \/\/   project &quot;rated_edges&quot; on rating\r\n==&gt;{fans=u622, rated_edges=9}\r\n==&gt;{fans=u628, rated_edges=9}\r\n==&gt;{fans=u223, rated_edges=9}\r\n==&gt;{fans=u434, rated_edges=9}\r\n==&gt;{fans=u1031, rated_edges=9}\r\n==&gt;{fans=u718, rated_edges=9}\r\n==&gt;{fans=u524, rated_edges=9}\r\n==&gt;{fans=u365, rated_edges=9}\r\n==&gt;{fans=u689, rated_edges=9}\r\n==&gt;{fans=u390, rated_edges=9}\r\n...\r\n<\/pre>\n<p>Easy. Now what if the 2 labeled steps are independent e.g. there is a mid traversal ?<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).     \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  as(&quot;fans_blade_runner&quot;).                   \/\/ Label this step &quot;fans_blade_runner&quot;\r\n  V().                                       \/\/ new traversal \r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Inception&quot;).        \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV(). \/\/ Iterator&lt;User&gt;\r\n  as(&quot;fans_inception&quot;).                       \/\/ Label this step &quot;fans_inception&quot;\r\n  select(&quot;fans_blade_runner&quot;,&quot;fans_inception&quot;). \/\/ Select &quot;fans_blade_runner&quot; and &quot;fans_inception&quot;\r\n    by(&quot;userId&quot;).                               \/\/   project &quot;fans_blade_runner&quot; on userId\r\n    by(&quot;userId&quot;).                               \/\/   project &quot;fans_inception&quot; on userId\r\n  limit(10)\r\n==&gt;{fans_blade_runner=u622, fans_inception=u744}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u978}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u963}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u598}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u252}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u513}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u555}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u764}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u378}\r\n==&gt;{fans_blade_runner=u622, fans_inception=u332}\r\n<\/pre>\n<p>As we can see <strong>Gremlin<\/strong> is performing a kind of <strong>cartesian product<\/strong> between the 2 sets of users. This is explained by the fact that we have a mid traversal step <code><strong>V()<\/strong><\/code> which is equivalent to having 2 separated traversals combined together, thus the cartesian product effect.<\/p>\n<h3>F. subgraph(variable)<\/h3>\n<p><code><strong>subgraph()<\/strong><\/code> allows you to save a partial edge-induced subgraph. Consequently the  <code><strong>subgraph()<\/strong><\/code> step can only be placed after an edge step, not after a vertex step:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).outV().\r\n  subgraph(&quot;fans_blade_runner&quot;)\r\ncom.datastax.bdp.graph.impl.element.vertex.DsegCachedVertexImpl cannot be cast to org.apache.tinkerpop.gremlin.structure.Edge\r\nType ':help' or ':h' for help.\r\nDisplay stack trace? [yN]\r\n<\/pre>\n<p>To fix this:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).  \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).has(&quot;rating&quot;, gte(9)).     \/\/ Iterator&lt;Rated_Edge&gt;\r\n  subgraph(&quot;fans_blade_runner&quot;).          \/\/ Save the subgraph connected by those edges\r\n  outV().                                 \/\/ Iterator&lt;User&gt;\r\n  cap(&quot;fans_blade_runner&quot;).               \/\/ Iterator&lt;TinkerGraph&gt;\r\n  next().                                 \/\/ TinkerGraph\r\n  traversal().                            \/\/ DefaultTraversal\r\n  V().                                    \/\/ Iterator&lt;Vertex&gt;   \r\n  count()                                 \/\/ count number of vertex\r\n==&gt;46\r\n<\/pre>\n<p>Please note the interesting sequence here <\/p>\n<ul>\n<li>Similar to <code><strong>group()<\/strong><\/code>, <code><strong>subgraph()<\/strong><\/code> needs a termination step and in this case <code><strong>cap(\"fans_blade_runner\")<\/strong><\/code>is <strong>mandatory<\/strong><\/li>\n<li><code><strong>cap(\"fans_blade_runner\")<\/strong><\/code> yields a <code><strong>Iterator&lt;TinkerGraph&gt;<\/strong><\/code><\/li>\n<li><code><strong>next()<\/strong><\/code> yields a <code><strong>TinkerGraph<\/strong><\/code><\/li>\n<li><code><strong>traversal()<\/strong><\/code> yields a <code><strong>DefaultTraversal<\/strong><\/code><\/li>\n<\/ul>\n<p>We found 46 vertices, which correspond to 1 <strong>Blade Runner<\/strong> <em>Movie<\/em> vertex + 45 fans <em>User<\/em> vertices.<\/p>\n<p>There is no much usage of <code><strong>subgraph()<\/strong><\/code> as <strong>in-stream<\/strong> variable since it cannot be re-used later in the traversal itself.<\/p>\n<h3>G. select(variable_or_label)<\/h3>\n<p>There is no much to say about <code><strong>select()<\/strong><\/code> step. It can be used to pick side effect variables as well as saved labels in the path history. <strong>The only thing you should pay attention to is that any reducer step (<code><strong>sum()<\/strong><\/code>, <code><strong>mean()<\/strong><\/code>, &#8230;) will destroy path history and thus you loose all previously labeled steps<\/strong>.<\/p>\n<h3>H. cap(variable)<\/h3>\n<p>Not much to say, every thing has been said about <code><strong>cap()<\/strong><\/code> above.<\/p>\n<h1>III In-stream variable<\/h1>\n<p>In normal Gremlin pipeline, it is not unusual to create intermediate variables and re-use them later in subsequent traversal. Let&#8217;s take the recommendation engine traversal we wrote in some the <a href=\"https:\/\/www.doanduyhai.com\/blog\/?p=13285\" target=\"_blank\"><strong>previous post<\/strong><\/a>.<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;def avg_rating = g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).  \/\/ Iterator&lt;Movie&gt;\r\n  inE(&quot;rated&quot;).values(&quot;rating&quot;).          \/\/ Iterator&lt;Int&gt;\r\n  mean().next()                           \/\/ Double\r\n==&gt;8.20353982300885\r\ngremlin&gt;def genres = g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).  \/\/ Iterator&lt;Movie&gt;\r\n  out(&quot;belongsTo&quot;).                       \/\/ Iterator&lt;Genre&gt;\r\n  values(&quot;name&quot;).                         \/\/ Iterator&lt;String&gt;\r\n  fold().next()                           \/\/ Collection&lt;String&gt;  \r\n==&gt;Sci-Fi\r\n==&gt;Action\r\ngremlin&gt; g.V().\r\n......1&gt;   has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).as(&quot;blade_runner&quot;).\r\n......2&gt;   inE(&quot;rated&quot;).filter(values(&quot;rating&quot;).is(gte(avg_rating))).outV().\r\n......3&gt;   outE(&quot;rated&quot;).filter(values(&quot;rating&quot;).is(gte(avg_rating))).inV().\r\n......4&gt;   where(neq(&quot;blade_runner&quot;)).\r\n......5&gt;   filter(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean().is(gte(avg_rating))).\r\n......6&gt;   filter(out(&quot;belongsTo&quot;).has(&quot;name&quot;, within(genres))).\r\n......7&gt;   dedup().\r\n......8&gt;   project(&quot;movie&quot;, &quot;average_rating&quot;, &quot;genres&quot;).\r\n......9&gt;     by(&quot;title&quot;).\r\n.....10&gt;     by(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean()).\r\n.....11&gt;     by(out(&quot;belongsTo&quot;).values(&quot;name&quot;).fold())\r\n==&gt;{movie=Pulp Fiction, average_rating=8.581005586592179, genres=[Thriller, Action]}\r\n==&gt;{movie=Seven Samurai, average_rating=8.470588235294118, genres=[Action, Adventure, Drama]}\r\n==&gt;{movie=A Clockwork Orange, average_rating=8.215686274509803, genres=[Sci-Fi, Drama]}\r\n<\/pre>\n<p>Fine, but what if we want to avoid defining intermediate variables like <em>avg_rating<\/em> and <em>genres<\/em> ? Can we have our recommendation traversal and define those variables inside the traversal itself to make it <strong>one-liner<\/strong> ? The answer is YES!!! That&#8217;s what&#8217;s I call <strong>in-stream variables<\/strong> e.g. variables created during previous traversal steps to be re-used later.<\/p>\n<p>1<sup>st<\/sup> solution using <code><strong>aggregate()<\/strong><\/code> <\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).as(&quot;blade_runner&quot;).  \/\/ Label blade_runner\r\n  sideEffect(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean().aggregate(&quot;avg_rating&quot;)). \/\/ Save the avg_rating in a sideEffect() step\r\n  sideEffect(out(&quot;belongsTo&quot;).values(&quot;name&quot;).fold().aggregate(&quot;genres&quot;)).   \/\/ Save the genres in a sideEffect() step\r\n  inE(&quot;rated&quot;).filter(values(&quot;rating&quot;).where(gte(&quot;avg_rating&quot;)).by(unfold())).outV(). \/\/ Recall &quot;avg_rating&quot; using where()\r\n  outE(&quot;rated&quot;).filter(values(&quot;rating&quot;).where(gte(&quot;avg_rating&quot;)).by(unfold())).inV(). \/\/ Recall &quot;avg_rating&quot; using where()\r\n  where(neq(&quot;blade_runner&quot;)).\r\n  filter(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean().where(gte(&quot;avg_rating&quot;)).by(unfold())). \/\/ Recall &quot;avg_rating&quot; using where()\r\n  filter(out(&quot;belongsTo&quot;).has(&quot;name&quot;, where(within(&quot;genres&quot;)).by(unfold()))).         \/\/ Recall &quot;genres&quot; using where() \r\n  dedup().\r\n  project(&quot;movie&quot;, &quot;average_rating&quot;, &quot;genres&quot;). \r\n    by(&quot;title&quot;).                                \r\n    by(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean()).   \r\n    by(out(&quot;belongsTo&quot;).values(&quot;name&quot;).fold())\r\n<\/pre>\n<p>A little bit of explanation is required.<\/p>\n<p><code><strong>sideEffect(inE(\"rated\").values(\"rating\").mean().aggregate(\"avg_rating\"))<\/strong><\/code> : in this step we save the average rating of Blade Runner using <code><strong>aggregate(\"avr_rating\")<\/strong><\/code>. It means that the type of <em>&#8220;avg_rating&#8221;<\/em> is now a <code><strong>BulkSet&lt;Double&gt;<\/strong><\/code> with a single element. The reducing barrier step <code><strong>mean()<\/strong><\/code> is performed <strong>inside<\/strong> the <code><strong>sideEffect()<\/strong><\/code> step leaving the outside path history untouched and preserving our <em>&#8220;blade_runner&#8221;<\/em> label.<\/p>\n<p>Similarly all the genres of <strong>Blade Runner<\/strong> are saved in <em>&#8220;genres&#8221;<\/em> using <code><strong>sideEffect(out(\"belongsTo\").values(\"name\").fold().aggregate(\"genres\"))<\/strong><\/code>. The type of <em>&#8220;genres&#8221;<\/em> is a <code><strong>BulkSet&lt;String&gt;<\/strong><\/code> with 2 elements: <em>&#8220;Sci-Fi&#8221;<\/em> &#038; <em>&#8220;Action&#8221;<\/em><\/p>\n<p>Now let&#8217;s analyse <code><strong>inE(\"rated\").filter(values(\"rating\").where(gte(\"avg_rating\")).by(unfold())).outV()<\/strong><\/code>: we&#8217;re using <code><strong>where(gte(\"avg_rating\"))<\/strong><\/code> to perform variable resolution for <em>&#8220;avg_rating&#8221;<\/em>. However, what is the <code><strong>by(unfold())<\/strong><\/code> ??? Indeed it is a projection modulator for the <code><strong>where()<\/strong><\/code> clause. The data type of <em>&#8220;avg_rating&#8221;<\/em> <code><strong>BulkSet&lt;Double&gt;<\/strong><\/code> so we cannot compare a Double coming from <code><strong>values(\"rating\")<\/strong><\/code> with this. <code><strong>by(unfold())<\/strong><\/code> will project the <strong>BulkSet&lt;Double&gt;<\/strong><\/code> using <strong>unfold()<\/strong><\/code> and then we obtain a <strong>Double<\/strong><\/code> as a result.<\/p>\n<p>The same remarks apply to <code><strong>filter(out(\"belongsTo\").has(\"name\", where(within(\"genres\")).by(unfold())))<\/strong><\/code><\/p>\n<p>A 2<sup>nd<\/sup> solution using labeled step <code><strong>as()<\/strong><\/code>:<\/p>\n<pre class=\"brush: java; title: ; wrap-lines: false; notranslate\" title=\"\">\r\ngremlin&gt;g.V().\r\n  has(&quot;movie&quot;, &quot;title&quot;, &quot;Blade Runner&quot;).as(&quot;blade_runner&quot;).   \/\/ Label &quot;blade_runner&quot;\r\n  map(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean()).as(&quot;avg_rating&quot;). \/\/ Label &quot;avg_rating&quot; step\r\n  select(&quot;blade_runner&quot;).                                     \/\/ Jump back    \r\n  map(out(&quot;belongsTo&quot;).values(&quot;name&quot;).fold()).as(&quot;genres&quot;).   \/\/ Label &quot;genres&quot; step\r\n  select(&quot;blade_runner&quot;).                                     \/\/ Jump back    \r\n  inE(&quot;rated&quot;).filter(values(&quot;rating&quot;).where(gte(&quot;avg_rating&quot;))).outV(). \r\n  outE(&quot;rated&quot;).filter(values(&quot;rating&quot;).where(gte(&quot;avg_rating&quot;))).inV().\r\n  where(neq(&quot;blade_runner&quot;)).\r\n  filter(map(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean()).where(gte(&quot;avg_rating&quot;))).\r\n  filter(out(&quot;belongsTo&quot;).has(&quot;name&quot;, where(within(&quot;genres&quot;)))).\r\n  dedup().\r\n  project(&quot;movie&quot;, &quot;average_rating&quot;, &quot;genres&quot;). \r\n    by(&quot;title&quot;).                                \r\n    by(inE(&quot;rated&quot;).values(&quot;rating&quot;).mean()).   \r\n    by(out(&quot;belongsTo&quot;).values(&quot;name&quot;).fold())\r\n<\/pre>\n<p>So to decorticate this giant traversal.<\/p>\n<p><code><strong>map(inE(\"rated\").values(\"rating\").mean()).as(\"avg_rating\")<\/strong><\/code>: classic trick, wrap the reducing barrier <code><strong>mean()<\/strong><\/code> inside a <code><strong>map()<\/strong><\/code> to avoid loosing path history and assign the result a label. <em>&#8220;avg_rating&#8221;<\/em> is now an <code><strong>Iterator&lt;Double&gt;<\/strong><\/code> with a single element<\/p>\n<p>Same strategy for <code><strong>map(out(\"belongsTo\").values(\"name\").fold()).as(\"genres\")<\/strong><\/code>:  the data type of  <em>&#8220;genres&#8221;<\/em> is <code><strong>Iterator&lt;String&gt;<\/strong><\/code> with 2 elements.<\/p>\n<p>Please notice the repeated usage of <code><strong>select(\"blade_runner\")<\/strong><\/code> to force <strong>Gremlin<\/strong> to backtrack to the original movie vertex to restart a new traversal from there.<\/p>\n<p><code><strong>inE(\"rated\").filter(values(\"rating\").where(gte(\"avg_rating\"))).outV()<\/strong><\/code>: unlike previously when we were using <code><strong>aggregate()<\/strong><\/code> we don&#8217;t need any <code><strong>by()<\/strong><\/code> modulator here since the average rating is not nested inside any collection. <code><strong>where(gte(\"avg_rating\"))<\/strong><\/code> will just pop the average rating value out from the <code><strong>Iterator&lt;Double&gt;<\/strong><\/code>, calling implicitly <code><strong>next()<\/strong><\/code> on it.<\/p>\n<p>The rest of the traversal is quite clear.<\/p>\n<p>And that&#8217;s all folks! <strong>Do not miss the other Gremlin recipes in this series<\/strong>.<\/p>\n<p>If you have any question about <strong>Gremlin<\/strong>, find me on the <strong><a href=\"http:\/\/datastaxacademy.slack.com\" target=\"_blank\">datastaxacademy.slack.com<\/a><\/strong>, channel <strong>dse-graph<\/strong>. My id is <em>@doanduyhai<\/em>   <\/p>\n","protected":false},"excerpt":{"rendered":"<p>This blog post is the 7th from the series Gremlin Recipes. It is recommended to read the previous blog posts first: Gremlin as a Stream SQL to Gremlin Recommendation Engine traversal Recursive traversals Path object Projection and selection<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[58,10],"tags":[],"_links":{"self":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/13374"}],"collection":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=13374"}],"version-history":[{"count":26,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/13374\/revisions"}],"predecessor-version":[{"id":13403,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/13374\/revisions\/13403"}],"wp:attachment":[{"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=13374"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=13374"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.doanduyhai.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=13374"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}