Skip to content

Commit

Permalink
Change Decode's return type to IReadOnlyList (#21)
Browse files Browse the repository at this point in the history
This avoids the memory allocation that .ToArray() entailed.
  • Loading branch information
aradalvand authored Sep 9, 2023
1 parent bff3a8c commit 83460d6
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 28 deletions.
10 changes: 5 additions & 5 deletions src/Sqids/SqidsEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,9 @@ private string Encode(ReadOnlySpan<int> numbers, int increment = 0)
/// empty, or includes characters not found in the alphabet.
/// </returns>
#if NET7_0_OR_GREATER
public T[] Decode(ReadOnlySpan<char> id)
public IReadOnlyList<T> Decode(ReadOnlySpan<char> id)
#else
public int[] Decode(ReadOnlySpan<char> id)
public IReadOnlyList<int> Decode(ReadOnlySpan<char> id)
#endif
{
if (id.IsEmpty)
Expand Down Expand Up @@ -318,7 +318,7 @@ public int[] Decode(ReadOnlySpan<char> id)
id = separatorIndex == -1 ? default : id[(separatorIndex + 1)..]; // NOTE: Everything to the right of the separator will be `id` for the next iteration

if (chunk.IsEmpty)
return result.ToArray();
return result;

var alphabetWithoutSeparator = alphabetTemp[1..]; // NOTE: Exclude the first character — which is the separator
var decodedNumber = ToNumber(chunk, alphabetWithoutSeparator);
Expand All @@ -328,7 +328,7 @@ public int[] Decode(ReadOnlySpan<char> id)
ConsistentShuffle(alphabetTemp);
}

return result.ToArray(); // TODO: A way to return an array without creating a new array from the list like this?
return result;
}

// NOTE: Implicit `string` => `Span<char>` conversion was introduced in .NET Standard 2.1 (see https://learn.microsoft.com/en-us/dotnet/api/system.string.op_implicit), which means without this overload, calling `Decode` with a string on versions older than .NET Standard 2.1 would require calling `.AsSpan()` on the string, which is cringe.
Expand All @@ -342,7 +342,7 @@ public int[] Decode(ReadOnlySpan<char> id)
/// if the ID represents a single number); or an empty array if the input ID is null,
/// empty, or includes characters not found in the alphabet.
/// </returns>
public int[] Decode(string id) => Decode(id.AsSpan());
public IReadOnlyList<int> Decode(string id) => Decode(id.AsSpan());
#endif

private bool IsBlockedId(ReadOnlySpan<char> id)
Expand Down
2 changes: 1 addition & 1 deletion test/Sqids.Tests/AlphabetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ string id
#endif

sqids.Encode(numbers).ShouldBe(id);
sqids.Decode(id).ShouldBeEquivalentTo(numbers);
sqids.Decode(id).ShouldBe(numbers);
}

[TestCase("abc", new[] { 1, 2, 3 })] // NOTE: Shortest possible alphabet
Expand Down
26 changes: 13 additions & 13 deletions test/Sqids.Tests/BlockListTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public void EncodeAndDecode_WithDefaultBlockList_BlocksWordsInDefaultBlockList()
var sqids = new SqidsEncoder();
#endif

sqids.Decode("aho1e").ShouldBeEquivalentTo(new[] { 4572721 });
sqids.Decode("aho1e").ShouldBe(new[] { 4572721 });
sqids.Encode(4572721).ShouldBe("JExTR");
}

Expand All @@ -27,7 +27,7 @@ public void EncodeAndDecode_WithEmptyBlockList_DoesNotBlockWords()
BlockList = new(),
});

sqids.Decode("aho1e").ShouldBeEquivalentTo(new[] { 4572721 });
sqids.Decode("aho1e").ShouldBe(new[] { 4572721 });
sqids.Encode(4572721).ShouldBe("aho1e");
}

