Redis STRALGO LCS命令與實(shí)現(xiàn)

作者: Wen Hui

轉(zhuǎn)載:中間件小哥

在 Redis 6.0-rc4版本的reiease中茧痒,我們看到 Redis支持一個(gè)新命令及其子命令: STRALO LCS, LCS是longest common subsequence(最長公共子序列)的縮寫,其定義是:一個(gè)數(shù)列{\displaystyle S}载荔,如果分別是兩個(gè)或多個(gè)已知數(shù)列的子序列内贮,且是所有符合此條件序列中最長的规脸,則{\displaystyle S}稱為已知序列的最長公共子序列沽一。例如x = [A,,B,C,B,D,A,B], y = [B,D,C,A,B,A],則序列[B,C,A]為x和y的最長公共子序列鼎兽。LCS在現(xiàn)實(shí)生活中有很多應(yīng)用答姥,例如檢測文本相似度铣除,版本控制等,在生物醫(yī)學(xué)中踢涌,lcs也被用在檢測DNA樣本的相似度。Redis的作者salvatore也在他的twitter中提到使用使用·lcs測試covid-19冠狀病毒的基因序列序宦。(https://twitter.com/antirez/status/1245448546205216777)睁壁。

STRALGO LCS命令格式如下:

STRALGO LCS [KEYS ...] [STRINGS ...] [LEN] [IDX] [MINMATCHLEN <len>] [WITHMATCHLEN]

借用官網(wǎng)的例子,如果我們計(jì)算ohmytext和mynnewtext兩個(gè)字符串的lcs,可以使用如下命令:

<pre class="public-DraftStyleDefault-pre" data-offset-key="112jf-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(26, 26, 26); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="326i" data-offset-key="112jf-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

STRALGO LCS STRINGS ohmytext mynewtext
"mytext"

</pre>

</pre>

我們也可以計(jì)算兩個(gè)鍵相對應(yīng)的字符串值的lcs互捌,通過使用keys參數(shù)潘明,例子如下:

<pre class="public-DraftStyleDefault-pre" data-offset-key="61fa1-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(26, 26, 26); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="326i" data-offset-key="61fa1-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

MSET key1 ohmytext key2 mynewtext
OK
STRALGO LCS KEYS key1 key2
"mytext"

</pre>

</pre>

我們也可以使用len參數(shù)只計(jì)算lcs的長度而不返回具體的lcs字符串:

<pre class="public-DraftStyleDefault-pre" data-offset-key="4piam-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(26, 26, 26); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="326i" data-offset-key="4piam-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

STRALGO LCS STRINGS ohmytext mynewtext LEN
6

</pre>

</pre>

另外,我們也可以通過idx參數(shù)得到每一個(gè)lcs字符的位置秕噪。

<pre class="public-DraftStyleDefault-pre" data-offset-key="2jseh-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(26, 26, 26); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="326i" data-offset-key="2jseh-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

STRALGO LCS KEYS key1 key2 IDX

  1. "matches"
        1. (integer) 4
        2. (integer) 7
        1. (integer) 5
        2. (integer) 8
        1. (integer) 2
        2. (integer) 3
        1. (integer) 0
        2. (integer) 1
  2. "len"
  3. (integer) 6

</pre>

</pre>

如上所示钳降,字符串key1的值(ohmytest)和key2的值(mynewtest)的lcs結(jié)果為字符串1的4-7索引和字符串2的5-8的索引(對應(yīng)著test)和字符串1的2-3的索引和字符串2的0-1的索引(對應(yīng)著my)。因?yàn)橛?jì)算的時(shí)候是從后往前計(jì)算的腌巾,所以輸出的結(jié)果也是相反的遂填。

我們也可以通過提供minmatchlen參數(shù)指定最小的匹配長度,如下所示:

<pre class="public-DraftStyleDefault-pre" data-offset-key="c11jt-0-0" style="margin: 1.4em 0px; padding: 0.88889em; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: auto; background: rgb(246, 246, 246); border-radius: 4px; color: rgb(26, 26, 26); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

<pre class="Editable-styled" data-block="true" data-editor="326i" data-offset-key="c11jt-0-0" style="margin: 0px; padding: 0px; font-size: 0.9em; word-break: normal; overflow-wrap: normal; white-space: pre; overflow: initial; background: rgb(246, 246, 246); border-radius: 0px;">

STRALGO LCS KEYS key1 key2 IDX MINMATCHLEN 4

  1. "matches"
        1. (integer) 4
        2. (integer) 7
        1. (integer) 5
        2. (integer) 8
  2. "len"
  3. (integer) 6

</pre>

</pre>

這樣字符串1的2-3的索引和字符串2的0-1的索引(對應(yīng)著my)的匹配就被過濾掉了澈蝙。

求解LCS及其長度本身是個(gè)·np-hard問題吓坚,但可以通過使用空間換時(shí)間的思想使用動(dòng)態(tài)規(guī)劃在多項(xiàng)式時(shí)間來求解。

具體思路如下灯荧,假如我們要求數(shù)組x [x1,x2,x3,x4 … xm]和數(shù)組y [y1,y2,y3,y4 … yn]的lcs長度礁击,如果xm 和yn的值相等,那么原問題便轉(zhuǎn)化為x的子數(shù)組x [x1,x2,x3,x4 … xm-1] 和y [y1,y2,y3,y4 … yn-1]的lcs長度加一逗载。相反地哆窿,如果xm 和yn的值不相等,則lcs的長度為lcs(x[x1,x2,x3,x4 … xm-1], y[y1,y2,y3,y4 … yn]) 和lcs(x[x1,x2,x3,x4 … xm], y[y1,y2,y3,y4 … yn-1])的較大值厉斟。綜上所述挚躯,通過轉(zhuǎn)換為子問題來求解,狀態(tài)轉(zhuǎn)移方程如下:

image

我們在具體實(shí)現(xiàn)時(shí)擦秽,可以通過建立動(dòng)態(tài)規(guī)劃表秧均,來存儲之前計(jì)算過的兩個(gè)前綴字串的lcs長度。例如如果我們要計(jì)算mynewtest和ohmytest的lcs長度号涯,則動(dòng)態(tài)規(guī)劃表如下:

image

我們從上向下目胡,從左向右填充填充動(dòng)態(tài)規(guī)劃表,需要注意的是邊界情況链快。在兩個(gè)前綴子串有一個(gè)為空的時(shí)候誉己,lcs的長度也為0.當(dāng)xi和yj相同時(shí),則當(dāng)前l(fā)cs長度為左上角的方格的值加1域蜗,當(dāng)xi和yj不相同時(shí)巨双,則當(dāng)前l(fā)cs長度為左邊或右邊方格中的值的最大值噪猾。這樣,當(dāng)處理到最右下角的方格時(shí)筑累,計(jì)算出的值就為兩個(gè)字符串的lcs長度袱蜡。

接下來我們要考慮的是如果知道lcs長度的動(dòng)態(tài)規(guī)劃表,怎樣來得到lcs呢慢宗?我們可以從后往前查找坪蚁,如果當(dāng)前位置i,j滿足xi 等于yj時(shí)镜沽,記錄當(dāng)前值到result數(shù)組里敏晤,并將i和j各減一。如果當(dāng)前位置xi和yj不相等缅茉,則查找動(dòng)態(tài)規(guī)劃表嘴脾,如果lcs(i-1,j)大于lcs(i蔬墩,j-1)時(shí)译打,i減一,反之j減一拇颅。重復(fù)以上過程直到i或j有一個(gè)為零為止扶平。下表記錄了計(jì)算lcs的過程。

image

其中紅色代表記錄當(dāng)前字符到結(jié)果數(shù)組蔬蕊,箭頭代表i和j的移動(dòng)方向结澄。

Redis的lcs命令實(shí)現(xiàn),就是通過以上算法實(shí)現(xiàn)的岸夯,具體代碼和詳細(xì)注釋如下:

image
image
image
image
image

參考資料:

https://zh.wikipedia.org/wiki/%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97

算法導(dǎo)論第三版 15.4節(jié)

https://redis.io/commands/stralgo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末麻献,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子猜扮,更是在濱河造成了極大的恐慌勉吻,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旅赢,死亡現(xiàn)場離奇詭異齿桃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)煮盼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門短纵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人僵控,你說我怎么就攤上這事香到。” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵悠就,是天一觀的道長千绪。 經(jīng)常有香客問我,道長梗脾,這世上最難降的妖魔是什么荸型? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮炸茧,結(jié)果婚禮上瑞妇,老公的妹妹穿的比我還像新娘。我一直安慰自己宇立,他們只是感情好踪宠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布自赔。 她就那樣靜靜地躺著妈嘹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绍妨。 梳的紋絲不亂的頭發(fā)上润脸,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音他去,去河邊找鬼毙驯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛灾测,可吹牛的內(nèi)容都是我干的爆价。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼媳搪,長吁一口氣:“原來是場噩夢啊……” “哼铭段!你這毒婦竟也來了屡久?” 一聲冷哼從身側(cè)響起侣背,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沮焕,失蹤者是張志新(化名)和其女友劉穎趣倾,沒想到半個(gè)月后柱搜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愕鼓,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡所宰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年悲没,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了望门。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片形娇。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筹误,靈堂內(nèi)的尸體忽然破棺而出埂软,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布勘畔,位于F島的核電站所灸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炫七。R本人自食惡果不足惜爬立,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望万哪。 院中可真熱鬧侠驯,春花似錦、人聲如沸奕巍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽的止。三九已至檩坚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诅福,已是汗流浹背匾委。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氓润,地道東北人赂乐。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像咖气,于是被迫代替她去往敵國和親挨措。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355