Sort by number of child tiddlers

Hello,

I believe it’s not a big problem, but I just can’t solve it.

I want to sort tiddlers by number of child tiddlers. Specifically, this is a wiki about my OCs, there are tiddlers of OC (tagged “OC”), and tiddlers of scanned image pages (tagged “imagepage”, and names of all mentioned OCs in that image (same as the OC tiddler title)). I want to know how many imagepage each OC have.

This is what I intuitively wrote:

<$vars pagestagging="[tag<currentTiddler>tag[imagepage]count[]]">
<table>
<tr><th>name</th><th>imagepage number</th></tr>
<$list filter="[tag[OC]sortsub:integer<pagestagging>]"><tr>
<td><$link to=<<currentTiddler>>/></td>
<td><$list filter=<<pagestagging>>>{{!!title}}</$list></td>
</tr></$list>
</table>
</$vars>

However, I got something like

name imagepage number
Aaron 1
Alice 3
Bob 1
Candice 5

The “pagestagging” filter worked in the second column, but the tiddlers are not sorted, they displayed by default alphabetical order.

How do I fix this?

Hello and welcome! Something like this should work — but modify as needed, and feel free to ask follow-ups:

<$let maxx={{{ [tags[]!is[system]] :map[tagging[]count[]] +[maxall[]] }}}>
<!-- the variable <<maxx>> (name it however you like!) marks the high-end of the possible count of tag-children, in your wiki. Its definition works by getting a list of non-system tags, and then using :map operator gets the count of each one, then selecting the maxall of those numbers. -->

<$list filter="[range<maxx>,[1]]" variable="descending-count">
<!-- starting with that maxx variable (highest count for tag-children), we count downward to 1, using range operator). Define <<descending-count>> to track the number we're at in this downward sequence… -->

<$list filter="""[tags[]!is[system]sort[]] :filter[tagging[]count[]match<descending-count>]""" variable="TagAtThisCount">
<!-- For the number we're currently at, we show tags whose count matches that number -->

<$tiddler tiddler=<<TagAtThisCount>> > <<tag>> <<descending-count>> </$tiddler><br>
<!-- For each tag, display whatever details you want... -->

</$list>
</$list>
</$let>

Thank you, Springer!
This filter was so much work for TidGi that it stopped working for several seconds, but it worked!

There are surely ways to make this more efficient to run, if you’re running into routine performance issues.

ALSO: I think there’s one edit I had made inconsistently on the code initially posted above. I’ve tweaked it, and it should be more efficient now. Check again, and see if it’s any smoother?

Hi @Lexie_Li

Working on your question, I had the intuition that maybe the input for your subsort filter might be incomplete. I confirmed it with my test data, so maybe you can try this:

<$vars pagestagging="[all[tiddlers]tag<currentTiddler>tag[imagepage]count[]]">
<table>
<tr><th>name</th><th>imagepage number</th></tr>
<$list filter="[tag[OC]sortsub:integer<pagestagging>]"><tr>
<td><$link to=<<currentTiddler>>/></td>
<td><$list filter=<<pagestagging>>>{{!!title}}</$list></td>
</tr></$list>
</table>
</$vars>

Note I prefixed the filter with all[tiddlers].

Please tell me how it goes.

Fred

1 Like

This works more closely with OP’s details and begins with their approach (while I just quickly ported over a solution of my own from another wiki)!

I often forget that I can define with <$vars …> and <currentTiddler> outside the scope of where it’s actually getting applied, knowing that the context of application (inside a list widget) determines what means when the variable is invoked there.

1 Like

Well, to be honest, I started by not reading OP’s code, because their question inspired me a solution with a :sort filter run prefix and a function, but it didn’t work immediately. I had to add the all[tiddlers] prefix to make it work.
Then only I realized it might be OP’s problem too, hence my answer.

This comes in handy when it’s difficult to pass a variable as a macro/procedure parameter inside a widget like <$button actions="""<<mymacro>>""" ...> for example.

Fred

1 Like

Another approach is <currentTiddler>tagging[] — which will capture shadow tiddlers as well. For a tag such as $:/tags/Stylesheet or $:/tags/ViewTemplate sometimes I really want to have a sense of ALL the things with that tag, but starting with all[tiddlers] actually leaves out the shadow tiddlers…

I just made a modified example of @Springer’s that works on TiddlyWiki.com with its data;