Expand All @@ -47,13 +47,13 @@ public void EncodeAndDecode_WithCustomBlockList_OnlyBlocksWordsInCustomBlockList
});

// NOTE: Make sure the default blocklist isn't used
sqids.Decode("aho1e").ShouldBeEquivalentTo(new[] { 4572721 });
sqids.Decode("aho1e").ShouldBe(new[] { 4572721 });
sqids.Encode(4572721).ShouldBe("aho1e");

// NOTE: Make sure the passed blocklist IS used:
sqids.Decode("ArUO").ShouldBeEquivalentTo(new[] { 100000 });
sqids.Decode("ArUO").ShouldBe(new[] { 100000 });
sqids.Encode(100000).ShouldBe("QyG4");
sqids.Decode("QyG4").ShouldBeEquivalentTo(new[] { 100000 });
sqids.Decode("QyG4").ShouldBe(new[] { 100000 });
}

[Test]
Expand All @@ -76,7 +76,7 @@ public void EncodeAndDecode_WithBlockListBlockingMultipleEncodings_RespectsBlock
});

sqids.Encode(1_000_000, 2_000_000).ShouldBe("1aYeB7bRUt");
sqids.Decode("1aYeB7bRUt").ShouldBeEquivalentTo(new[] { 1_000_000, 2_000_000 });
sqids.Decode("1aYeB7bRUt").ShouldBe(new[] { 1_000_000, 2_000_000 });
}

[Test]
Expand All @@ -98,11 +98,11 @@ public void Decode_BlockedIds_StillDecodesSuccessfully()
},
});

sqids.Decode("86Rf07").ShouldBeEquivalentTo(new[] { 1, 2, 3 });
sqids.Decode("se8ojk").ShouldBeEquivalentTo(new[] { 1, 2, 3 });
sqids.Decode("ARsz1p").ShouldBeEquivalentTo(new[] { 1, 2, 3 });
sqids.Decode("Q8AI49").ShouldBeEquivalentTo(new[] { 1, 2, 3 });
sqids.Decode("5sQRZO").ShouldBeEquivalentTo(new[] { 1, 2, 3 });
sqids.Decode("86Rf07").ShouldBe(new[] { 1, 2, 3 });
sqids.Decode("se8ojk").ShouldBe(new[] { 1, 2, 3 });
sqids.Decode("ARsz1p").ShouldBe(new[] { 1, 2, 3 });
sqids.Decode("Q8AI49").ShouldBe(new[] { 1, 2, 3 });
sqids.Decode("5sQRZO").ShouldBe(new[] { 1, 2, 3 });
}

[Test]
Expand All @@ -120,7 +120,7 @@ public void EncodeAndDecode_WithShortCustomBlockList_RoundTripsSuccessfully()
},
});

sqids.Decode(sqids.Encode(1000)).ShouldBeEquivalentTo(new[] { 1000 });
sqids.Decode(sqids.Encode(1000)).ShouldBe(new[] { 1000 });
}

[Test]
Expand All @@ -140,7 +140,7 @@ public void EncodeAndDecode_WithLowerCaseBlockListAndUpperCaseAlphabet_IgnoresCa
});

sqids.Encode(1, 2, 3).ShouldBe("IBSHOZ"); // NOTE: Without the blocklist, would've been "SQNMPN".
sqids.Decode("IBSHOZ").ShouldBeEquivalentTo(new[] { 1, 2, 3 });
sqids.Decode("IBSHOZ").ShouldBe(new[] { 1, 2, 3 });
}

[Test]
Expand Down
10 changes: 5 additions & 5 deletions test/Sqids.Tests/EncodingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public void EncodeAndDecode_SingleNumber_ReturnsExactMatch(int number, string id
#endif

sqids.Encode(number).ShouldBe(id);
sqids.Decode(id).ShouldBeEquivalentTo(new[] { number });
sqids.Decode(id).ShouldBe(new[] { number });
}

