How can I sort tasks in a random order using Dataview?

I’m currently using Dataview to compile all of my to-do tasks into one checklist. As I have quite a few tasks, I limit the number of tasks that can be seen in the list just so it doesn’t seem so daunting. However, I would like to know if it is possible to sort this list in a random order? The default order is by order of creation (or the original order of how they appear in a note). I would like to sort the list so that I can essentially “shuffle” my tasks, and see a good mix of newer and older tasks from different areas in my vault. Please let me know if this is something that can be done whether through Dataview or any other means. :slight_smile:

Any help would be appreciated.

2 Likes

I posed this question on Reddit and, with help from u/emehlya, the following set-up using Dataviewjs can randomize tasks:

dv.taskList(dv.pages("#Tasks").file.tasks
    .where(t => !t.completed <= luxon.DateTime.now())
    .sort(() => Math.random() - 0.5) .limit(3))

However, the dataview is refreshed constantly and will change the results every time you leave the page or after a random amount of time has passed. If anyone knows how to stop dataview from refreshing the results, please let me know! (Bonus: tasks that are showing as being completed appear in the results for me too. Please let me know if there is a way to only show tasks that have yet to be completed).

1 Like

I’ve figured out why some tasks that have been completed were showing up in the results, and this updated dataviewjs works if I delete the luxon.DateTime.now:

dv.taskList(dv.pages("#Tasks").file.tasks
.where(t => !t.completed) 
.sort(() => Math.random() - 0.5)
.limit(6));

However, I have not found a solution to stop the randomized list from refreshing when I leave the page.

1 Like

By the nature of dataviews being dynamic, they’ll keep on returning different results when randomised. If you don’t change how you randomise the order, or store the query result.

One idea, could be to shuffle the list related to something which changes, but not too often. One idea could be to randomise related to which day in the week it is, so that every Tuesday you would get the same subset of tasks. Similarily, you could choose to have a given subset of tasks every 10th day.

How to select the tasks could then be something like enumerating the tasks, and if the modulo given your day-number is zero, then display task.

The advantage of this method would be that when refreshing on that particular day, you’ll get the same subset. The disadvantage is that if not tasks are completed, you’ll get the same list after your day offset.

Another way to do this, is to somehow make the dataview query generate a new note, and display that note. But this would require two different entry points, one to view the note, and one to generate the new list.

It could possibly be achieved by having a button on the note page, that would only run the query when you hit the button. This way a refresh would just reload the note, and not run the query again. This would also allow for you to “ditch” the current task list whenever you want to by hitting the button, if you feel inclined to do that.

Regards,
Holroy

2 Likes

That’s such a creative way of solving the problem. So basically you’re suggesting to use a pseudorandom number generator (PRNG) that takes the date as seed, right? But will this give every task on the list the same probability of being selected over, say, the course of a month?

In any case, I have no idea how to implement this in practice though and as I contemplate whether I should embark on the journey of figuring it out, I realize another problem. Not a big one, but substantial enough to look for a different solution. Here it is: in order for the PRNG to produce the same result in the course of the day, the original list of tasks has to remain the same. So if I add a new task, that will change the output.

OK, as I think about it, that could be solved by excluding new tasks from the list, but since tasks don’t have creation dates, that’s yet another thing to figure out how to do.

So, I think the easier solution would probably be to find a way of writing the first result into the note and sticking to that result, rather than updating the query. But I don’t know how to do that either…

The issue with this is still that if you make it “permanent”, and not a query, which could be done using template functionality you’ll end up duplicating the tasks, or having an issue locating the original of the task.

So what you kind of would want is to “freeze” the execution of the dataview query, which is kind of counter-intutive for such a query.

Another approach, is to change how you do your workflow, and adapt some of the stuff from the GTD-workflow (I think it’s that), and that would be to have a scheduling meeting (with yourself) and choose which tasks to focus on.

One way this could look would be that you every now and then, maybe once a week?, take a look at the larger lists of tasks, and select which tasks you’d like to focus on for the following period. For each of these tasks, you go to the task, and add a tag to it, like #next or #focus or whatever. Do this to a suitable number of tasks which trigger your interest or where you can see an initiative to complete the task.

Then in your daily note, or wherever you had this random task query, you choose to only view the tasks with your given task. Then this list will be a lot smaller, and you’ll see the progress a lot easier.

1 Like

I implemented a PRNG to order page results from Dataview that don’t re-order every time a page is changed. You might be able to do something similar with tasks?

1 Like

LOL, good point. Didn’t think of that. Haha, I really like figuring this kind of stuff out and finding all those pitfalls. :smile:

^^^^ This!

Excellent! That should do the job for me. Will try it out asap.

Yes, I think that would be the most natural solution for this (and perhaps other scenarios where you don’t want the query to refresh). Do we know whether dataview or dataviewjs have any setting to adjust the refresh rate for an individual query? I see that there is a (global) Refresh Intervall setting in the Dataview plugin settings, so perhaps there is a way of setting this for an individual query?

I appreciate your thought. In my case (not the OP), this is not about randomizing the tasks that I have to do (I agree with you that a system like GTD is a good way forward) but rather the tasks that I don’t have to do. I have a long list of “tasks” that fall into the category “maybe” and/or “someday”. In order to not completely forget about those, I’d like to add one or two of them to the template for my daily note (or my dashboard, we’ll see). Just as an inspiration if I have some spare time that day. I suppose it will also help me with ticking off some of those tasks that have meanwhile been completed or to delete those that are no longer relevant (not even “sometime”).

1 Like

Speaking of which: It just strikes me: can’t we just “cache” the results in a constant, as in

const dueTasks = dv.pages().file.tasks
  .where(t => !t.completed)
  .where(t => t.due && t.due.day == dv.date('2022-12-07').day);

or will the entire script refresh, including the constant?

Need to try this, but don’t have time now. No need to try this: of course the constant is refreshed as well, otherwise task lists using constants wouldn’t be updating when a new task is added…

Excellent! This is what I managed to come up with for my use case (listing a couple or “someday”-tasks in my daily notes, so this is form my template to be rendered with templater:

TASK
WHERE !completed AND text != "" AND !due AND !start
FLATTEN date(<%tp.date.now("YYYY-MM-DD",0,tp.file.title,"YYYY-MM-DD")%>) as Today
FLATTEN (file.ctime.year + file.ctime.hour + file.ctime.day + 
	     file.ctime.hour + file.ctime.minute + file.ctime.second + 
	     file.size + Today.day + Today.month + Today.year) * 15485863 
	     as Hash
FLATTEN ((Hash * Hash * Hash) % 2038074743) / 2038074743 as Rand
SORT Rand
LIMIT 3

I don’t understand the code entirely, but it seems to work. Are those numbers (15485853 and 2038074743) arbitrary or do they represent something?

What I changed compared to Randomly order rows in a Dataview query

  • Instead of the last modified time, I’m using the created time so as to avoid changes of the list when files are updated.
  • And in order to stop the list from changing every time the note is reloaded, I am using the current date so that the list should stay the same the entire day.

One problem that I haven’t solved yet is to “randomize” the tasks instead of their files. Doing that would be better because currently, a note with multiple tasks will fill up the rendered list with its own tasks. I would prefer to have only one task from each note (or at least to give all tasks an equal chance to get into the selection, even once the first note hs been selected.

1 Like

So I’m in kind of a strange mood, and had an idea, which I went on to implement. The idea was that if you gather all the tasks, and generate a hash based upon the task text, it should be possible to get a random set of tasks. And if you then continue to select a set based on a slow changing variable, you would get the same random set, as long as the set chooser is the same.

One day to get such a set chooser could be to use the day of year modula some number, which would return the same set every number of days.

Actual code

The code is somewhat long, but it does also include some debug stuff like the display of the full task list with hash codes and set numbers, and so on. Hopefully it’s understandable where and what to change to make it actual useful code.

```dataviewjs

// Alternative 1: A proper hash generator
function hashCodeProper(str) {
  return str.split('').reduce((prevHash, currVal) =>
    (((prevHash << 5) + prevHash) + currVal.charCodeAt(0))|0,0)
}

// Alternative 2: Simple addition of the charcode values
function hashCodeSimple(str) {
  return str.split('').reduce((prev, curr) =>
    (curr.charCodeAt(0) + prev) |0,0)
}

// How many random set of tasks to get
const randomSetCount = 7

// Select which set of tasks, depending on the day of the year
const taskSet = dv.date("today").day % randomSetCount

// Gather all task candidates
const tasks = dv.pages('"AnotherFolder" or "ForumStuff/f48094"').file.tasks

// Loop through tasks, generate hash codes
// and pick a random assortment of tasks
let randomTasks = []
for (let task of tasks) {
  // Change in line BELOW HERE if you want to change hash code function
  task.hashCode = hashCodeSimple(task.text + task.path)

  task.modHashCode = Math.abs(task.hashCode %  randomSetCount)
  
  if (task.modHashCode == taskSet)
    randomTasks.push(task)
}

const sortedTasks = tasks.sort(t => t.modHashCode)

// Show all tasks, sorted on the modula hash code
dv.header(2, "All tasks")
dv.table(["Mod " + randomSetCount, "Hash", "Task"], 
  tasks.sort(t => t.modHashCode)
       .map(t => [t.modHashCode, t.hashCode, t.text]))

// Show the random list of tasks
dv.header(2, "Random tasks")
dv.taskList(randomTasks, false)
```

I’ve provided two different hash code generators, if you want to see the other one, change the names of hash code at the indicated spot in the code. You do also of course need to change the selector of tasks to something appropriate for your settings.

In the code, shown above, I currently set the number of sets to 7, which would indicate that the set of random tasks repeated every 7 days. This number should be changed accordingly to how many of tasks you’ve got, and how many random task you would want in each set. Given a larger set of tasks, maybe something like 23 would be nice, as it then would take 23 days before getting the same task set, and that would be on a different weekday.

Sadly, depending on the hash code generation, the distribution of tasks could be skewed so that you get more or less task in each set. This would however be reduced as the task number grows.

This approach would preserve the randomness with regards to adding tasks, as the hash code (and thusly selection of task) is a constant as long as the task text doesn’t change.

Sorry, if this way of dealing with getting a random task is way above your comfort level, or outside your expected solution, but I do believe it addresses some of the remaining issues, namely:

  • Using the hash based upon the task text gives a predictability on the randomness
  • Using the day of year (modula some number) would keep the randomness to only change every day

Finally, the code also lends itself to variations as doing variations in the loop could easily create various other options. Like you could drop the entire hash code generation, and just select tasks based on some other qualities you see fit.

1 Like

They’re not arbitrary; they’re large prime numbers. My understanding is that they improve the pseudorandomness of the resulting set, and help make sure the hashes don’t “bunch up” too much around certain values. You can read more about this particular algorithm and implementation on Wikipedia: Pseudorandom number generator - Wikipedia

2 Likes