Skip to content

Commit 3196415

Browse files
authored
Merge pull request swiftlang#39867 from rjmccall/list-merger
[NFC] Add a utility for handling sorted linked lists
2 parents 3ee6325 + 28c07c5 commit 3196415

File tree

3 files changed

+478
-0
lines changed

3 files changed

+478
-0
lines changed

include/swift/Basic/ListMerger.h

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
//===--- ListMerger.h - Merging sorted linked lists -------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file defines a class that helps with maintaining and merging a
14+
// sorted linked list.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#ifndef SWIFT_BASIC_LISTMERGER_H
19+
#define SWIFT_BASIC_LISTMERGER_H
20+
21+
#include <assert.h>
22+
23+
namespace swift {
24+
25+
/// A class for building and merging sorted linked lists.
26+
///
27+
/// The `Node` type parameter represents a reference to a list node.
28+
/// Conceptually, a `Node` value is either null or a reference to an
29+
/// object with an abstract sort value and a `next` reference
30+
/// (another `Node` value).
31+
///
32+
/// A null reference can be created by explicitly default-constructing
33+
/// the `Node` type, e.g. with `Node()`. Converting a `Node` value
34+
/// contextually to `bool` tests whether the node is a null reference.
35+
/// `Node` values can be compared with the `==` and `!=` operators,
36+
/// and equality with `Node()` is equivalent to a `bool` conversion.
37+
/// These conditions are designed to allow pointer types to be used
38+
/// directly, but they also permit other types. `ListMerger` is not
39+
/// currently written to support smart pointer types efficiently,
40+
/// however.
41+
///
42+
/// The sort value and `next` reference are not accessed directly;
43+
/// instead, they are accessed with `static` functions on the
44+
/// `NodeTraits` type parameter:
45+
///
46+
/// ```
47+
/// /// Return the current value of the next reference.
48+
/// static Node getNext(Node n);
49+
///
50+
/// /// Set the current value of the next reference.
51+
/// static void setNext(Node n, Node next);
52+
///
53+
/// /// Compare the sort value of this node with that of another
54+
/// /// node, returning negative (<), zero (==), or positive (>).
55+
/// /// A node must compare equal to itself. A sorted list obeys
56+
/// /// the condition that each node in the list compares <= the next.
57+
/// static int compare(Node lhs, Node rhs);
58+
/// ```
59+
///
60+
/// The merger holds a current list of nodes. The sort value and
61+
/// next references of nodes must not be accessed after being added
62+
/// to the merger and before being released except by the merger.
63+
template <class Node, class NodeTraits>
64+
class ListMerger {
65+
Node root;
66+
Node lastInsertionPoint = Node();
67+
bool lastInsertionPointIsKnownLastOfEquals = false;
68+
public:
69+
/// Construct a merger with the given sorted list as its current list.
70+
ListMerger(Node initialList = Node())
71+
: root(initialList) {}
72+
73+
/// Add a single node to this merger's current list.
74+
///
75+
/// The next reference of the node will be overwritten and does not
76+
/// need to be meaningful.
77+
///
78+
/// The relative order of nodes in the current list will not change,
79+
/// and if there are nodes in the current list which compare equal
80+
/// to the new node, it will be inserted after them.
81+
void insert(Node newNode) {
82+
assert(newNode && "inserting a null node");
83+
84+
Node prev = Node();
85+
Node cur = root;
86+
Node stopper = Node();
87+
88+
// If we have a previous insertion point, compare against it.
89+
if (Node lastIP = lastInsertionPoint) {
90+
int comparison = NodeTraits::compare(lastIP, newNode);
91+
92+
// If it compares equal, put the new node immediately after the
93+
// last in the sequence of equals that contains it. This is a
94+
// common fast path when we're adding many nodes that compare equal.
95+
if (comparison == 0) {
96+
lastIP = findLastOfEqualsFromLastIP(lastIP);
97+
NodeTraits::setNext(newNode, NodeTraits::getNext(lastIP));
98+
NodeTraits::setNext(lastIP, newNode);
99+
setLastInsertionPoint(newNode, /*known last of equals*/ true);
100+
return;
101+
102+
// If the new node must follow the last insertion node, we can
103+
// at least start the search there.
104+
} else if (comparison < 0) {
105+
lastIP = findLastOfEqualsFromLastIP(lastIP);
106+
prev = lastIP;
107+
cur = NodeTraits::getNext(lastIP);
108+
109+
// Otherwise, we can at least end the search at the last inserted
110+
// node.
111+
} else {
112+
stopper = lastIP;
113+
}
114+
}
115+
116+
// Invariants:
117+
// root == [ ..., prev, cur, ... ]
118+
// prev <= newRoot
119+
120+
// Scan forward looking for either `end` or a node that strictly
121+
// follows the new node.
122+
while (cur != stopper && NodeTraits::compare(cur, newNode) <= 0) {
123+
prev = cur;
124+
cur = NodeTraits::getNext(cur);
125+
}
126+
127+
NodeTraits::setNext(newNode, cur);
128+
if (prev) {
129+
NodeTraits::setNext(prev, newNode);
130+
} else {
131+
root = newNode;
132+
}
133+
setLastInsertionPoint(newNode, /*known last of equals*/ true);
134+
}
135+
136+
/// Add a sorted list of nodes to this merger's current list.
137+
/// The list must be well-formed (i.e. appropriately terminated).
138+
///
139+
/// The relative order of nodes in both the current and the new list
140+
/// will not change. If there are nodes in the current list which
141+
/// compare equal to nodes in the new list, they will appear before
142+
/// the new nodes.
143+
///
144+
/// For example, if the current list is `[1@A, 1@B, 2@C]`, and the new
145+
/// list is `[0@D, 1@E, 2@F]`, the current list after the merge will
146+
/// be `[0@D, 1@A, 1@B, 1@E, 2@C, 2@F]`.
147+
void merge(Node rootOfNewList) {
148+
if (!rootOfNewList) return;
149+
150+
Node prev = Node();
151+
Node cur = root;
152+
Node stopper = Node();
153+
154+
// If we have a previous insertion point, compare the new root
155+
// against it.
156+
if (Node lastIP = lastInsertionPoint) {
157+
int comparison = NodeTraits::compare(lastIP, rootOfNewList);
158+
159+
// If it compares equal, we've got an insertion point where
160+
// we can place rootOfNewList: the end of the sequence of
161+
// equals that includes lastIP. This is a common fast path
162+
// when we have many nodes that compare equal.
163+
if (comparison == 0) {
164+
lastIP = findLastOfEqualsFromLastIP(lastIP);
165+
prev = lastIP;
166+
cur = NodeTraits::getNext(lastIP);
167+
goto foundInsertionPoint; // seems to be the best option
168+
169+
// If the new node must follow the last insertion point, we can
170+
// at least start the search there.
171+
} else if (comparison < 0) {
172+
lastIP = findLastOfEqualsFromLastIP(lastIP);
173+
prev = lastIP;
174+
cur = NodeTraits::getNext(lastIP);
175+
176+
// Otherwise, we can end the initial search at that position.
177+
} else {
178+
stopper = lastIP;
179+
}
180+
}
181+
182+
while (rootOfNewList) {
183+
// Invariants:
184+
// root == [ ..., prev, cur, ... ]
185+
// prev <= rootOfNewList
186+
187+
// Check if the position between prev and cur is where we should
188+
// insert the root of the new list.
189+
if (cur != stopper && NodeTraits::compare(cur, rootOfNewList) <= 0) {
190+
prev = cur;
191+
cur = NodeTraits::getNext(cur);
192+
continue;
193+
}
194+
195+
// Place rootOfNewList at this position. Note that this might not be
196+
// a proper splice because there may be nodes following prev that
197+
// are now no longer reflected in the existing list.
198+
if (!prev) {
199+
root = rootOfNewList;
200+
} else {
201+
foundInsertionPoint:
202+
NodeTraits::setNext(prev, rootOfNewList);
203+
}
204+
205+
// If we've run out of nodes in the existing list, it *is*
206+
// a proper splice, and we're done.
207+
if (!cur) {
208+
assert(!stopper);
209+
setLastInsertionPoint(rootOfNewList, /*known end of equals*/ false);
210+
return;
211+
}
212+
213+
// If not, scan forward in the new list looking for a node that
214+
// cur should precede.
215+
Node prevInNewList = rootOfNewList;
216+
Node curInNewList = NodeTraits::getNext(rootOfNewList);
217+
while (curInNewList && NodeTraits::compare(cur, curInNewList) > 0) {
218+
prevInNewList = curInNewList;
219+
curInNewList = NodeTraits::getNext(curInNewList);
220+
}
221+
222+
// prevInNewList < cur <= curInNewList (if it exists)
223+
224+
// Turn this:
225+
// root == [ ..., prev, cur, ... ]
226+
// rootOfNewList == [ ..., prevInNewList, curInNewList, ... ]
227+
// into:
228+
// root == [ ..., prev, rootOfNewList, ..., prevInNewList,
229+
// cur, ... ]
230+
// rootOfNewList' == [ curInNewList, ... ]
231+
//
232+
// Note that the next insertion point we'll check is *after* cur,
233+
// since we know that cur <= curInNewList.
234+
235+
NodeTraits::setNext(prevInNewList, cur);
236+
rootOfNewList = curInNewList;
237+
prev = cur;
238+
cur = NodeTraits::getNext(cur);
239+
240+
setLastInsertionPoint(prevInNewList, /*known end of equals*/ true);
241+
242+
// Any stopper we have was only known to exceed the original root
243+
// node of the new list, which we've now inserted. From now on,
244+
// we'll need to scan to the end of the list.
245+
stopper = Node();
246+
}
247+
}
248+
249+
/// Get the current list that's been built up, and clear the internal
250+
/// state of this merger.
251+
Node release() {
252+
Node result = root;
253+
root = Node();
254+
lastInsertionPoint = Node();
255+
return result;
256+
}
257+
258+
private:
259+
/// Set the last point at which we inserted a node, and specify
260+
/// whether we know it was the last in its sequence of equals.
261+
void setLastInsertionPoint(Node lastIP, bool knownEndOfEquals) {
262+
lastInsertionPoint = lastIP;
263+
lastInsertionPointIsKnownLastOfEquals = knownEndOfEquals;
264+
}
265+
266+
/// Given the value of lastInsertionPoint (passed in to avoid
267+
/// reloading it), find the last node in the sequence of equals that
268+
/// contains it.
269+
Node findLastOfEqualsFromLastIP(Node lastIP) const {
270+
assert(lastIP == lastInsertionPoint);
271+
if (!lastInsertionPointIsKnownLastOfEquals)
272+
return findLastOfEquals(lastIP);
273+
return lastIP;
274+
}
275+
276+
/// Find the last node in the sequence of equals that contains `node`.
277+
static Node findLastOfEquals(Node node) {
278+
while (Node next = NodeTraits::getNext(node)) {
279+
int comparison = NodeTraits::compare(node, next);
280+
assert(comparison <= 0 && "list is out of order");
281+
if (comparison < 0) break;
282+
node = next;
283+
}
284+
return node;
285+
}
286+
};
287+
288+
} // end namespace swift
289+
290+
#endif

utils/test-list-merger/Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
SWIFT_SRCROOT=${CURDIR}/../..
2+
SRCROOT=${SWIFT_SRCROOT}/..
3+
LLVM_SRCROOT=${SRCROOT}/llvm/
4+
LLVM_OBJROOT=${SRCROOT}/build/Ninja-DebugAssert/llvm-macosx-x86_64
5+
6+
HEADERS=${SWIFT_SRCROOT}/include/swift/Basic/ListMerger.h
7+
8+
CXXFLAGS=-Wall -std=c++17 -stdlib=libc++ -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -I${OBJROOT}/include -I${SWIFT_SRCROOT}/include -I${LLVM_SRCROOT}/include -I${LLVM_OBJROOT}/include
9+
10+
TestListMerger: TestListMerger.o
11+
$(CXX) -L${LLVM_OBJROOT}/lib -lLLVMSupport $< -o $@
12+
13+
TestListMerger.o: ${HEADERS}

0 commit comments

Comments
 (0)