// NOTE: Simple case
Expand Down Expand Up @@ -65,7 +65,7 @@ public void EncodeAndDecode_MultipleNumbers_ReturnsExactMatch(int[] numbers, str

sqids.Encode(numbers).ShouldBe(id);
sqids.Encode(numbers.ToList()).ShouldBe(id); // NOTE: Selects the `IEnumerable<int>` overload
sqids.Decode(id).ShouldBeEquivalentTo(numbers);
sqids.Decode(id).ShouldBe(numbers);
}

[TestCase(new[] { 0, 0, 0, 1, 2, 3, 100, 1_000, 100_000, 1_000_000, int.MaxValue })]
Expand All @@ -85,7 +85,7 @@ public void EncodeAndDecode_MultipleNumbers_RoundTripsSuccessfully(int[] numbers
var sqids = new SqidsEncoder();
#endif

sqids.Decode(sqids.Encode(numbers)).ShouldBeEquivalentTo(numbers);
sqids.Decode(sqids.Encode(numbers)).ShouldBe(numbers);
}

[TestCase("*")] // NOTE: Character not found in the alphabet
Expand Down Expand Up @@ -127,7 +127,7 @@ T number
) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
{
var sqids = new SqidsEncoder<T>();
sqids.Decode(sqids.Encode(number)).ShouldBeEquivalentTo(new[] { number });
sqids.Decode(sqids.Encode(number)).ShouldBe(new[] { number });
}

[TestCaseSource(nameof(MultipleNumbersOfDifferentIntegerTypesTestCaseSource))]
Expand All @@ -136,7 +136,7 @@ T[] numbers
) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
{
var sqids = new SqidsEncoder<T>();
sqids.Decode(sqids.Encode(numbers)).ShouldBeEquivalentTo(numbers);
sqids.Decode(sqids.Encode(numbers)).ShouldBe(numbers);
}

private static TestCaseData[] MultipleNumbersOfDifferentIntegerTypesTestCaseSource => new TestCaseData[]
Expand Down
6 changes: 3 additions & 3 deletions test/Sqids.Tests/MinLengthTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void EncodeAndDecode_WithHighMinLength_ReturnsExactMatch(int[] numbers, s
});

sqids.Encode(numbers).ShouldBe(id);
sqids.Decode(id).ShouldBeEquivalentTo(numbers);
sqids.Decode(id).ShouldBe(numbers);
}

[TestCaseSource(nameof(IncrementalMinLengthsSource))]
Expand All @@ -47,7 +47,7 @@ string id

sqids.Encode(numbers).ShouldBe(id);
sqids.Encode(numbers).Length.ShouldBeGreaterThanOrEqualTo(minLength);
sqids.Decode(id).ShouldBeEquivalentTo(numbers);
sqids.Decode(id).ShouldBe(numbers);
}
private static TestCaseData[] IncrementalMinLengthsSource => new TestCaseData[]
{
Expand Down Expand Up @@ -100,7 +100,7 @@ int[] numbers

var id = sqids.Encode(numbers);
id.Length.ShouldBeGreaterThanOrEqualTo(minLength);
sqids.Decode(id).ShouldBeEquivalentTo(numbers);
sqids.Decode(id).ShouldBe(numbers);
}
private static int[] MinLengthsValueSource => new[]
{
Expand Down
2 changes: 1 addition & 1 deletion test/Sqids.Tests/UniquenessTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void EncodeAndDecode_LargeRange_ReturnsUniqueIdsAndRoundTripsSuccessfully
var numbers = Enumerable.Repeat(i, numbersCount).ToArray();
var id = sqids.Encode(numbers);
hashSet.Add(id);
sqids.Decode(id).ShouldBeEquivalentTo(numbers);
sqids.Decode(id).ShouldBe(numbers);
}

hashSet.Count.ShouldBe(range); // NOTE: Ensures that all the IDs were unique.
Expand Down

0 comments on commit 83460d6

Please sign in to comment.