Stumped on complex sort

Inspired by this thread (with variable-responsive html tables), I wanted to mock up a shiraz-style dynamic table that does the same things.

But I realized (in mocking up my suggestion) that I was stumped by the sorting needs of @mswho42 — which seem to combine :sortsub-style need (retrieve the value in the stack field, since that value is the basis of sort order) with sortby-style recourse to a separate tiddler (that contains the custom sort order for those values).


For a simpler example, documentation shows how to do
{{{ Friday Monday +[sortby{Days of the Week!!list}] }}}

… But if I have a list of tiddlers each with a day field, how do I get that list to sort based on their day field, but in the custom order? Seems to need a sortsub move, but I’m having trouble getting that play nice with sortby…?

<$vars compare-by-day="[get[day]sortby{Days of the Week!!list}]">

<$list filter="[[Amanda]] [[Jane]] +[sortsub<compare-by-day>]">

{{!!title}} — {{!!day}}

</$list>

The result is

Amanda — Friday
Jane — Monday

but of course I’m hoping to get it the other way `round … (which then should map onto the more complex problem in the other post)

???

Sorry, keyboard mishit posted before I finished.

1 Like

I don’t have time now to consider how we might get this right, but I can explain why it’s not meeting your expectations.

sortby<whatever> takes a list, and sorts that entire list based on their positions in whatever.

sortsub<converter> takes a list, uses the runnable converter (function, procedure, macro) to create an index for each element, then sorts the originals by that index.

The latter is more general, as we could derive sortby from sortsub in a fairly straightforward manner, but not vice versa.

Think of sortsub like this:

Amanda Jane  +[sortsub:number<letter-count>]
(calculate `Amanda -> 6` and `Jane -> 4`)
sort the pairs (Amanda, 6), (Jane, 4) numerically by their second element to get:
(Jane, 4), (Amanda, 6)
then extract the first components, to get:
Jane Amanda

I doubt this is the exact mechanism, but it should show the idea.

I will often use sortsub:string<by-last> with something like

\procedure by-last(tid) [<tid>get[last-name]] [<tid>get[first-name]] +join[~]]

Aside: TW seems to be missing the most powerful of JS’s sorting tools: supplying a comparator, which for any pair A and B returns a number which is negative if A should come before B, zero if it doesn’t matter, and positive if B should come before A. That’s harder to use, but quite powerful.

I mostly solved my own problem… Unsurprisingly, it’s actually easy to get the sort needed here — just so long as we go at it from the other end (which is pretty much the logic @mswho42 used in that other thread, though that approach employed nested list widgets):

\function sortby.StackOrder()
[{StackingOrder}enlist-input[]listed[stack]is[tiddler]]
\end

{{{ [sortby.StackOrder[]] +[format:titlelist[]] }}}

(And further conditions can be integrated into that same filter run.)

Or in the simplified example I started with, it might look like:

\function sortby.weekday()
[{Days of the Week!!list}enlist-input[]listed[day]is[tiddler]]
\end

<$list filter="[sortby.weekday[]]">
{{!!title}} — {{!!day}}<br>
</$list>

This does get the required order.

Alas, there’s still no “hook” that is easily harnessed by shiraz dynamic tables… which was the impetus — hoping I could get it set up so that the table could sort tiddlers according to a column/field with a proprietary (indirectly-established) order.

I’m glad to hear it, as I’ve gotten stumped myself.

I really feel there are one or two missing operators that would help here, notably indexof:

[enlist[A B C D E]indexof[D]]

would yield 4.

With something like that, I think we could quickly write a filter that would help us do these types of sorting.

I can write it in JS easily enough, but it’s more annoying in wikitext.

Still, this should work with @mswho42 's custom tables, allowing us to run a filter or three to choose the inputs and just sort them with sortsub,

It’s bedtime now, and I can’t go any further right now, but here’s one barely tested JS module:

title: $:/core/modules/filters/indexof.js
type: application/javascript
module-type: filteroperator

/*\
title: $:/core/modules/filters/indexof.js
type: application/javascript
module-type: filteroperator

Filter operator for the index of a value in list

\*/

"use strict";

/*
Export our filter function
*/
exports.indexof = function(source,operator,options) {
	var results = [], index = 0, found = false;
	source(function(tiddler, title) {
		index += 1;
		if(!found && String(title) == operator.operand) {
			results.push(String(index));
			found = true;
		}
	});
	if (!found) {results.push(String(Number.MAX_SAFE_INTEGER));}
	return results;
};

If it’s not found, we return a very large number. Traditionally a not-found would yield -1, but this version feels better for the type of sorting we’re discussing.

I see you have a working JS solution already, but I think you could do something similar with a function operator:

\function index.of(item) [allbefore:include<item>count[]]
\function zindex.of(item) [allbefore<item>count[]]

{{{ [enlist[A B C D E]index.of[D]] }}}

{{{ [enlist[A B C D E]zindex.of[D]] }}}

As I was writing this it occurred to me that index.of is essentially the inverse of the existing nth operator, so just for fun, I added zindex.of to mirror zth.

Both return “0” for any parameter not present in the input list, e.g. {{{ [enlist[A B C D E]index.of[Q]] }}} = 0. I don’t read JS well enough to understand how your custom operator handles missing items, but I suspect the function could also be tweaked to provide your preferred output — as a rough guess, possibly something like

\function index.of(item) [match<item>] :then[allbefore:include<item>count[]] ~[[missing value]]

{{{ [enlist[A B C D E]index.of[Q]] }}}

where “missing value” could be “-1” if you prefer.

Edit: Or the following, which will bump any missing value to the end of the list rather than the beginning:

\function index.of(item) [match<item>] :then[allbefore:include<item>count[]] ~[count[]add[1]]