2021-07-19 02:55:01 +02:00
|
|
|
# File: PriorityQueue.pm
|
|
|
|
#
|
|
|
|
# Purpose: Bare-bones lightweight implementation of a priority queue.
|
|
|
|
|
|
|
|
# SPDX-FileCopyrightText: 2021 Pragmatic Software <pragma78@gmail.com>
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
|
2021-07-24 04:22:25 +02:00
|
|
|
package PBot::Core::Utils::PriorityQueue;
|
2021-07-19 02:55:01 +02:00
|
|
|
|
|
|
|
use PBot::Imports;
|
|
|
|
|
2021-07-21 06:38:07 +02:00
|
|
|
sub new {
|
|
|
|
my ($class, %args) = @_;
|
2021-07-19 02:55:01 +02:00
|
|
|
|
2021-07-21 06:38:07 +02:00
|
|
|
return bless {
|
|
|
|
# list of entries; each entry is expected to have a `priority` and an `id` field
|
|
|
|
queue => [],
|
|
|
|
}, $class;
|
2021-07-19 02:55:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub queue {
|
|
|
|
my ($self) = @_;
|
|
|
|
return $self->{queue};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub entries {
|
|
|
|
my ($self) = @_;
|
|
|
|
return @{$self->{queue}};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub count {
|
|
|
|
my ($self) = @_;
|
|
|
|
return scalar @{$self->{queue}};
|
|
|
|
}
|
|
|
|
|
2021-07-19 03:14:28 +02:00
|
|
|
sub get {
|
2021-07-19 02:55:01 +02:00
|
|
|
my ($self, $position) = @_;
|
|
|
|
return $self->{queue}->[$position];
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_priority {
|
|
|
|
my ($self, $position) = @_;
|
|
|
|
return $self->{queue}->[$position]->{priority};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub remove {
|
|
|
|
my ($self, $position) = @_;
|
|
|
|
return splice @{$self->{queue}}, $position, 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
# quickly and efficiently find the best position in the entry
|
|
|
|
# queue array for a given priority value
|
|
|
|
sub find_enqueue_position {
|
|
|
|
my ($self, $priority) = @_;
|
|
|
|
|
2021-07-19 03:14:28 +02:00
|
|
|
$priority //= 0;
|
|
|
|
|
2021-07-23 19:13:19 +02:00
|
|
|
# shorter alias
|
|
|
|
my $queue = $self->{queue};
|
|
|
|
|
2021-07-19 02:55:01 +02:00
|
|
|
# no entries in queue yet, early-return first position
|
2021-07-23 19:13:19 +02:00
|
|
|
return 0 if not @$queue;
|
2021-07-19 02:55:01 +02:00
|
|
|
|
|
|
|
# early-return first position if entry's priority is less
|
|
|
|
# than first position's
|
2021-07-23 19:13:19 +02:00
|
|
|
if ($priority < $queue->[0]->{priority}) {
|
2021-07-19 02:55:01 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
# early-return last position if entry's priority is greater
|
2021-07-23 19:13:19 +02:00
|
|
|
if ($priority > $queue->[@$queue - 1]->{priority}) {
|
|
|
|
return scalar @$queue;
|
2021-07-19 02:55:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# binary search to find enqueue position
|
|
|
|
|
|
|
|
my $lo = 0;
|
2021-07-23 19:13:19 +02:00
|
|
|
my $hi = scalar @$queue - 1;
|
2021-07-19 02:55:01 +02:00
|
|
|
|
|
|
|
while ($lo <= $hi) {
|
|
|
|
my $mid = int (($hi + $lo) / 2);
|
|
|
|
|
2021-07-23 19:13:19 +02:00
|
|
|
if ($priority < $queue->[$mid]->{priority}) {
|
2021-07-19 02:55:01 +02:00
|
|
|
$hi = $mid - 1;
|
2021-07-23 19:13:19 +02:00
|
|
|
} elsif ($priority > $queue->[$mid]->{priority}) {
|
2021-07-19 02:55:01 +02:00
|
|
|
$lo = $mid + 1;
|
|
|
|
} else {
|
|
|
|
# found a slot with the same priority. we "slide" down the array
|
|
|
|
# to append this entry to the end of this region of same-priorities
|
|
|
|
# and then return the final slot
|
2021-07-23 19:13:19 +02:00
|
|
|
while ($mid < @$queue and $queue->[$mid]->{priority} == $priority) {
|
2021-07-19 02:55:01 +02:00
|
|
|
$mid++;
|
|
|
|
}
|
|
|
|
return $mid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $lo;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub add {
|
|
|
|
my ($self, $entry) = @_;
|
|
|
|
my $position = $self->find_enqueue_position($entry->{priority});
|
|
|
|
splice @{$self->{queue}}, $position, 0, $entry;
|
|
|
|
return $position;
|
|
|
|
}
|
|
|
|
|
2021-07-19 05:49:11 +02:00
|
|
|
sub update_priority {
|
|
|
|
my ($self, $id, $priority) = @_;
|
|
|
|
my @entries = grep { $_->{id} eq $id } @{$self->{queue}};
|
|
|
|
map { $_->{priority} = $priority } @entries;
|
|
|
|
$self->{queue} = [ sort { $a->{priority} <=> $b->{priority} } @{$self->{queue}} ];
|
|
|
|
}
|
|
|
|
|
2021-07-19 02:55:01 +02:00
|
|
|
1;
|