Created
July 12, 2023 12:06
-
-
Save nietras/08c1ecef7477e6084bc608fd19000051 to your computer and use it in GitHub Desktop.
David Fowler RemoveInstanceIdFromQuery C# Optimization Challenge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.InteropServices; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
namespace RemoveInstanceIdFromQueryStringTest; | |
[TestClass] | |
public class RemoveInstanceIdFromQueryStringTest | |
{ | |
// David Fowler tweet https://twitter.com/davidfowl/status/1678738294933159937 | |
// Cases to consider: | |
// A. `query` is null/Empty => null | |
// B. `instanceId=` is not found => `query` | |
// C. `instanceId=<XX>` is `query` => null (assume return null since query empty returns null) | |
// D. `instanceId=` is found at start => copy of `query` after `instanceId=<XX>&` | |
// E. `instanceId=<XX>` is found at end => copy of `query` before `instanceId=<XX>` | |
// F. `instanceId=` is found at middle => copy of `query` before + after `instanceId=<XX>&` | |
// G. Ignore partial `instanceId=` match and repeat | |
// Code assumes `&` followed by something always. | |
[TestMethod] | |
public void Test() | |
{ | |
// A. | |
Verify(null, null!); | |
Verify(null, string.Empty); | |
// B. | |
Verify("a"); | |
Verify("k1=v1&k2=v2"); | |
Verify("k1=v1&XinstanceId=v2"); | |
Verify("k1=v1&instanceIdX=v2"); | |
// C. | |
Verify(null, "instanceId=V"); | |
// D. | |
Verify("k=v", "instanceId=V&k=v"); | |
// E. | |
Verify("k=v", "k=v&instanceId=V"); | |
// F. | |
Verify("k1=v1&k2=v2", "k1=v1&instanceId=V&k2=v2"); | |
// G. | |
Verify("k1=v1&instanceIdX=v2", "instanceId=V&k1=v1&instanceIdX=v2"); | |
Verify("k1=v1&instanceIdX=v2", "k1=v1&instanceId=V&instanceIdX=v2"); | |
Verify("k1=v1&instanceIdX=v2", "k1=v1&instanceIdX=v2&instanceId=V"); | |
} | |
static void Verify(string query) => Verify(query, query); | |
static void Verify(string? expected, string query) => | |
Assert.AreEqual(expected, RemoveInstanceIdFromQueryString(query)); | |
static string? RemoveInstanceIdFromQueryString(string query) | |
{ | |
const string key = "instanceId="; | |
const char separator = '&'; | |
// A. `query` is null/Empty => null | |
if (string.IsNullOrEmpty(query)) { return null; } | |
var end = 0; | |
do | |
{ | |
var start = query.IndexOf(key, end, StringComparison.OrdinalIgnoreCase); | |
// B. `instanceId=` is not found => `query` | |
if (start < 0) { return query; } | |
var newEnd = start + key.Length; | |
end = query.IndexOf(separator, newEnd); | |
if (start == 0) | |
{ | |
// C. `instanceId=<XX>` is `query` => null (assume return null since query empty returns null) | |
if (end < 0) { return null; } | |
// D. `instanceId=` is found at start => copy of `query` after `instanceId=<XX>&` | |
return query.Substring(end + 1); | |
} | |
// The key should be preceded by & if not at start | |
var separatorIndex = start - 1; | |
var isKey = query[separatorIndex] == '&'; | |
if (isKey) | |
{ | |
// E. `instanceId=<XX>` is found at end => copy of `query` before `instanceId=<XX>` | |
if (end < 0) { return query.Substring(0, separatorIndex); } | |
// F. `instanceId=` is found at middle => copy of `query` before + after `instanceId=<XX>&` | |
return CopyBeforeAfterToNewString(query, start, end); | |
} | |
// G. Ignore partial `instanceId=` match and repeat | |
end = newEnd; | |
} | |
while (true); | |
static string CopyBeforeAfterToNewString(ReadOnlySpan<char> query, int start, int end) | |
{ | |
var queryBefore = query.Slice(0, start); | |
var queryAfter = query.Slice(end + 1); | |
// Create a zero initialized string (avoid filling) | |
var result = new string('\0', queryBefore.Length + queryAfter.Length); | |
// Jump through hoops to copy to that string | |
var resultSpan = MemoryMarshal.CreateSpan( | |
ref MemoryMarshal.GetReference(result.AsSpan()), result.Length); | |
queryBefore.CopyTo(resultSpan.Slice(0, queryBefore.Length)); | |
queryAfter.CopyTo(resultSpan.Slice(queryBefore.Length)); | |
return result; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment