// 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 class PriorityQueue { public: static const intptr_t kMinimumSize = 16; struct Entry { P priority; V value; }; PriorityQueue() : hashmap_(&MatchFun, kMinimumSize) { min_heap_size_ = kMinimumSize; min_heap_ = reinterpret_cast(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(value); } static uint32_t HashKey(const V& key) { return static_cast(reinterpret_cast(CastKey(key))); } static intptr_t ValueOfMapEntry(SimpleHashMap::Entry* entry) { return reinterpret_cast(entry->value); } static void* CastKey(const V& key) { return reinterpret_cast((const_cast(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( realloc(min_heap_, sizeof(Entry) * new_min_heap_size)); if (new_backing == NULL) 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_