28

I'm trying to figure out how to transfer the concept of a priority queue to the functional world. Searches have turned up some implementations that use Append and other expense list copying techniques. I'm guessing there is a better way.

An example of what I am trying to solve is consider the products of all pairs of N digit numbers in descending value order. For small N I can do something like...

Reverse[
    Cases[
        SortBy[
            Flatten[
                Table[{i, j, i*j}, {i, 1, 9}, {j, 1, 9}],
            1], 
        Last[#] &], 
    {i_, j_, k_} /; i <= j]
]

Alternative solutions to the problem in particular are welcomed, but I am really looking for a generic answer of how to apply the priority queue concept to the functional world.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
Andrew White
  • 485
  • 3
  • 7
  • I'll admit I'm not really familiar with priority queues. What operations do you wish to perform on this data structure? – Mr.Wizard Sep 09 '13 at 12:34
  • 1
    @Mr.Wizard: simply enqueue (put some value) and dequeue (take the smallest value). Namely it is used to always get the smallest (or largest) value in O(1) time and insertions usually take O(lg(N)) time. Traditional implementations use a heaps to achieve this. – Andrew White Sep 09 '13 at 12:42
  • 2
    I have found an old implementation by Roman E. Maeder. The code can probably be made faster in current versions of Mathematica but the underlying algorithm is likely well thought out. – Mr.Wizard Sep 09 '13 at 13:04
  • As a small suggestion, you shall use FactorInteger to generate factors and refer http://mathematica.stackexchange.com/questions/30683/preserve-information-in-flattening-a-nested-list/30684#30684 to see how you can get your desired results. How you will use it for priority queue, I don't know but in case its important for you. – Pankaj Sejwal Sep 09 '13 at 13:11
  • See here or here for implementations. The latter is more fun, I think. – Daniel Lichtblau Sep 09 '13 at 15:29
  • 1
    By the way, for the problem at hand, one could simply do a Sort on the list of values. Point being, if you are going to work on the set all at once, a queue will likely slow you as compared to a sorting (even though that sorting might be implemented via priority queue-- it will be at a lower level using more optimized code). – Daniel Lichtblau Sep 09 '13 at 15:31
  • @DanielLichtblau if N is as small as 6 you have to sort on the order 10^12 numbers to get the whole list. Sorting for each step is also very expensive. – Andrew White Sep 09 '13 at 16:09
  • If you have to do the sorting iteratively then yes, that's a bad way to go (which is pretty much what I stated). If it is a one-time thing, and you require the entire list as opposed to, say, the top "few" elements, then sorting is about as efficient as anything else you can do. Also, in a case like your example, if you have enough memory you can write an O(n) sort where n is the full range of values (including gaps). – Daniel Lichtblau Sep 09 '13 at 16:27
  • @DanielLichtblau; I do not have a 1TB+ of RAM to dedicate to a counting sort... – Andrew White Sep 09 '13 at 19:11
  • [I realized that. Well, strongly suspected, at any rate.] But...what will a queue do for you? Is this a situation where you are NOT going to generate all such products, or generate them but only keep the k largest? That is to say, how does it help you to avoid the memory issue? Even without a counting sort, you are still looking at an O(10^12) memory hit, if you are keeping all products you generate. – Daniel Lichtblau Sep 09 '13 at 19:48
  • @DanielLichtblau because I'll find the distinct answer I am looking for far before I hit the end of my list and the recursion will terminate. The size of the queue will never be asymptotically large either. It's probably not clear from my question but I am looking for the largest n*m pair with a certain property. – Andrew White Sep 09 '13 at 19:58
  • @Andrew your problem might make for an interesting second question; there may be a different way to approach it that leverages Mathematica's strengths. – Mr.Wizard Sep 09 '13 at 22:44

3 Answers3

31

Actually, Mathematica has this stuff built in. I couldn't find this information anywhere, so posting it here for general reference. You can use it like this:

Needs["Parallel`Queue`Priority`"]
Unprotect@Priority; Priority[i_Integer] := Abs[i]
q = priorityQueue[];
EnQueue[q, 10]; EnQueue[q, 7]; EnQueue[q, -20];
Size[q] == 3;
Top[q] == -20;
Normal[q] == {-20, 10, 7}
DeQueue[q] == -20;

There is also a simple FIFO queue in

Parallel`Queue`FIFO`FIFOQueue[]

and stack in

Parallel`Queue`LIFO`LIFOQueue[]
panda-34
  • 1,268
  • 7
  • 8
  • 1
    How did you find this? – Andrew White Sep 24 '13 at 17:43
  • 1
    @AndrewWhite, was browsing through mathematica files and found a suspiciously looking Queue folder in AddOns\Applications\Parallel (and I just needed a FIFO queue for my task), and inside was the whole works: FIFO, LIFO, Priority, even a Lisp queue which I have no idea what is for. – panda-34 Sep 24 '13 at 18:06
  • 1
    @panda-34, Is It a indexed priority queue? How should I use the priorityqueue beyond the simple example. Suppose I want to turn this list {{0.1, {a}}, {0.6, {b}}, {0.5, {c}}, {2.3, {d}}} into a priorityqueue. The smaller first value has high priority, for example {0.1, {1}} has priority over {0.6, {b}}. – novice Dec 20 '13 at 08:39
  • 2
    @novice, use global Priority[element] function as in my example. In you case it may be like: Priority[i_List] := -i[[1]]; – panda-34 Dec 28 '13 at 09:57
  • @panda-34, can I somehow increase the value of an element in the queue? For example I have a min-first queue, just like in novice's case: {{-5, 3}, {0.5, 8}, {1, 3}, {5, 5}} and I want to decrease the 3rd elements value to {0, 3} and get {{-5, 3}, {0, 3}, {0.5, 8}, {5, 5}} One should be able to do this very fast only by changing parents and children in the tree. –  May 17 '16 at 08:04
  • @BalázsNovák, you delete it and insert it, that's how you change parents and children (if it's even a tree) – panda-34 May 17 '16 at 11:38
  • http://library.wolfram.com/infocenter/Conferences/5363/Queues.pdf – Alan Nov 01 '18 at 19:54
21

This is going to be transcript of Roman E. Maeder's priority queue code with any updates I can find to make to take advantage of functions added since he wrote it.

I believe I am within right to copy it here for noncommercial purposes.

Refactor v0.2 -- any bugs are almost certainly my own.

BeginPackage["PriorityQueue`"]

MakeQueue::usage = "MakeQueue[pred] creates an empty priority queue with
    the given ording predicate. The default predicate is Greater."
CopyQueue::usage = "CopyQueue[q] makes a copy of the priority queue q."
DeleteQueue::usage = "DeleteQueue[q] frees the storage used for q."
EmptyQueue::usage = "EmptyQueue[q] is True if the priority queue q is empty."
EnQueue::usage = "EnQueue[a, item] inserts item into the priority queue q."
TopQueue::usage = "TopQueue[q] returns the largest item in the priority queue q."
DeQueue::usage = "DeQueue[q] removes the largest item from the priority queue q.
    It returns the item removed."
PriorityQueue::usage = "PriorityQueue[...] is the print form of priority queues."

Begin["`Private`"]

SetAttributes[queue, HoldAll]
SetAttributes[array, HoldAllComplete]

makeArray[n_] := array @@ ConstantArray[Null, n]

MakeQueue[pred_:Greater] :=
  Module[{ar,n=0},
    ar = makeArray[2];
    queue[ar, n, pred]
  ]

CopyQueue[queue[a0_,n0_,pred_]] :=
  Module[{ar=a0,n=n0},
    queue[ar, n, pred]
  ]

EnQueue[q:queue[ar_,n_,pred_], val_] :=
  Module[{i,j},
    If[ n == Length[ar], (* extend (double size) *)
        ar = Join[ar, makeArray @ Length @ ar] ];
    n++;
    ar[[n]] = val; i = n;
    While[ True, (* restore heap *)
      j = Quotient[i, 2];
      If[ j < 1 || pred[ar[[j]], ar[[i]]], Break[] ];
      ar[[{i,j}]] = {ar[[j]], ar[[i]]};
      i = j;
    ];
    q
  ]

EmptyQueue[queue[ar_,n_,pred_]] := n == 0

TopQueue[queue[ar_,n_,pred_]] := ar[[1]]

DeQueue[queue[ar_,n_,pred_]] := 
  Module[{i,j,res=ar[[1]]},
    ar[[1]] = ar[[n]]; ar[[n]] = Null; n--;
    j = 1;
    While[ j <= Quotient[n, 2], (* restore heap *)
      i = 2j;
      If[ i < n && pred[ar[[i+1]], ar[[i]]], i++ ];
      If[ pred[ar[[i]], ar[[j]]],
          ar[[{i,j}]] = {ar[[j]], ar[[i]]}; ];
      j = i
    ];
    res
  ]

DeleteQueue[queue[ar_,n_,pred_]] := (ClearAll[ar,n];)

queue/:Normal[q0_queue] :=
  Module[{q=CopyQueue[q0]},
    Reap[While[!EmptyQueue[q], Sow @ DeQueue[q]]; DeleteQueue[q];][[2,1]]
  ]

Format[q_queue/;EmptyQueue[q]] := PriorityQueue[]
Format[q_queue] := PriorityQueue[TopQueue[q], "\[TripleDot]"]

End[]

EndPackage[]
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • This seems to fail when adding a second value; q = MakeQueue[]; EnQueue[q, 5]; EnQueue[q, 10]; The second EnQueue fails with "Part::partd: "Part specification PriorityQueuePrivatex[[{1,2}]] is longer than depth of object" and "Set::noval: Symbol PriorityQueuePrivatex in part assignment does not have an immediate value." – Andrew White Sep 09 '13 at 15:16
  • @Andrew Thanks! There's the first bug I introduced I guess. Disconcertingly I thought I tested this before posting. – Mr.Wizard Sep 09 '13 at 15:25
  • @Andrew Please try it now. – Mr.Wizard Sep 09 '13 at 15:32
  • Version 0.2 seems to be working. Not the speedest thing in the world though. I've +1ed and I'll leave this open for another day to encourage alternative solutions. Thanks for the insight. – Andrew White Sep 09 '13 at 22:36
  • @Andrew You in no way need to Accept this answer. There may very well be a completely different and superior approach that is empowered by functionality added since version 3 (for which I believe this was written). For example with the new LibraryLink it may be possible to do this externally and still have reasonable communication overhead. – Mr.Wizard Sep 09 '13 at 22:41
9

As of Mathematica 12.1, you can use CreateDataStructure to, well, create data structures, and priority queues are one of them.

SeedRandom[1337];
stuff = RandomInteger[100, 10]
(* {58, 91, 36, 72, 63, 16, 60, 13, 44, 18} *)

pq = CreateDataStructure["PriorityQueue"]
(* DataStructure["PriorityQueue", {"Data" -> {}}] *)

Scan[pq["Push", #]&, stuff];
(* This neat trick comes right from the doc page! *)
Table[pq["Pop"], {pq["Length"]}]
(* {91, 72, 63, 60, 58, 44, 36, 18, 16, 13} *)
Pillsy
  • 18,498
  • 2
  • 46
  • 92
  • How is the performance of this? – Mr.Wizard Mar 18 '20 at 23:29
  • 2
    It filled and emptied 10000 elements in just over 50 milliseconds on my computer. ReverseSort took 1 millisecond to get the same result, FWIW, but that seems like a dubious comparison. – Pillsy Mar 18 '20 at 23:33