Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Fall back to linear search for prefix matches
Browse files Browse the repository at this point in the history
  • Loading branch information
dougbu committed Jun 30, 2017
1 parent 293ac81 commit f76a390
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 6 deletions.
58 changes: 52 additions & 6 deletions src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ private int BinarySearch(string prefix)
{
Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));

// Ok, so now we have a candiate that starts with the prefix. If the candidate is longer than
// Okay, now we have a candidate that starts with the prefix. If the candidate is longer than
// the prefix, we need to look at the next character and see if it's a delimiter.
if (candidate.Length == prefix.Length)
{
Expand All @@ -208,15 +208,17 @@ private int BinarySearch(string prefix)
return pivot;
}

// Ok, so the candidate has some extra text. We need to keep searching, but we know
// the candidate string is considered "greater" than the prefix, so treat it as-if
// the comparer returned a negative number.
// Okay, so the candidate has some extra text. We need to keep searching.
//
// Ex:
// Can often assume the candidate string is greater than the prefix e.g. that works for
// prefix: product
// candidate: productId
// most of the time because "product", "product.id", etc. will sort earlier than "productId". But,
// the assumption isn't correct if "product[0]" is also in _sortedValues because that value will
// sort later than "productId".
//
compare = -1;
// Fall back to brute force and cover all the cases.
return LinearSearch(prefix, start, end);
}

if (compare > 0)
Expand All @@ -231,5 +233,49 @@ private int BinarySearch(string prefix)

return ~start;
}

private int LinearSearch(string prefix, int start, int end)
{
for (; start <= end; start++)
{
var candidate = _sortedValues[start];
var compare = string.Compare(
prefix,
0,
candidate,
0,
prefix.Length,
StringComparison.OrdinalIgnoreCase);
if (compare == 0)
{
Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));

// Okay, now we have a candidate that starts with the prefix. If the candidate is longer than
// the prefix, we need to look at the next character and see if it's a delimiter.
if (candidate.Length == prefix.Length)
{
// Exact match
return start;
}

var c = candidate[prefix.Length];
if (c == '.' || c == '[')
{
// Match, followed by delimiter
return start;
}

// Keep checking until we've passed all StartsWith() matches.
}

if (compare < 0)
{
// Prefix is less than the candidate. No potential matches left.
break;
}
}

return ~start;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,54 @@ public void ContainsPrefix_HasEntries_PrefixMatch_WithSquareBrace(string prefix)
Assert.True(result);
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
public void ContainsPrefix_HasEntries_PartialAndPrefixMatch_WithDot(int partialMatches)
{
// Arrange
var keys = new string[partialMatches + 1];
for (var i = 0; i < partialMatches; i++)
{
keys[i] = $"aa[{i}]";
}
keys[partialMatches] = "a.b"; // Sorted before all "aa" keys.
var container = new PrefixContainer(keys);

// Act
var result = container.ContainsPrefix("a");

// Assert
Assert.True(result);
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
public void ContainsPrefix_HasEntries_PartialAndPrefixMatch_WithSquareBrace(int partialMatches)
{
// Arrange
var keys = new string[partialMatches + 1];
for (var i = 0; i < partialMatches; i++)
{
keys[i] = $"aa[{i}]";
}
keys[partialMatches] = "a[0]"; // Sorted after all "aa" keys.
var container = new PrefixContainer(keys);

// Act
var result = container.ContainsPrefix("a");

// Assert
Assert.True(result);
}

[Theory]
[InlineData("")]
[InlineData("foo")]
Expand Down

0 comments on commit f76a390

Please sign in to comment.