Files
swift-mirror/include/swift/Basic/ListMerger.h
John McCall c5c78c55f2 Add an insertAtFront method to the list merger.
The existing insert method preserves insertion order, but the
atomic queue use case actually wants to *reverse* the natural
insertion order.
2021-11-15 17:28:51 -05:00

384 lines
13 KiB
C++

//===--- ListMerger.h - Merging sorted linked lists -------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines a class that helps with maintaining and merging a
// sorted linked list.
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_BASIC_LISTMERGER_H
#define SWIFT_BASIC_LISTMERGER_H
#include <assert.h>
namespace swift {
/// A class for building and merging sorted linked lists.
///
/// The `Node` type parameter represents a reference to a list node.
/// Conceptually, a `Node` value is either null or a reference to an
/// object with an abstract sort value and a `next` reference
/// (another `Node` value).
///
/// A null reference can be created by explicitly default-constructing
/// the `Node` type, e.g. with `Node()`. Converting a `Node` value
/// contextually to `bool` tests whether the node is a null reference.
/// `Node` values can be compared with the `==` and `!=` operators,
/// and equality with `Node()` is equivalent to a `bool` conversion.
/// These conditions are designed to allow pointer types to be used
/// directly, but they also permit other types. `ListMerger` is not
/// currently written to support smart pointer types efficiently,
/// however.
///
/// The sort value and `next` reference are not accessed directly;
/// instead, they are accessed with `static` functions on the
/// `NodeTraits` type parameter:
///
/// ```
/// /// Return the current value of the next reference.
/// static Node getNext(Node n);
///
/// /// Set the current value of the next reference.
/// static void setNext(Node n, Node next);
///
/// /// Compare the sort value of this node with that of another
/// /// node, returning negative (<), zero (==), or positive (>).
/// /// A node must compare equal to itself. A sorted list obeys
/// /// the condition that each node in the list compares <= the next.
/// static int compare(Node lhs, Node rhs);
/// ```
///
/// The merger holds a current list of nodes. The sort value and
/// next references of nodes must not be accessed after being added
/// to the merger and before being released except by the merger.
template <class Node, class NodeTraits>
class ListMerger {
Node root;
Node lastInsertionPoint = Node();
bool lastInsertionPointIsKnownLastOfEquals = false;
public:
/// Construct a merger with the given sorted list as its current list.
ListMerger(Node initialList = Node())
: root(initialList) {}
/// Add a single node to this merger's current list.
///
/// The next reference of the node will be overwritten and does not
/// need to be meaningful.
///
/// The relative order of nodes in the current list will not change,
/// and if there are nodes in the current list which compare equal
/// to the new node, it will be inserted after them.
void insert(Node newNode) {
assert(newNode && "inserting a null node");
Node prev = Node();
Node cur = root;
Node stopper = Node();
// If we have a previous insertion point, compare against it.
if (Node lastIP = lastInsertionPoint) {
int comparison = NodeTraits::compare(lastIP, newNode);
// If it compares equal, put the new node immediately after the
// last in the sequence of equals that contains it. This is a
// common fast path when we're adding many nodes that compare equal.
if (comparison == 0) {
lastIP = findLastOfEqualsFromLastIP(lastIP);
NodeTraits::setNext(newNode, NodeTraits::getNext(lastIP));
NodeTraits::setNext(lastIP, newNode);
setLastInsertionPoint(newNode, /*known last of equals*/ true);
return;
// If the new node must follow the last insertion node, we can
// at least start the search there.
} else if (comparison < 0) {
lastIP = findLastOfEqualsFromLastIP(lastIP);
prev = lastIP;
cur = NodeTraits::getNext(lastIP);
// Otherwise, we can at least end the search at the last inserted
// node.
} else {
stopper = lastIP;
}
}
// Invariants:
// root == [ ..., prev, cur, ... ]
// prev <= newRoot
// Scan forward looking for either `end` or a node that strictly
// follows the new node.
while (cur != stopper && NodeTraits::compare(cur, newNode) <= 0) {
prev = cur;
cur = NodeTraits::getNext(cur);
}
NodeTraits::setNext(newNode, cur);
if (prev) {
NodeTraits::setNext(prev, newNode);
} else {
root = newNode;
}
setLastInsertionPoint(newNode, /*known last of equals*/ true);
}
/// Add a single node to this merger's current list.
///
/// The next reference of the node will be overwritten and does not
/// need to be meaningful.
///
/// The relative order of nodes in the current list will not change,
/// and if there are nodes in the current list which compare equal
/// to the new node, it will be inserted *before* them.
///
/// This is useful for the pattern where nodes are naturally encountered
/// in the opposite of their desired order in the final list and
/// need to be reversed. It generally doesn't make any sense to mix
/// this with calls to insert or merge on the same merger.
void insertAtFront(Node newNode) {
assert(newNode && "inserting a null node");
auto insertBetween = [newNode, this](Node prev, Node next) {
if (prev) {
assert(NodeTraits::getNext(prev) == next);
assert(NodeTraits::compare(prev, newNode) < 0);
NodeTraits::setNext(prev, newNode);
} else {
assert(root == next);
root = newNode;
}
assert(!next || NodeTraits::compare(newNode, next) <= 0);
NodeTraits::setNext(newNode, next);
setLastInsertionPoint(prev, /*known last of equals*/ true);
};
Node prev = Node();
Node cur = root;
// If we have a previous insertion point, check for the presumed-common
// case that we're inserting something that should immediately follow it.
if (auto lastIP = lastInsertionPoint) {
lastIP = findLastOfEqualsFromLastIP(lastIP);
// Compare against the next node after lastIP, if it exists.
if (Node nextAfterLastIP = NodeTraits::getNext(lastIP)) {
int comparison = NodeTraits::compare(nextAfterLastIP, newNode);
// If the new node compares equal to the next node, insert here.
if (comparison == 0) {
insertBetween(lastIP, nextAfterLastIP);
return;
}
// If the new node should follow the next node, start scanning
// after it.
if (comparison < 0) {
prev = nextAfterLastIP;
cur = NodeTraits::getNext(nextAfterLastIP);
}
// Otherwise, we'll need to scan from the beginning.
// If there is no next node, compare against the previous.
} else {
int comparison = NodeTraits::compare(lastIP, newNode);
// If the new node should follow the last node, we can
// insert here.
if (comparison < 0) {
insertBetween(lastIP, Node());
return;
}
// Otherwise, we'll need to scan from the beginning.
}
}
assert(!prev || NodeTraits::compare(prev, newNode) < 0);
// Scan forward, looking for a node which the new node must be
// inserted prior to.
// Invariant: prev < newNode, if prev exists
while (cur) {
// Compare the new node against the current IP.
int comparison = NodeTraits::compare(cur, newNode);
// If the new node isn't strictly greater than cur, insert here.
if (comparison >= 0) break;
// Otherwise, continue.
prev = cur;
cur = NodeTraits::getNext(prev);
}
insertBetween(prev, cur);
}
/// Add a sorted list of nodes to this merger's current list.
/// The list must be well-formed (i.e. appropriately terminated).
///
/// The relative order of nodes in both the current and the new list
/// will not change. If there are nodes in the current list which
/// compare equal to nodes in the new list, they will appear before
/// the new nodes.
///
/// For example, if the current list is `[1@A, 1@B, 2@C]`, and the new
/// list is `[0@D, 1@E, 2@F]`, the current list after the merge will
/// be `[0@D, 1@A, 1@B, 1@E, 2@C, 2@F]`.
void merge(Node rootOfNewList) {
if (!rootOfNewList) return;
Node prev = Node();
Node cur = root;
Node stopper = Node();
// If we have a previous insertion point, compare the new root
// against it.
if (Node lastIP = lastInsertionPoint) {
int comparison = NodeTraits::compare(lastIP, rootOfNewList);
// If it compares equal, we've got an insertion point where
// we can place rootOfNewList: the end of the sequence of
// equals that includes lastIP. This is a common fast path
// when we have many nodes that compare equal.
if (comparison == 0) {
lastIP = findLastOfEqualsFromLastIP(lastIP);
prev = lastIP;
cur = NodeTraits::getNext(lastIP);
goto foundInsertionPoint; // seems to be the best option
// If the new node must follow the last insertion point, we can
// at least start the search there.
} else if (comparison < 0) {
lastIP = findLastOfEqualsFromLastIP(lastIP);
prev = lastIP;
cur = NodeTraits::getNext(lastIP);
// Otherwise, we can end the initial search at that position.
} else {
stopper = lastIP;
}
}
while (rootOfNewList) {
// Invariants:
// root == [ ..., prev, cur, ... ]
// prev <= rootOfNewList
// Check if the position between prev and cur is where we should
// insert the root of the new list.
if (cur != stopper && NodeTraits::compare(cur, rootOfNewList) <= 0) {
prev = cur;
cur = NodeTraits::getNext(cur);
continue;
}
// Place rootOfNewList at this position. Note that this might not be
// a proper splice because there may be nodes following prev that
// are now no longer reflected in the existing list.
if (!prev) {
root = rootOfNewList;
} else {
foundInsertionPoint:
NodeTraits::setNext(prev, rootOfNewList);
}
// If we've run out of nodes in the existing list, it *is*
// a proper splice, and we're done.
if (!cur) {
assert(!stopper);
setLastInsertionPoint(rootOfNewList, /*known end of equals*/ false);
return;
}
// If not, scan forward in the new list looking for a node that
// cur should precede.
Node prevInNewList = rootOfNewList;
Node curInNewList = NodeTraits::getNext(rootOfNewList);
while (curInNewList && NodeTraits::compare(cur, curInNewList) > 0) {
prevInNewList = curInNewList;
curInNewList = NodeTraits::getNext(curInNewList);
}
// prevInNewList < cur <= curInNewList (if it exists)
// Turn this:
// root == [ ..., prev, cur, ... ]
// rootOfNewList == [ ..., prevInNewList, curInNewList, ... ]
// into:
// root == [ ..., prev, rootOfNewList, ..., prevInNewList,
// cur, ... ]
// rootOfNewList' == [ curInNewList, ... ]
//
// Note that the next insertion point we'll check is *after* cur,
// since we know that cur <= curInNewList.
NodeTraits::setNext(prevInNewList, cur);
rootOfNewList = curInNewList;
prev = cur;
cur = NodeTraits::getNext(cur);
setLastInsertionPoint(prevInNewList, /*known end of equals*/ true);
// Any stopper we have was only known to exceed the original root
// node of the new list, which we've now inserted. From now on,
// we'll need to scan to the end of the list.
stopper = Node();
}
}
/// Get the current list that's been built up, and clear the internal
/// state of this merger.
Node release() {
Node result = root;
root = Node();
lastInsertionPoint = Node();
return result;
}
private:
/// Set the last point at which we inserted a node, and specify
/// whether we know it was the last in its sequence of equals.
void setLastInsertionPoint(Node lastIP, bool knownEndOfEquals) {
lastInsertionPoint = lastIP;
lastInsertionPointIsKnownLastOfEquals = knownEndOfEquals;
}
/// Given the value of lastInsertionPoint (passed in to avoid
/// reloading it), find the last node in the sequence of equals that
/// contains it.
Node findLastOfEqualsFromLastIP(Node lastIP) const {
assert(lastIP == lastInsertionPoint);
if (!lastInsertionPointIsKnownLastOfEquals)
return findLastOfEquals(lastIP);
return lastIP;
}
/// Find the last node in the sequence of equals that contains `node`.
static Node findLastOfEquals(Node node) {
while (Node next = NodeTraits::getNext(node)) {
int comparison = NodeTraits::compare(node, next);
assert(comparison <= 0 && "list is out of order");
if (comparison < 0) break;
node = next;
}
return node;
}
};
} // end namespace swift
#endif