New book-sorting algorithm almost reaches perfection
quantamagazine.org
quantamagazine.org
It was surprising to me too! But reflecting on it more closely, most performance isn't about "faster" in a literal sense of "more instructions run per time", but about carefully choosing how to do less work. The security property here being "history independence" is also in a way stating "we don't need to, and literally cannot, do any work that tracks history".
It's definitely an interesting approach to performance, essentially using cryptography as a contraint to prevent more work. What properties do we need, and what properties can we ignore? The question becomes if we MUST ignore this property cryptographically, how does that affect the process and the related performance?
It certainly feels like it may be a useful perspective, a rigorous approach to performance that may be a path to more improvements in key cases.
An example of that is a linked list with no particular sort order. By not caring about maintaining an order the insertion appends or preprends a node and is O(1).
As soon as you have to maintain any additional invariant, the insertion cost goes up. Either directly or amortized.
To generalize it, if figuring out what information is important is more expensive than assuming none of it is, better to simplify.
But the measurement they're actually using is how many books need to be moved. They're free to use infinite compute time AIUI.
Said another way, stateless approaches are simpler than stateful. There can be an instinct to optimize by using more information, but in this case at least, that was a red herring and adding the constraint improved the results.
Apparently both of them are linked deep inside the article, perhaps Quanta can make it mandatory to list all the reference towards the end of the article, it will be very helpful for the readers.
[1] Nearly Optimal List Labeling:
https://arxiv.org/abs/2405.00807
[2] A sparse table implementation of priority queues:
> This problem was introduced in a 1981 paper
where "1981 paper" links to https://link.springer.com/chapter/10.1007/3-540-10843-2_34
and in the next paragraph:
> Last year, in a study that was presented at the Foundations of Computer Science conference in Chicago, a team of seven researchers
where "a study" links to https://arxiv.org/abs/2405.00807
Note that both of these are in the introductory section (third and fourth paragraphs), before the article goes into details, history and context. If this is what counts as “Apparently both of them are linked deep inside the article”, then it appears there exist very different ideas of what “deep inside” means.
IIRC their real life solution was to leave gaps between elements (eg index 0, 100, 200… instead of 0, 1, 2) and re index when needed. Probably works well enough, what I came up with is (as you say) the idea of fractional indexing, but dealing with decimals is a pain so you can instead represent them as vectors, which you can just represent as a string of numbers that you sort lexicographically.
So an element inserted between elements 1 and 2 gets an index 11 (anything between 11-19 is fine). Between 1 and 11 is 101. Between 11 and 2 is 12 (or anything between 12-19). Note that these indexes are not numbers, they’re string that are compared lexicographically.
I’m sure there’s downsides, eg it takes a lot more memory to sort these indexes (strings are much larger than numbers). It feels a bit too smart to not have unexpected downsides.
To be fair, this sounds like one of those classic problems that someone for sure already figured out in the 50s or 60s, just under a different name and/or context. Hash chaining is a similar idea, but not quite the same.
Reminds me of my days writing BASIC programs.
Those are the same thing. Leaving gaps is fractional indexing. It's just fixed-point rather than floating point.
> an element inserted between elements 1 and 2 gets an index 11 (anything between 11-19 is fine). Between 1 and 11 is 101. Between 11 and 2 is 12 (or anything between 12-19). Note that these indexes are not numbers, they’re string that are compared lexicographically.
This reminds me of the most interesting method of generating random integers in an arbitrary range from random bits: interpret the bitstream as a string representing a real number (in binary) between 0 and 1. If, for example, you want to use bits to generate a number between 0 and 5, you divide the unit interval into sixths, and examine bits until you've determined conclusively that your number lies within one of those intervals:
+---- 0 = 0.000000000... ---+
| 0.000 -> 0 |
| 0.00100 -> 0 |
+-- 1/6 = 0.001010101... --+
| 0.0011 -> 1 |
| 0.0100 -> 1 |
+-- 2/6 = 0.010101010... --+
| 0.011 -> 2 |
+-- 3/6 = 0.100000000... --+
| 0.100 -> 3 |
+-- 4/6 = 0.101010101... --+
| 0.1011 -> 4 |
| 0.1100 -> 4 |
+-- 5/6 = 0.110101010... --+
| 0.11011 -> 5 |
| 0.111 -> 5 |
+---------------------------+Basically it works as long as your label space is large compared to the number of items. The more sophisticated methods are necessary when that isn’t the case. For example, say you have 4 bytes for the label and 1 billion items.
So e.g. to frame this, one approach to a CRDT is to just treat the document as a list of facts, "line 1 is 'foo', line 2 is 'bar'", and each fact has a number of times it has been asserted, and to "merge" you just add together the assertion counts, and then you can detect conflicts when a fact has been asserted more than once or fewer than zero times. So a patch says "change line 2 to 'baz'", this becomes "unassert that line 2 is 'bar', assert that line 2 is 'baz'" and it conflicts with a patch that says "change line 2 to 'quux'" because the fact "line 2 is 'bar'" has an assertion count of -1.
But anyway, in this context you might want to allow inserting lines, and then you have the list-labeling problem, you don't want the patch to unassert lines 4,5,6 just to insert a new line after line 3. So then an obvious thing is to just use a broader conception of numbers, say "line 3.5 is <X>" when you insert, and then we hide the line numbers from the user anyways, they don't need to know that internally the line numbers of the 7 lines go "1, 2, 3, 3.5, 4, 5, 6".
So then you need a relabeling step because you eventually have some line at 3.198246315 and you want to be able to say "yeah, that's actually line 27, let's have some sanity again in this thing."
This also maybe hints at the fun of adding randomization, consider that one person might add line 3.5, then add line 3.75, and then remove line 3.5; meanwhile someone else might add a different line 3.5, add a line 3.25, and then remove their line 3.5, and these patches would both amount to "assert line 3.25 is A, assert line 3.75 is B", and would merge without conflict. This means that in general if two people are messing with the same part of the same document asynchronously, this model is not able to consistently flag a merge failure in that case, but will sometimes instead just randomly order the lines that were added.
We can then just make that a feature rather than a fault: you don't insert at 3.5, which is 3 + (4 - 3) / 2, rather you insert at 3 + (4 — 3) * rand(). And then when two people both try to insert 12 lines between line 3 and 4 independently, when you merge them together, you get 24 lines from both, in their original orders but interleaved randomly, and like that's not the end of the world, it might be legitimately better than just declaring a merge failure and harrumphing at the user.
Aren't the goals of git and CRDTs different. With git you want to get the merged result to be semantically correct. With CRDTs you want to achieve convergence (so no merge conflicts), as far as I know semantically correct convergence (not sure what to correct term is) is not really possible as it is too difficult to encode for CRDTs, though. Isn't that why CRDTs are mostly used for multiplayer interactive applications where these kinds of mismatches are quickly seen?
Kind of a click-baity title.
For arrays in B-tree nodes, which is the main place where I have encountered this problem, I doubt it will be faster than just using `memmove()`, and for truly large arrays, it would be easier to use a B tree.
If that is the case, this joins a number of algorithms that are asymptotically faster than algorithms in use and paradoxically slower than them. Examples of such algorithms include all of the fast matrix multiplication algorithms, which are slower than good implementations of the the textbook O(n^3) algorithm (GEMM).
The very first example on the page has a pretty good quote describing their usefulness:
>>> An example of a galactic algorithm is the fastest known way to multiply two numbers, which is based on a 1729-dimensional Fourier transform. It needs O(n log n) bit operations, but as the constants hidden by the big O notation are large, it is never used in practice. However, it also shows why galactic algorithms may still be useful. The authors state: "we are hopeful that with further refinements, the algorithm might become practical for numbers with merely billions or trillions of digits."
This is true! One of the things I find cool about big-O complexity using polynomial reference classes is that logarithms give you an infinitesimal value. Take that, "infinitesimals don't really exist" people!
The limit as x goes to infinity of (ln x) / (x^k) is zero for all positive k. So if you want to approximate the behavior of a logarithm with a function of the form f(x) = x^k, k must be greater than 0 [because x^0 = o(ln x)], but less than every positive real number. This is the definition of an infinitesimal.
That's why it makes sense to say that x (log x)^3 is equivalent to x^{1.000000...[infinite 0s]...1}. Though in this analogy, it's more like x^{1.000000...3}.
> I don't really understand what you're asking for.
That comes across as rude and condescending; please don't communicate like that. Your English skills seem OK but in case you still need help, they're asking for a "reference" -- that's an online source of some sort probably, where they can "learn about" it -- that means that the reference would contain content helping them to understand the topic better. For example, the helpful content you gave after your rude and condescending initial comment would be an example of such content.
I’d say that prologue was more medicinal than condescending. It’s good to remind people in the age of LLMs and ubiquitous search that “you can just think things.”
I lose track of so many infrequently used objects in my home -- which storage bin in which closet did I put the x-acto blade refills in? -- and one bin will overflow while another is half-empty because I try to keep similar items together. Sometimes I fantasize about tracking every possession in a spreadsheet that points to which bin, so I'd never lose anything and could always use storage space with maximum efficiency. But then I know I'd be lazy and skip updating the spreadsheet when I put something new away... plus it just seems so inhumanly weird, like something a robot would so rather than a person.
Game: https://patorjk.com/games/subpixel-snake/ Code: https://github.com/patorjk/subpixel-snake
Resource for subpixel geometries: https://geometrian.com/resources/subpixelzoo/
insert(X), delete(X), label(X)
Where label extracts the label of element X (which had previously been inserted but not deleted). The label is a number from 0 to n-1, where n is the number of elements currently stored.
Can you explain to them what you want them to now do?
I read the paper and I would like to say, it's confusing at best. But I'm hoping you can help out.
Also, don’t discredit the intelligence of retirees, teachers and librarians in the same post. It’s bad for karma and makes you sound like a douche. I’m sure they can figure it out.
So you answered and I'll ask, "what do they do, it does not seem simple" except for the Database people filling ....
Thanks (former teacher)