Skip to content

Commit a502b2a

Browse files
committed
Fix issue 16588 - uniqs BidirectionalRange behavior is inconsistent with its InputRange behavior
1 parent 72af009 commit a502b2a

File tree

1 file changed

+141
-14
lines changed

1 file changed

+141
-14
lines changed

std/algorithm/iteration.d

Lines changed: 141 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4804,16 +4804,18 @@ Params:
48044804
pred = Predicate for determining equivalence between range elements.
48054805
r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of
48064806
elements to filter.
4807+
pp = The $(LREF PickingPolicy), which by default is the first unique
4808+
element from left to right
48074809
48084810
Returns:
48094811
An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of
48104812
consecutively unique elements in the original range. If $(D r) is also a
48114813
forward range or bidirectional range, the returned range will be likewise.
48124814
*/
4813-
auto uniq(alias pred = "a == b", Range)(Range r)
4815+
auto uniq(alias pred = "a == b", Range)(Range r, PickingPolicy pp = PickingPolicy.first)
48144816
if (isInputRange!Range && is(typeof(binaryFun!pred(r.front, r.front)) == bool))
48154817
{
4816-
return UniqResult!(binaryFun!pred, Range)(r);
4818+
return UniqResult!(binaryFun!pred, Range)(r, pp);
48174819
}
48184820

48194821
///
@@ -4835,13 +4837,64 @@ if (isInputRange!Range && is(typeof(binaryFun!pred(r.front, r.front)) == bool))
48354837
assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1]));
48364838
}
48374839

4840+
@safe unittest
4841+
{
4842+
import std.algorithm.comparison : equal;
4843+
4844+
struct S
4845+
{
4846+
int i;
4847+
string s;
4848+
}
4849+
4850+
auto arr = [ S(1, "a"), S(1, "b"), S(2, "c"), S(2, "d") ];
4851+
4852+
// Let's consider just the 'i' member for equality
4853+
auto r = arr.uniq!((a, b) => a.i == b.i);
4854+
assert(r.equal([S(1, "a"), S(2, "c")]));
4855+
assert(r.front == S(1, "a"));
4856+
assert(r.back == S(2, "c"));
4857+
4858+
auto r2 = arr.uniq!((a, b) => a.i == b.i)(PickingPolicy.last);
4859+
assert(r2.equal([S(1, "b"), S(2, "d")]));
4860+
assert(r2.front == S(1, "b"));
4861+
assert(r2.back == S(2, "d"));
4862+
4863+
auto arr2 = [ S(1, "a"), S(1, "b") ];
4864+
auto r3 = arr2.uniq!((a, b) => a.i == b.i);
4865+
assert(r3.front == S(1, "a"));
4866+
assert(r3.front == r3.back);
4867+
}
4868+
4869+
/**
4870+
Dictates how `uniq` elements should be picked. By default stop at
4871+
the end of the shortest of all ranges.
4872+
*/
4873+
enum PickingPolicy
4874+
{
4875+
/// Stop at the first uniq element, from left to right
4876+
first,
4877+
/// Stop at the last uniq element, from left to right
4878+
last
4879+
}
4880+
48384881
private struct UniqResult(alias pred, Range)
48394882
{
4883+
private PickingPolicy _pickingPolicy;
48404884
Range _input;
4885+
ElementType!Range _front;
4886+
bool _isInFront;
4887+
static if (isBidirectionalRange!Range)
4888+
{
4889+
ElementType!Range _back;
4890+
bool _isInBack;
4891+
bool _isOverlapping;
4892+
}
48414893

4842-
this(Range input)
4894+
this(Range input, PickingPolicy pp = PickingPolicy.first)
48434895
{
48444896
_input = input;
4897+
_pickingPolicy = pp;
48454898
}
48464899

48474900
auto opSlice()
@@ -4852,37 +4905,101 @@ private struct UniqResult(alias pred, Range)
48524905
void popFront()
48534906
{
48544907
assert(!empty, "Attempting to popFront an empty uniq.");
4855-
auto last = _input.front;
4856-
do
4908+
if (_input.empty)
48574909
{
4858-
_input.popFront();
4910+
_isInFront = false;
4911+
static if (isBidirectionalRange!Range)
4912+
{
4913+
if (_isOverlapping)
4914+
{
4915+
_isInBack = false;
4916+
}
4917+
}
4918+
}
4919+
else
4920+
{
4921+
_isInFront = true;
4922+
_front = _input.front;
4923+
ElementType!Range last;
4924+
do
4925+
{
4926+
last = _input.front;
4927+
_input.popFront();
4928+
}
4929+
while (!_input.empty && pred(last, _input.front));
4930+
4931+
if (_pickingPolicy == PickingPolicy.last)
4932+
{
4933+
_front = last;
4934+
}
48594935
}
4860-
while (!_input.empty && pred(last, _input.front));
48614936
}
48624937

48634938
@property ElementType!Range front()
48644939
{
48654940
assert(!empty, "Attempting to fetch the front of an empty uniq.");
4866-
return _input.front;
4941+
static if (isBidirectionalRange!Range)
4942+
{
4943+
if (_input.empty && !_isInFront && _isInBack)
4944+
{
4945+
_isInFront = true;
4946+
_isOverlapping = true;
4947+
_front = _back;
4948+
}
4949+
}
4950+
if (!_isInFront)
4951+
{
4952+
popFront();
4953+
}
4954+
return _front;
48674955
}
48684956

48694957
static if (isBidirectionalRange!Range)
48704958
{
48714959
void popBack()
48724960
{
48734961
assert(!empty, "Attempting to popBack an empty uniq.");
4874-
auto last = _input.back;
4875-
do
4962+
if (_input.empty)
48764963
{
4877-
_input.popBack();
4964+
_isInBack = false;
4965+
if (_isOverlapping)
4966+
{
4967+
_isInFront = false;
4968+
}
4969+
}
4970+
else
4971+
{
4972+
_isInBack = true;
4973+
_back = _input.back;
4974+
ElementType!Range last;
4975+
do
4976+
{
4977+
last = _input.back;
4978+
_input.popBack();
4979+
}
4980+
while (!_input.empty && pred(_back, _input.back));
4981+
4982+
if (_pickingPolicy == PickingPolicy.first)
4983+
{
4984+
_back = last;
4985+
}
48784986
}
4879-
while (!_input.empty && pred(last, _input.back));
48804987
}
48814988

48824989
@property ElementType!Range back()
48834990
{
48844991
assert(!empty, "Attempting to fetch the back of an empty uniq.");
4885-
return _input.back;
4992+
if (_input.empty && _isInFront && !_isInBack)
4993+
{
4994+
_isInBack = true;
4995+
_isOverlapping = true;
4996+
_back = _front;
4997+
}
4998+
else if (!_isInBack)
4999+
{
5000+
popBack();
5001+
}
5002+
return _back;
48865003
}
48875004
}
48885005

@@ -4892,7 +5009,17 @@ private struct UniqResult(alias pred, Range)
48925009
}
48935010
else
48945011
{
4895-
@property bool empty() { return _input.empty; }
5012+
@property bool empty()
5013+
{
5014+
static if (isBidirectionalRange!Range)
5015+
{
5016+
return _input.empty && !_isInFront && !_isInBack;
5017+
}
5018+
else
5019+
{
5020+
return _input.empty && !_isInFront;
5021+
}
5022+
}
48965023
}
48975024

48985025
static if (isForwardRange!Range)

0 commit comments

Comments
 (0)