Subtracting collections

Is there a way to subtract a collection from another one? I can’t seem to find any function that does that.

Here’s what I’m trying to do on the same page:

  1. Display 4 latest posts – easy.
  2. Display 4 random posts but avoid duplicates with the posts already displayed in (1).

Essentially, I’m missing the operation SUBTRACT from the listing below.

{{ $latest := first 4 (where .Pages "Type" "posts") }}
{{ $randomCandidates := SUBTRACT (where .Pages "Type" "posts") $latest }}
{{ $random := first 4 (shuffle $randomCandidates) }}

I did something similar using the following code:

{{ range first 6 (after 3 (.Data.Pages.ByDate.Reverse)) }}

The first 3 articles are already displayed separately…

Cool workaround. It’ll work for this case, but not for some other cases that I didn’t post here for clarity.

Intersect

intersect returns common elements, while I would need something more like xor.

I wrote the above on mobile, hence the “brevity”.

{{ $latest := first 4 (where .Pages "Type" "posts") }}
{{ $randomCandidates := (where .Pages "Type" "posts") }}
{{ $randomCandidates =  $randomCandidates  | intersect (where $randomCandidates "not in" $latest) }}
{{ $random := first 4 (shuffle $randomCandidates) }}

The above is totally intested, of course …

1 Like

Of course, looking at the above, the intersect is superflous:

{{ $latest := where .Pages "Type" "posts" | first 4 }}
{{ $randomCandidates := (where .Pages "Type" "posts") }}
{{ $randomCandidates = where $randomCandidates "not in" $latest }}
{{ $random := $randomCandidates | shuffle | first 4  }}
1 Like

Ah, now I see. Didn’t know that where ... "not in" accepted whole objects. Thanks!

Not quite there yet. This part:

{{ $randomCandidates = where $randomCandidates "not in" $latest }}

causes an error:

executing “main” at <where $randomCandida…>: error calling where: not in isn’t a field of struct type *hugolib.

I guess that’s because where expects a key as the second parameter? What should be the key here?

I was wrong, the above does not work. I will get back to you if I come up with something that actually works.

Hugo lacks difference and symmetric difference functions to go along with union and intersect. We should consider adding those.

As was already noted, a naive solution to your example:

{{ $set := where .Pages "Type" "posts" }}
{{ $latest := first 4 $set }}
{{ $random := first 4 (shuffle (after 4 $set)) }}

The only way to accomplish the subtraction/difference logic today is to key off of a unique Page property:

{{ $set := where .Pages "Type" "posts" }}
{{ $latest := first 4 $set }}

{{ $exclude := slice }}
{{ range $latest }}{{ $exclude = append .Filename $exclude }}{{ end }}
{{ $random := first 4 (shuffle (where $set "Filename" "not in" $exclude)) }}
4 Likes

Thanks for that @moorereason, it works. If you can add explicit difference functions that’d be grand too :slight_smile:

Yes, that would be useful, if we could find good names.

1 Like

I suggest “complement” for the set difference, just as Wolfram do (and we could have it for multiple sets), e.g. complement setA setB setC ... would output all elements of setA that are not in setB, nor in set C, etc.

(No good name idea for symmetric difference).

3 Likes

I have implemented the complement funcion – but when I was looking for a good example for the docs, I see that the above isn’t a particular good one, as it could be rewritten as:

{{ $all := where .Pages "Type" "posts" }}
{{ $first4 := $all | first 4 }}
{{ $randomCandidates := $all | after 4 }}
{{ $random := $randomCandidates | shuffle | first 4 }}

suggestion
https://docs.microsoft.com/en-us/sql/t-sql/language-elements/set-operators-except-and-intersect-transact-sql?view=sql-server-2017

INTERSECT and EXCEPT

this works for over 10 years in databases

I have boldly added:

  • complement
  • symdiff

I’m open to change the names above if someone can come with a convincing argument. I guess “complement” reads nicely, symdiff not so much.

1 Like

Just tried out the newly released complement – works superbly. Thank you!

1 Like

Thanks for the compliment on the complement.

2 Likes