Skip to content

Instantly share code, notes, and snippets.

@JohanLarsson
Created April 15, 2016 08:29
Show Gist options
  • Save JohanLarsson/1101d195cf3d78413e545d11e5e71cc5 to your computer and use it in GitHub Desktop.
Save JohanLarsson/1101d195cf3d78413e545d11e5e71cc5 to your computer and use it in GitHub Desktop.
internal static class FormatString
{
private static readonly IReadOnlyList<string> Empty = new string[0];
private static readonly ThreadLocal<SortedSet<int>> Indexes = new ThreadLocal<SortedSet<int>>(() => new SortedSet<int>());
/// <summary>
/// Call with "first: {0}, second {1} returns new []{"0", "1"};
/// </summary>
/// <param name="format">The format string</param>
/// <returns>An unordered list of format items found in <paramref name="format"/></returns>
internal static IReadOnlyCollection<string> GetFormatItems(string format)
{
if (string.IsNullOrEmpty(format))
{
return Empty;
}
var matches = Regex.Matches(format, @"{(?<index>\d+)}");
var items = matches.Cast<Match>()
.Select(x => x.Groups["index"].Value)
.ToList();
return items;
}
internal static int CountUnique(IReadOnlyCollection<string> items)
{
if (items.Count == 0)
{
return 0;
}
var indexes = Indexes.Value;
indexes.Clear();
foreach (var item in items)
{
int index;
if (!int.TryParse(item, out index))
{
throw new InvalidOperationException($"Format item is not an int: {item}");
}
indexes.Add(index);
}
return indexes.Count;
}
/// <summary>Checks that <paramref name="items"/> are 0-n with no gaps.</summary>
/// <param name="items">The format items</param>
/// <returns>True if <paramref name="items"/> are 0-n with no gaps</returns>
internal static bool AreItemsValid(IReadOnlyCollection<string> items)
{
if (items.Count == 0)
{
return true;
}
var indexes = Indexes.Value;
indexes.Clear();
foreach (var item in items)
{
int index;
if (!int.TryParse(item, out index))
{
return false;
}
if (index < 0 || index >= items.Count)
{
return false;
}
indexes.Add(index);
}
return indexes.Min == 0 && indexes.Max == indexes.Count - 1;
}
}
public class FormatStringTests
{
[TestCase("first: {0}, second {1}", "0, 1")]
[TestCase("second: {1}, first {0}", "1, 0")]
[TestCase("second: {1}, first {0}, first: {0}", "1, 0, 0")]
public void GetFormatItems(string format, string expecteds)
{
var expected = expecteds.Split(',')
.Select(x => x.Trim())
.ToArray();
var actual = FormatString.GetFormatItems(format);
CollectionAssert.AreEqual(expected, actual);
}
[TestCase("0", true)]
[TestCase("0, 0, 0", true)]
[TestCase("1, 0, 0", true)]
[TestCase("0, 0, 1", true)]
[TestCase("1", false)]
[TestCase("1, 1", false)]
[TestCase("0, 2", false)]
public void AreItemsValid(string itemsString, bool expected)
{
var items = itemsString.Split(',')
.Select(x => x.Trim())
.ToArray();
var actual = FormatString.AreItemsValid(items);
Assert.AreEqual(expected, actual);
actual = FormatString.AreItemsValid(items);
Assert.AreEqual(expected, actual);
}
[Test]
public void AreItemsValidWhenEmpty()
{
Assert.IsTrue(FormatString.AreItemsValid(new string[0]));
}
[TestCase("0", 1)]
[TestCase("0, 0, 0", 1)]
[TestCase("1, 0, 0", 2)]
[TestCase("0, 0, 1", 2)]
public void Count(string itemsString, int expected)
{
var items = itemsString.Split(',')
.Select(x => x.Trim())
.ToArray();
var actual = FormatString.CountUnique(items);
Assert.AreEqual(expected, actual);
actual = FormatString.CountUnique(items);
Assert.AreEqual(expected, actual);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment