Skip to content

Commit

Permalink
feat: hash() - Generate a unique number based on input (#2272)
Browse files Browse the repository at this point in the history
Based on a unique combination of a seed, text and a variant number, it generates a fixed number which can be used for sorting in a random order. Typical seed could be the date of today, the text a file name, and the variant could be the line number of a list/task item.
  • Loading branch information
holroy authored Mar 20, 2024
1 parent 242a7d0 commit 4f70e92
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 3 deletions.
19 changes: 19 additions & 0 deletions docs/docs/reference/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,25 @@ choice(false, "yes", "no") = "no"
choice(x > 4, y, z) = y if x > 4, else z
```

### `hash(seed, [text], [variant])`

Generate a hash based on the `seed`, and the optional extra `text` or a variant `number`. The function
generates a fixed number based on the combination of these parameters, which can be used to randomise
the sort order of files or lists/tasks. If you choose a `seed` based on a date, i.e. "2024-03-17",
or another timestamp, i.e. "2024-03-17 19:13", you can make the "randomness" be fixed
related to that timestamp. `variant` is a number, which in some cases is needed to make the combination of
`text` and `variant` become unique.

```js
hash(dateformat(date(today), "YYYY-MM-DD"), file.name) = ... A unique value for a given date in time
hash(dateformat(date(today), "YYYY-MM-DD"), file.name, position.start.line) = ... A unique "random" value in a TASK query
```

This function can be used in a `SORT` statement to randomise the order. If you're using a `TASK` query,
since the file name could be the same for multiple tasks, you can add some number like the starting line
number (as shown above) to make it a unique combination. If using something like `FLATTEN file.lists as item`,
the similar addition would be to do `item.position.start.line` as the last parameter.

### `striptime(date)`

Strip the time component of a date, leaving only the year, month, and day. Good for date comparisons if you don't care
Expand Down
14 changes: 14 additions & 0 deletions src/expression/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Fields } from "./field";
import { EXPRESSION } from "./parse";
import { escapeRegex } from "util/normalize";
import { DataArray } from "api/data-array";
import { cyrb53 } from "util/hash";

/**
* A function implementation which takes in a function context and a variable number of arguments. Throws an error if an
Expand Down Expand Up @@ -704,6 +705,18 @@ export namespace DefaultFunctions {
.vectorize(3, [0])
.build();

export const hash = new FunctionBuilder("hash")
.add2("string", "number", (seed, variant) => {
return cyrb53(seed, variant);
})
.add2("string", "string", (seed, text) => {
return cyrb53(seed + text);
})
.add3("string", "string", "number", (seed, text, variant) => {
return cyrb53(seed + text, variant);
})
.build();

export const reduce = new FunctionBuilder("reduce")
.add2("array", "string", (lis, op, context) => {
if (lis.length == 0) return null;
Expand Down Expand Up @@ -924,5 +937,6 @@ export const DEFAULT_FUNCTIONS: Record<string, FunctionImpl> = {
default: DefaultFunctions.fdefault,
ldefault: DefaultFunctions.ldefault,
choice: DefaultFunctions.choice,
hash: DefaultFunctions.hash,
meta: DefaultFunctions.meta,
};
7 changes: 7 additions & 0 deletions src/test/function/functions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ test("Evaluate choose()", () => {
expect(parseEval("choice(false, 1, 2)")).toEqual(2);
});

test("Evaulate hash()", () => {
expect(DefaultFunctions.hash(simpleContext(), "2024-03-17", "")).toEqual(3259376374957153);
expect(DefaultFunctions.hash(simpleContext(), "2024-03-17", 2)).toEqual(271608741894590);
expect(DefaultFunctions.hash(simpleContext(), "2024-03-17", "Home")).toEqual(3041844187830523);
expect(DefaultFunctions.hash(simpleContext(), "2024-03-17", "note a1", 21)).toEqual(1143088188331616);
});

// <-- extract() -->

test("Evaluate 1 field extract()", () => {
Expand Down
9 changes: 8 additions & 1 deletion src/ui/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ export async function renderValue(
}

if (Values.isNull(field)) {
await renderCompactMarkdown(app, settings.renderNullAs, container, originFile, component, isInlineFieldLivePreview);
await renderCompactMarkdown(
app,
settings.renderNullAs,
container,
originFile,
component,
isInlineFieldLivePreview
);
} else if (Values.isDate(field)) {
container.appendText(renderMinimalDate(field, settings, currentLocale()));
} else if (Values.isDuration(field)) {
Expand Down
22 changes: 22 additions & 0 deletions src/util/hash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated.
// A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance.
// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480
// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js

export function cyrb53(str: string, seed: number = 0): number {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
// For a full 64-bit value we could return
// [h2>>>0, h1>>>0]
return 4294967296 * (2097151 & h2) + (h1 >>> 0); // ;
}
4 changes: 2 additions & 2 deletions test-vault/.obsidian/community-plugins.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[
"dataview",
"hot-reload"
"hot-reload",
"dataview"
]

0 comments on commit 4f70e92

Please sign in to comment.