Skip to content

Commit f919a11

Browse files
committed
improve lookups using HashSets and Dictionaries
1 parent db7b6d8 commit f919a11

File tree

14 files changed

+94
-95
lines changed

14 files changed

+94
-95
lines changed

src/System.CommandLine.Tests/ArgumentTests.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -355,16 +355,13 @@ public void Default_value_and_custom_argument_parser_can_be_used_together()
355355
var argument = new Argument<int>(_ => 789, true);
356356
argument.SetDefaultValue(123);
357357

358-
var result = argument.Parse("");
358+
var result = argument.Parse("");
359359

360-
var argumentResult = result.FindResultFor(argument);
361-
362-
argumentResult
363-
.GetValueOrDefault<int>()
364-
.Should()
365-
.Be(123);
360+
result.ValueForArgument(argument)
361+
.Should()
362+
.Be(123);
366363
}
367-
364+
368365
[Fact]
369366
public void Multiple_command_arguments_can_have_custom_parse_delegates()
370367
{

src/System.CommandLine/ArgumentArity.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,15 @@ public ArgumentArity(int minimumNumberOfValues, int maximumNumberOfValues)
3030
public int MaximumNumberOfValues { get; set; }
3131

3232
internal static FailedArgumentConversionArityResult? Validate(
33-
SymbolResult? symbolResult,
33+
SymbolResult symbolResult,
3434
IArgument argument,
3535
int minimumNumberOfValues,
3636
int maximumNumberOfValues)
3737
{
3838
var argumentResult = symbolResult switch
3939
{
40-
CommandResult commandResult => commandResult.Root?.FindResultFor(argument),
41-
OptionResult optionResult => optionResult.Children.ResultFor(argument),
42-
_ => symbolResult
40+
ArgumentResult a => a,
41+
_ => symbolResult.Root!.FindResultFor(argument)
4342
};
4443

4544
var tokenCount = argumentResult?.Tokens.Count ?? 0;

src/System.CommandLine/Binding/ArgumentConversionResultSet.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ namespace System.CommandLine.Binding
88
{
99
internal class ArgumentConversionResultSet : AliasedSet<ArgumentConversionResult>
1010
{
11-
protected override IReadOnlyList<string> GetAliases(ArgumentConversionResult item)
11+
protected override IReadOnlyCollection<string> GetAliases(ArgumentConversionResult item)
1212
{
1313
return new[] { item.Argument.Name };
1414
}
1515

16-
protected override IReadOnlyList<string> GetRawAliases(ArgumentConversionResult item)
16+
protected override IReadOnlyCollection<string> GetRawAliases(ArgumentConversionResult item)
1717
{
1818
return new[] { item.Argument.Name };
1919
}

src/System.CommandLine/Binding/FailedArgumentTypeConversionResult.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ private static string FormatErrorMessage(
2525
{
2626
// TODO: (FailedArgumentTypeConversionResult) localize
2727

28-
var firstParent = a.Parents.First();
28+
var firstParent = a.Parents[0];
2929

3030
var symbolType =
3131
firstParent switch {
@@ -34,7 +34,7 @@ private static string FormatErrorMessage(
3434
_ => null
3535
};
3636

37-
var alias = firstParent.RawAliases[0];
37+
var alias = firstParent.RawAliases.First();
3838

3939
return $"Cannot parse argument '{value}' for {symbolType} '{alias}' as expected type {type}.";
4040
}

src/System.CommandLine/Collections/AliasedSet.cs

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,23 @@ namespace System.CommandLine.Collections
99
public abstract class AliasedSet<T> : IReadOnlyList<T>
1010
where T : class
1111
{
12+
private protected Dictionary<string, T> _itemsByAlias = new Dictionary<string, T>();
13+
1214
protected IList<T> Items { get; } = new List<T>();
1315

1416
public T? this[string alias] => GetByAlias(alias);
1517

1618
public T? GetByAlias(string alias)
1719
{
18-
for (var i = 0; i < Items.Count; i++)
20+
if (_itemsByAlias.TryGetValue(alias, out var value) &&
21+
value is { })
1922
{
20-
var item = Items[i];
21-
22-
if (Contains(GetAliases(item), alias) ||
23-
Contains(GetRawAliases(item), alias))
24-
{
25-
return item;
26-
}
23+
return value;
2724
}
2825

2926
return null;
3027
}
3128

32-
private protected bool Contains(
33-
IReadOnlyList<string> aliases,
34-
string alias)
35-
{
36-
for (var i = 0; i < aliases.Count; i++)
37-
{
38-
if (string.Equals(aliases[i], alias))
39-
{
40-
return true;
41-
}
42-
}
43-
44-
return false;
45-
}
46-
4729
public int Count => Items.Count;
4830

4931
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
@@ -53,18 +35,50 @@ private protected bool Contains(
5335
internal virtual void Add(T item)
5436
{
5537
Items.Add(item);
38+
39+
foreach (var alias in GetRawAliases(item))
40+
{
41+
if (!_itemsByAlias.ContainsKey(alias))
42+
{
43+
_itemsByAlias.Add(alias, item);
44+
}
45+
}
46+
47+
foreach (var alias in GetAliases(item))
48+
{
49+
if (!_itemsByAlias.ContainsKey(alias))
50+
{
51+
_itemsByAlias.Add(alias, item);
52+
}
53+
}
5654
}
5755

5856
internal void Remove(T item)
5957
{
6058
Items.Remove(item);
59+
60+
foreach (var alias in GetRawAliases(item))
61+
{
62+
if (_itemsByAlias.ContainsKey(alias))
63+
{
64+
_itemsByAlias.Remove(alias);
65+
}
66+
}
67+
68+
foreach (var alias in GetAliases(item))
69+
{
70+
if (_itemsByAlias.ContainsKey(alias))
71+
{
72+
_itemsByAlias.Remove(alias);
73+
}
74+
}
6175
}
6276

63-
protected abstract IReadOnlyList<string> GetAliases(T item);
77+
protected abstract IReadOnlyCollection<string> GetAliases(T item);
6478

65-
protected abstract IReadOnlyList<string> GetRawAliases(T item);
79+
protected abstract IReadOnlyCollection<string> GetRawAliases(T item);
6680

67-
public bool Contains(string alias) => GetByAlias(alias) != null;
81+
public bool Contains(string alias) => _itemsByAlias.ContainsKey(alias);
6882

6983
public T this[int index] => Items[index];
7084
}

src/System.CommandLine/Collections/SymbolSet.cs

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Linq;
67

78
namespace System.CommandLine.Collections
89
{
@@ -21,65 +22,51 @@ internal bool IsAnyAliasInUse(
2122
ISymbol item,
2223
[MaybeNullWhen(false)] out string aliasAlreadyInUse)
2324
{
24-
var itemRawAliases = GetRawAliases(item);
25-
26-
for (var i = 0; i < Items.Count; i++)
25+
if (item is IArgument)
2726
{
28-
var existingItem = Items[i];
27+
// arguments don't have aliases so match based on Name
28+
for (var i = 0; i < Items.Count; i++)
29+
{
30+
var existing = Items[i];
31+
if (string.Equals(item.Name, existing.Name, StringComparison.Ordinal))
32+
{
33+
aliasAlreadyInUse = existing.Name;
34+
return true;
35+
}
36+
}
37+
}
38+
else
39+
{
40+
var itemRawAliases = item.RawAliases.ToArray();
2941

30-
for (var j = 0; j < itemRawAliases.Count; j++)
42+
for (var i = 0; i < itemRawAliases.Length; i++)
3143
{
32-
var rawAliasToCheckFor = itemRawAliases[j];
44+
var alias = itemRawAliases[i];
3345

34-
if (Contains(GetRawAliases(existingItem), rawAliasToCheckFor))
46+
if (_itemsByAlias.ContainsKey(alias))
3547
{
36-
aliasAlreadyInUse = rawAliasToCheckFor;
48+
aliasAlreadyInUse = alias;
3749
return true;
3850
}
3951
}
4052
}
4153

4254
aliasAlreadyInUse = null!;
43-
return false;
4455

45-
static IReadOnlyList<string> GetRawAliases(ISymbol symbol)
46-
{
47-
return symbol switch
48-
{
49-
IArgument arg => new[] { arg.Name },
50-
_ => symbol.RawAliases
51-
};
52-
}
56+
return false;
5357
}
5458

5559
internal void ThrowIfAnyAliasIsInUse(ISymbol item)
5660
{
57-
string? rawAliasAlreadyInUse;
58-
59-
switch (item)
61+
if (IsAnyAliasInUse(item, out var rawAliasAlreadyInUse))
6062
{
61-
case IOption _:
62-
case ICommand _:
63-
if (IsAnyAliasInUse(item, out rawAliasAlreadyInUse))
64-
{
65-
throw new ArgumentException($"Alias '{rawAliasAlreadyInUse}' is already in use.");
66-
}
67-
68-
break;
69-
70-
case IArgument argument:
71-
if (IsAnyAliasInUse(argument, out rawAliasAlreadyInUse))
72-
{
73-
throw new ArgumentException($"Alias '{rawAliasAlreadyInUse}' is already in use.");
74-
}
75-
76-
break;
63+
throw new ArgumentException($"Alias '{rawAliasAlreadyInUse}' is already in use.");
7764
}
7865
}
7966

80-
protected override IReadOnlyList<string> GetAliases(ISymbol item) =>
67+
protected override IReadOnlyCollection<string> GetAliases(ISymbol item) =>
8168
item.Aliases;
8269

83-
protected override IReadOnlyList<string> GetRawAliases(ISymbol item) => item.RawAliases;
70+
protected override IReadOnlyCollection<string> GetRawAliases(ISymbol item) => item.RawAliases;
8471
}
8572
}

src/System.CommandLine/ISymbol.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ public interface ISymbol : ISuggestionSource
1313

1414
string? Description { get; }
1515

16-
IReadOnlyList<string> Aliases { get; }
16+
IReadOnlyCollection<string> Aliases { get; }
1717

18-
IReadOnlyList<string> RawAliases { get; }
18+
IReadOnlyCollection<string> RawAliases { get; }
1919

2020
bool HasAlias(string alias);
2121

src/System.CommandLine/Option.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public virtual Argument Argument
5656

5757
private protected override void ChooseNameForUnnamedArgument(Argument argument)
5858
{
59-
argument.Name = Aliases[0].ToLower();
59+
argument.Name = Name.ToLower();
6060
}
6161
}
6262
}

src/System.CommandLine/Parsing/ArgumentResult.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ internal virtual ArgumentConversionResult Convert(
5151
var parentResult = Parent;
5252

5353
if (ShouldCheckArity() &&
54+
parentResult is {} &&
5455
ArgumentArity.Validate(parentResult,
5556
argument,
5657
argument.Arity.MinimumNumberOfValues,

src/System.CommandLine/Parsing/CommandResult.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ internal CommandResult(
3535

3636
public Token Token { get; }
3737

38-
internal virtual RootCommandResult? Root => (Parent as CommandResult)?.Root;
39-
4038
internal bool TryGetValueForArgument(
4139
IValueDescriptor valueDescriptor,
4240
out object? value)

0 commit comments

Comments
 (0)