Skip to content

Commit 09a067c

Browse files
Avital-Fineslorello89NickCraver
authored
Support LCS (#2104)
Adds support for https://redis.io/commands/lcs/ (#2055) Co-authored-by: slorello89 <[email protected]> Co-authored-by: Nick Craver <[email protected]>
1 parent 116132c commit 09a067c

File tree

12 files changed

+373
-16
lines changed

12 files changed

+373
-16
lines changed

docs/ReleaseNotes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- Adds: Support for `ZMPOP` with `.SortedSetPop()`/`.SortedSetPopAsync()` ([#2094 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2094))
2828
- Adds: Support for `XAUTOCLAIM` with `.StreamAutoClaim()`/.`StreamAutoClaimAsync()` and `.StreamAutoClaimIdsOnly()`/.`StreamAutoClaimIdsOnlyAsync()` ([#2095 by ttingen](https://github.com/StackExchange/StackExchange.Redis/pull/2095))
2929
- Fix [#2071](https://github.com/StackExchange/StackExchange.Redis/issues/2071): Add `.StringSet()`/`.StringSetAsync()` overloads for source compat broken for 1 case in 2.5.61 ([#2098 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2098))
30+
- Adds: Support for `LCS` with `.StringLongestCommonSubsequence()`/`.StringLongestCommonSubsequence()`, `.StringLongestCommonSubsequenceLength()`/`.StringLongestCommonSubsequenceLengthAsync()`, and `.StringLongestCommonSubsequenceWithMatches()`/`.StringLongestCommonSubsequenceWithMatchesAsync()` ([#2104 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2104))
3031
- Adds: Support for `OBJECT FREQ` with `.KeyFrequency()`/`.KeyFrequencyAsync()` ([#2105 by Avital-Fine](https://github.com/StackExchange/StackExchange.Redis/pull/2105))
3132
- Performance: Avoids allocations when computing cluster hash slots or testing key equality ([#2110 by Marc Gravell](https://github.com/StackExchange/StackExchange.Redis/pull/2110))
3233
- Adds: Support for `SORT_RO` with `.Sort()`/`.SortAsync()` ([#2111 by slorello89](https://github.com/StackExchange/StackExchange.Redis/pull/2111))
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
3+
namespace StackExchange.Redis;
4+
5+
/// <summary>
6+
/// The result of a LongestCommonSubsequence command with IDX feature.
7+
/// Returns a list of the positions of each sub-match.
8+
/// </summary>
9+
public readonly struct LCSMatchResult
10+
{
11+
internal static LCSMatchResult Null { get; } = new LCSMatchResult(Array.Empty<LCSMatch>(), 0);
12+
13+
/// <summary>
14+
/// The matched positions of all the sub-matched strings.
15+
/// </summary>
16+
public LCSMatch[] Matches { get; }
17+
18+
/// <summary>
19+
/// The length of the longest match.
20+
/// </summary>
21+
public long LongestMatchLength { get; }
22+
23+
/// <summary>
24+
/// Returns a new <see cref="LCSMatchResult"/>.
25+
/// </summary>
26+
/// <param name="matches">The matched positions in each string.</param>
27+
/// <param name="matchLength">The length of the match.</param>
28+
internal LCSMatchResult(LCSMatch[] matches, long matchLength)
29+
{
30+
Matches = matches;
31+
LongestMatchLength = matchLength;
32+
}
33+
34+
/// <summary>
35+
/// Represents a sub-match of the longest match. i.e first indexes the matched substring in each string.
36+
/// </summary>
37+
public readonly struct LCSMatch
38+
{
39+
/// <summary>
40+
/// The first index of the matched substring in the first string.
41+
/// </summary>
42+
public long FirstStringIndex { get; }
43+
44+
/// <summary>
45+
/// The first index of the matched substring in the second string.
46+
/// </summary>
47+
public long SecondStringIndex { get; }
48+
49+
/// <summary>
50+
/// The length of the match.
51+
/// </summary>
52+
public long Length { get; }
53+
54+
/// <summary>
55+
/// Returns a new Match.
56+
/// </summary>
57+
/// <param name="firstStringIndex">The first index of the matched substring in the first string.</param>
58+
/// <param name="secondStringIndex">The first index of the matched substring in the second string.</param>
59+
/// <param name="length">The length of the match.</param>
60+
internal LCSMatch(long firstStringIndex, long secondStringIndex, long length)
61+
{
62+
FirstStringIndex = firstStringIndex;
63+
SecondStringIndex = secondStringIndex;
64+
Length = length;
65+
}
66+
}
67+
}

src/StackExchange.Redis/Enums/RedisCommand.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ internal enum RedisCommand
8686

8787
LASTSAVE,
8888
LATENCY,
89+
LCS,
8990
LINDEX,
9091
LINSERT,
9192
LLEN,
@@ -393,6 +394,7 @@ internal static bool IsPrimaryOnly(this RedisCommand command)
393394
case RedisCommand.KEYS:
394395
case RedisCommand.LASTSAVE:
395396
case RedisCommand.LATENCY:
397+
case RedisCommand.LCS:
396398
case RedisCommand.LINDEX:
397399
case RedisCommand.LLEN:
398400
case RedisCommand.LPOS:

src/StackExchange.Redis/Interfaces/IDatabase.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2771,6 +2771,46 @@ IEnumerable<SortedSetEntry> SortedSetScan(RedisKey key,
27712771
/// <remarks><seealso href="https://redis.io/commands/strlen"/></remarks>
27722772
long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None);
27732773

2774+
/// <summary>
2775+
/// Implements the longest common subsequence algorithm between the values at <paramref name="first"/> and <paramref name="second"/>,
2776+
/// returning a string containing the common sequence.
2777+
/// Note that this is different than the longest common string algorithm,
2778+
/// since matching characters in the string does not need to be contiguous.
2779+
/// </summary>
2780+
/// <param name="first">The key of the first string.</param>
2781+
/// <param name="second">The key of the second string.</param>
2782+
/// <param name="flags">The flags to use for this operation.</param>
2783+
/// <returns>A string (sequence of characters) of the LCS match.</returns>
2784+
/// <remarks><seealso href="https://redis.io/commands/lcs"/></remarks>
2785+
string? StringLongestCommonSubsequence(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None);
2786+
2787+
/// <summary>
2788+
/// Implements the longest common subsequence algorithm between the values at <paramref name="first"/> and <paramref name="second"/>,
2789+
/// returning the legnth of the common sequence.
2790+
/// Note that this is different than the longest common string algorithm,
2791+
/// since matching characters in the string does not need to be contiguous.
2792+
/// </summary>
2793+
/// <param name="first">The key of the first string.</param>
2794+
/// <param name="second">The key of the second string.</param>
2795+
/// <param name="flags">The flags to use for this operation.</param>
2796+
/// <returns>The length of the LCS match.</returns>
2797+
/// <remarks><seealso href="https://redis.io/commands/lcs"/></remarks>
2798+
long StringLongestCommonSubsequenceLength(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None);
2799+
2800+
/// <summary>
2801+
/// Implements the longest common subsequence algorithm between the values at <paramref name="first"/> and <paramref name="second"/>,
2802+
/// returning a list of all common sequences.
2803+
/// Note that this is different than the longest common string algorithm,
2804+
/// since matching characters in the string does not need to be contiguous.
2805+
/// </summary>
2806+
/// <param name="first">The key of the first string.</param>
2807+
/// <param name="second">The key of the second string.</param>
2808+
/// <param name="minLength">Can be used to restrict the list of matches to the ones of a given minimum length.</param>
2809+
/// <param name="flags">The flags to use for this operation.</param>
2810+
/// <returns>The result of LCS algorithm, based on the given parameters.</returns>
2811+
/// <remarks><seealso href="https://redis.io/commands/lcs"/></remarks>
2812+
LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None);
2813+
27742814
/// <inheritdoc cref="StringSet(RedisKey, RedisValue, TimeSpan?, bool, When, CommandFlags)" />
27752815
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
27762816
bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when);

src/StackExchange.Redis/Interfaces/IDatabaseAsync.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2723,6 +2723,46 @@ IAsyncEnumerable<SortedSetEntry> SortedSetScanAsync(RedisKey key,
27232723
/// <remarks><seealso href="https://redis.io/commands/strlen"/></remarks>
27242724
Task<long> StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None);
27252725

2726+
/// <summary>
2727+
/// Implements the longest common subsequence algorithm between the values at <paramref name="first"/> and <paramref name="second"/>,
2728+
/// returning a string containing the common sequence.
2729+
/// Note that this is different than the longest common string algorithm,
2730+
/// since matching characters in the string does not need to be contiguous.
2731+
/// </summary>
2732+
/// <param name="first">The key of the first string.</param>
2733+
/// <param name="second">The key of the second string.</param>
2734+
/// <param name="flags">The flags to use for this operation.</param>
2735+
/// <returns>A string (sequence of characters) of the LCS match.</returns>
2736+
/// <remarks><seealso href="https://redis.io/commands/lcs"/></remarks>
2737+
Task<string?> StringLongestCommonSubsequenceAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None);
2738+
2739+
/// <summary>
2740+
/// Implements the longest common subsequence algorithm between the values at <paramref name="first"/> and <paramref name="second"/>,
2741+
/// returning the legnth of the common sequence.
2742+
/// Note that this is different than the longest common string algorithm,
2743+
/// since matching characters in the string does not need to be contiguous.
2744+
/// </summary>
2745+
/// <param name="first">The key of the first string.</param>
2746+
/// <param name="second">The key of the second string.</param>
2747+
/// <param name="flags">The flags to use for this operation.</param>
2748+
/// <returns>The length of the LCS match.</returns>
2749+
/// <remarks><seealso href="https://redis.io/commands/lcs"/></remarks>
2750+
Task<long> StringLongestCommonSubsequenceLengthAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None);
2751+
2752+
/// <summary>
2753+
/// Implements the longest common subsequence algorithm between the values at <paramref name="first"/> and <paramref name="second"/>,
2754+
/// returning a list of all common sequences.
2755+
/// Note that this is different than the longest common string algorithm,
2756+
/// since matching characters in the string does not need to be contiguous.
2757+
/// </summary>
2758+
/// <param name="first">The key of the first string.</param>
2759+
/// <param name="second">The key of the second string.</param>
2760+
/// <param name="minLength">Can be used to restrict the list of matches to the ones of a given minimum length.</param>
2761+
/// <param name="flags">The flags to use for this operation.</param>
2762+
/// <returns>The result of LCS algorithm, based on the given parameters.</returns>
2763+
/// <remarks><seealso href="https://redis.io/commands/lcs"/></remarks>
2764+
Task<LCSMatchResult> StringLongestCommonSubsequenceWithMatchesAsync(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None);
2765+
27262766
/// <inheritdoc cref="StringSetAsync(RedisKey, RedisValue, TimeSpan?, bool, When, CommandFlags)" />
27272767
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
27282768
Task<bool> StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when);

src/StackExchange.Redis/KeyspaceIsolation/DatabaseWrapper.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,15 @@ public bool LockRelease(RedisKey key, RedisValue value, CommandFlags flags = Com
299299
public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) =>
300300
Inner.LockTake(ToInner(key), value, expiry, flags);
301301

302+
public string? StringLongestCommonSubsequence(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) =>
303+
Inner.StringLongestCommonSubsequence(ToInner(first), ToInner(second), flags);
304+
305+
public long StringLongestCommonSubsequenceLength(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) =>
306+
Inner.StringLongestCommonSubsequenceLength(ToInner(first), ToInner(second), flags);
307+
308+
public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) =>
309+
Inner.StringLongestCommonSubsequenceWithMatches(ToInner(first), ToInner(second), minLength, flags);
310+
302311
public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) =>
303312
Inner.Publish(ToInner(channel), message, flags);
304313

src/StackExchange.Redis/KeyspaceIsolation/WrapperBase.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,15 @@ public Task<bool> LockReleaseAsync(RedisKey key, RedisValue value, CommandFlags
310310
public Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None) =>
311311
Inner.LockTakeAsync(ToInner(key), value, expiry, flags);
312312

313+
public Task<string?> StringLongestCommonSubsequenceAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) =>
314+
Inner.StringLongestCommonSubsequenceAsync(ToInner(first), ToInner(second), flags);
315+
316+
public Task<long> StringLongestCommonSubsequenceLengthAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) =>
317+
Inner.StringLongestCommonSubsequenceLengthAsync(ToInner(first), ToInner(second), flags);
318+
319+
public Task<LCSMatchResult> StringLongestCommonSubsequenceWithMatchesAsync(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) =>
320+
Inner.StringLongestCommonSubsequenceWithMatchesAsync(ToInner(first), ToInner(second), minLength, flags);
321+
313322
public Task<long> PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) =>
314323
Inner.PublishAsync(ToInner(channel), message, flags);
315324

0 commit comments

Comments
 (0)