mirror of
https://github.com/dart-lang/sdk
synced 2024-09-04 16:03:44 +00:00
![Ryan Macnak](/assets/img/avatar_default.png)
TEST=build Change-Id: I61ebb04f414d36813a99b424fb1bcdb440f68ad5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/293801 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
270 lines
7.7 KiB
C++
270 lines
7.7 KiB
C++
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#ifndef RUNTIME_PLATFORM_PRIORITY_QUEUE_H_
|
|
#define RUNTIME_PLATFORM_PRIORITY_QUEUE_H_
|
|
|
|
#include "platform/assert.h"
|
|
#include "platform/globals.h"
|
|
#include "platform/hashmap.h"
|
|
#include "platform/utils.h"
|
|
|
|
namespace dart {
|
|
|
|
// A min-priority queue with deletion support.
|
|
//
|
|
// The [PriorityQueue] allows insertion of entries with a priority [P] and a
|
|
// value [V]. The minimum element can be queried in O(1) time.
|
|
// Insertion/Deletion operations have O(N) time.
|
|
//
|
|
// In addition to the normal insert/minimum/remove-minimum operations this
|
|
// priority queue allows deletion-by-value. We have therefore an invariant
|
|
// is that the value must be unique amongst all entries.
|
|
template <typename P, typename V>
|
|
class PriorityQueue {
|
|
public:
|
|
static constexpr intptr_t kMinimumSize = 16;
|
|
|
|
struct Entry {
|
|
P priority;
|
|
V value;
|
|
};
|
|
|
|
PriorityQueue() : hashmap_(&MatchFun, kMinimumSize) {
|
|
min_heap_size_ = kMinimumSize;
|
|
min_heap_ =
|
|
reinterpret_cast<Entry*>(malloc(sizeof(Entry) * min_heap_size_));
|
|
if (min_heap_ == nullptr) FATAL("Cannot allocate memory.");
|
|
size_ = 0;
|
|
}
|
|
|
|
~PriorityQueue() { free(min_heap_); }
|
|
|
|
// Whether the queue is empty.
|
|
bool IsEmpty() const { return size_ == 0; }
|
|
|
|
// Inserts a new entry with [priority] and [value], requires there to be no
|
|
// existing entry with given [value].
|
|
void Insert(const P& priority, const V& value) {
|
|
ASSERT(!ContainsValue(value));
|
|
|
|
if (size_ == min_heap_size_) {
|
|
Resize(min_heap_size_ << 1);
|
|
}
|
|
|
|
Set(size_, {priority, value});
|
|
BubbleUp(size_);
|
|
|
|
size_++;
|
|
}
|
|
|
|
// Returns a reference to the minimum entry.
|
|
//
|
|
// The caller can access it's priority and value in read-only mode only.
|
|
const Entry& Minimum() const {
|
|
ASSERT(!IsEmpty());
|
|
return min_heap_[0];
|
|
}
|
|
|
|
// Removes the minimum entry.
|
|
void RemoveMinimum() {
|
|
ASSERT(!IsEmpty());
|
|
RemoveAt(0);
|
|
}
|
|
|
|
// Removes an existing entry with the given [value].
|
|
//
|
|
// Returns true if such an entry was removed.
|
|
bool RemoveByValue(const V& value) {
|
|
auto entry = FindMapEntry(value);
|
|
if (entry != nullptr) {
|
|
const intptr_t offset = ValueOfMapEntry(entry);
|
|
RemoveAt(offset);
|
|
|
|
ASSERT(hashmap_.size() == size_);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Whether the priority queue contains an entry with the given [value].
|
|
bool ContainsValue(const V& value) { return FindMapEntry(value) != nullptr; }
|
|
|
|
// Changes the priority of an existing entry with given [value] or adds a
|
|
// new entry.
|
|
bool InsertOrChangePriority(const P& priority, const V& value) {
|
|
auto map_entry = FindMapEntry(value);
|
|
if (map_entry == nullptr) {
|
|
Insert(priority, value);
|
|
return true;
|
|
}
|
|
|
|
const intptr_t offset = ValueOfMapEntry(map_entry);
|
|
ASSERT(offset < size_);
|
|
|
|
Entry& entry = min_heap_[offset];
|
|
entry.priority = priority;
|
|
if (offset == 0) {
|
|
BubbleDown(offset);
|
|
} else {
|
|
intptr_t parent = (offset - 1) / 2;
|
|
intptr_t diff = entry.priority - min_heap_[parent].priority;
|
|
if (diff < 0) {
|
|
BubbleUp(offset);
|
|
} else if (diff > 0) {
|
|
BubbleDown(offset);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef TESTING
|
|
intptr_t min_heap_size() { return min_heap_size_; }
|
|
#endif // TESTING
|
|
|
|
private:
|
|
// Utility functions dealing with the SimpleHashMap interface.
|
|
static bool MatchFun(void* key1, void* key2) { return key1 == key2; }
|
|
|
|
SimpleHashMap::Entry* FindMapEntry(const V& key, bool insert = false) {
|
|
return hashmap_.Lookup(CastKey(key), HashKey(key), insert);
|
|
}
|
|
void RemoveMapEntry(const V& key) {
|
|
ASSERT(FindMapEntry(key) != nullptr);
|
|
hashmap_.Remove(CastKey(key), HashKey(key));
|
|
}
|
|
void SetMapEntry(const V& key, intptr_t value) {
|
|
FindMapEntry(key, /*insert=*/true)->value = reinterpret_cast<void*>(value);
|
|
}
|
|
static uint32_t HashKey(const V& key) {
|
|
return static_cast<uint32_t>(reinterpret_cast<intptr_t>(CastKey(key)));
|
|
}
|
|
static intptr_t ValueOfMapEntry(SimpleHashMap::Entry* entry) {
|
|
return reinterpret_cast<intptr_t>(entry->value);
|
|
}
|
|
static void* CastKey(const V& key) {
|
|
return reinterpret_cast<void*>((const_cast<V&>(key)));
|
|
}
|
|
|
|
void RemoveAt(intptr_t offset) {
|
|
ASSERT(offset < size_);
|
|
|
|
size_--;
|
|
|
|
if (offset == size_) {
|
|
RemoveMapEntry(min_heap_[offset].value);
|
|
} else {
|
|
Replace(offset, size_);
|
|
BubbleDown(offset);
|
|
}
|
|
|
|
if (size_ <= (min_heap_size_ >> 2) &&
|
|
kMinimumSize <= (min_heap_size_ >> 1)) {
|
|
Resize(min_heap_size_ >> 1);
|
|
}
|
|
}
|
|
|
|
void BubbleUp(intptr_t offset) {
|
|
while (true) {
|
|
if (offset == 0) return;
|
|
|
|
intptr_t parent = (offset - 1) / 2;
|
|
if (min_heap_[parent].priority > min_heap_[offset].priority) {
|
|
Swap(parent, offset);
|
|
}
|
|
offset = parent;
|
|
}
|
|
}
|
|
|
|
void BubbleDown(intptr_t offset) {
|
|
while (true) {
|
|
intptr_t left_child_index = 2 * offset + 1;
|
|
bool has_left_child = left_child_index < size_;
|
|
|
|
if (!has_left_child) return;
|
|
|
|
intptr_t smallest_index = offset;
|
|
|
|
if (min_heap_[left_child_index].priority < min_heap_[offset].priority) {
|
|
smallest_index = left_child_index;
|
|
}
|
|
|
|
intptr_t right_child_index = left_child_index + 1;
|
|
bool has_right_child = right_child_index < size_;
|
|
if (has_right_child) {
|
|
if (min_heap_[right_child_index].priority <
|
|
min_heap_[smallest_index].priority) {
|
|
smallest_index = right_child_index;
|
|
}
|
|
}
|
|
|
|
if (offset == smallest_index) {
|
|
return;
|
|
}
|
|
|
|
Swap(offset, smallest_index);
|
|
offset = smallest_index;
|
|
}
|
|
}
|
|
|
|
void Set(intptr_t offset1, const Entry& entry) {
|
|
min_heap_[offset1] = entry;
|
|
SetMapEntry(entry.value, offset1);
|
|
}
|
|
|
|
void Swap(intptr_t offset1, intptr_t offset2) {
|
|
Entry temp = min_heap_[offset1];
|
|
min_heap_[offset1] = min_heap_[offset2];
|
|
min_heap_[offset2] = temp;
|
|
|
|
SetMapEntry(min_heap_[offset1].value, offset1);
|
|
SetMapEntry(min_heap_[offset2].value, offset2);
|
|
}
|
|
|
|
void Replace(intptr_t index, intptr_t with_other) {
|
|
RemoveMapEntry(min_heap_[index].value);
|
|
|
|
const Entry& entry = min_heap_[with_other];
|
|
SetMapEntry(entry.value, index);
|
|
min_heap_[index] = entry;
|
|
}
|
|
|
|
void Resize(intptr_t new_min_heap_size) {
|
|
ASSERT(size_ < new_min_heap_size);
|
|
ASSERT(new_min_heap_size != min_heap_size_);
|
|
|
|
Entry* new_backing = reinterpret_cast<Entry*>(
|
|
realloc(min_heap_, sizeof(Entry) * new_min_heap_size));
|
|
|
|
if (new_backing == nullptr) FATAL("Cannot allocate memory.");
|
|
|
|
min_heap_ = new_backing;
|
|
min_heap_size_ = new_min_heap_size;
|
|
}
|
|
|
|
// The array is representing a tree structure with guaranteed log(n) height.
|
|
// It has the property that the value of node N is always equal or smaller
|
|
// than the value of N's children. Furthermore it is a "dense" tree in the
|
|
// sense that all rows/layers of the tree are fully occupied except the last
|
|
// one. The way to represent such "dense" trees is via an array that allows
|
|
// finding left/right children by <2*index+1><2*index+2> and the parent by
|
|
// <(index-1)/2>.
|
|
//
|
|
// Insertion operations can be performed by adding one more entry at the end
|
|
// (bottom right) and bubbling it up until the tree invariant is satisfied
|
|
// again.
|
|
//
|
|
// Deletion operations can be performed by replacing the minimum element
|
|
// (first entry) by the last entry (bottom right) and bubbling it down until
|
|
// the tree invariant is satisfied again.
|
|
Entry* min_heap_;
|
|
intptr_t min_heap_size_;
|
|
intptr_t size_;
|
|
SimpleHashMap hashmap_;
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_PLATFORM_PRIORITY_QUEUE_H_
|