<$vars sort-index="[all[tiddlers]tag<currentTiddler>count[]]">
<table>
<tr><th>name</th><th>imagepage number</th></tr>
<$list filter="[tag[TableOfContents]sort[]!sortsub:integer<sort-index>]"><tr>
<td><$link to=<<currentTiddler>>/></td>
<td><$list filter=<<sort-index>>>{{!!title}}</$list></td>
</tr></$list>
</table>
</$vars>
  • Here it counts the number of times the current tiddler is used as a tag.

Of note is the additional sort[] which ensures items with the same number of tags appear in alphabetical order.

My research suggests there may be 3 or 4 ways to achieve similar results, I am interested in identifying the most intuitive thus easiest to use and share.

I will update here if I find a better one

Here’s another option without the subfilter:

<table>
<tr><th>name</th><th>imagepage number</th></tr>
<$list filter="[tag[TableOfContents]sort[]] :sort:integer:reverse[{!!title}tagging[]count[]]"><tr>
<td><$link/></td>
<td><$count filter="[{!!title}tagging[]]" /></td>
</tr></$list>
</table>
2 Likes

Why did I not think it was possible to use the sort filter prefix for this??

Thanks!

1 Like

I was also thinking about separating the sort index from the sort so others can be used interchangeably

\function tag.count() [{!!title}tagging[]count[]]
\function list.count() [{!!title}list[]count[]]
\function tags.count() [{!!title}tags[]count[]]
<table>
<tr><th>name</th><th>number</th></tr>
<$list filter="[tag[TableOfContents]sort[]] :sort:integer:reverse[<tag.count>]"><tr>
<td><$link to=<<currentTiddler>>/></td>
<td><<tag.count>></td>
</tr></$list>
</table>
  • You will observe the third line needs to also be changed to reflect a change in function see generalised
  • I have started to play with the possibility of moving a filter run into a function. No luck yet.

Note: that the above functions are valid as global macros so it makes sense to make them even more versatile for use as global macros;

\function tag.count(tiddler:"currentTiddler") [<tiddler>getvariable[]tagging[]count[]]
\function list.count(tiddler:"currentTiddler") [<tiddler>getvariable[]list[]count[]]
\function tags.count(tiddler:"currentTiddler") [<tiddler>getvariable[]tags[]count[]]
  • These now default to currentTiddler but can be now passed any “variable name” containing a title.
  • of course a number of other common counts can be included

Generalised

However since the function is interchangable we can “paramatise the function name”,

<$let sort-function="tag.count">
<table>
<tr><th>#</th><th>name</th><th>number</th></tr>
<$list filter="[tag[TableOfContents]sort[]] :sort:integer:reverse[function<sort-function>]" counter=item><tr>
<td><<item>></td>
<td><$link/></td>
<td><$macrocall $name=<<sort-function>>/></td>
</tr></$list>
</table>
</$let>
  • Note I find the old macrocall works here and is more readable.
  • I have added an item column
  • One could store the item number and title to freeze the order in each tiddler or a data tiddler

Thank you FRed, but somehow the table was not sorted; and all counts were 0.

Thank you, Tones!
It worked, and was much quicker than what we previously got.
However I wasn’t quite sure where to add the “tag[imagepage]” part in these neatly written filters. (The filter here is supposed to count all child tiddlers, not only those with an “imagepage” tag, …right?)

Right. Like my first post, @TW_Tones seems to have been making a general solution, which you should tweak as needed:

<$let sort-function="tag.count">
<table>
<tr><th>#</th><th>name</th><th>number</th></tr>
<$list filter="[tag[TableOfContents]sort[]] …

Replace that segment [tag[TableOfContents]sort[]] with whatever your desired filter is, such as tag<currentTiddler>tag[OC] … … sort[]]. (And you can omit that ending sort[] step, since there’s a sort coming up in the next filter run.)

And in the functions themselves — if you ALWAYS just want counts for the imagepage-tagged stuff, then:

\function tag.count() [{!!title}tagging[]tag[imagepage]count[]]
1 Like

FYI: I added this additional sort to put the items in alphabetical order before the subsequent sort via a count. Sometimes a count will come up with the same number for more than one title, this will result in it sorting alphabetically within each matching count.

  • I knew this was possible but I have struggled to find good working examples. Thus in filters sort1 > sort2 will result in items sorted by sort2 and within that sort1.
1 Like

This is a very useful technique. It’s much, much easier than trying to do a complex sort by count-then-alphabetical-order in one go. A decade ago it wasn’t guaranteed, but the 2015 JS standard required that sorts be stable, and so now a double-, triple-, or multiple-sort is easy to achieve by running the sorts in reverse priority order.

2 Likes