MQTT订阅主题匹配


10

背景

MQTT(消息队列遥测传输)是一种基于ISO标准基于发布-订阅的消息传递协议(Wikipedia)。

每条消息都有一个主题,例如以下示例:

  • myhome/groundfloor/livingroom/temperature
  • USA/California/San Francisco/Silicon Valley
  • 5ff4a2ce-e485-40f4-826c-b1a5d81be9b6/status
  • Germany/Bavaria/car/2382340923453/latitude

MQTT客户端可以使用通配符订阅消息主题:

  • 单层: +
  • 所有级别开始: #

例如,订阅myhome/groundfloor/+/temperature将产生以下结果(不符合项以粗体显示):

✅myhome /地面/客厅/温度
✅myhome /地面/厨房/温度 ❌myhome / 地面 /客厅/温度
/ 亮度
❌myhome / 第一层 /客厅/温度
车库 /地面/ 冰箱 /温度

订阅+/groundfloor/#将产生以下结果:

✅myhome /地面/客厅/温度
✅myhome /地面/厨房/亮度
✅车库/地面/冰箱/温度/更多/特定的/领域
/ myhome / 第一 /客厅/温度
❌myhome / 地下室 /角/温度

更多信息在这里

任务

实现一个接受两个字符串并返回布尔值的函数/程序。第一个字符串是主题主题,第二个是标准主题。条件主题使用上面详细介绍的订阅语法。当受试者符合标准时,该功能是正确的。

此任务的规则:

  • 主题是ASCII
  • #通配符之外没有标准字段
  • 通配符不会出现在主题中
  • 主题字段数> =条件字段数
  • 没有0个字符的字段,也没有正斜杠的开头或结尾

测试用例

条件1 =“ myhome / groundfloor / + / temperature”
标准2 =“ + / groundfloor /#”

(“ abc”,“ ab”)=>否
(“ abc”,“ abc”)=>正确
(“ abc / de”,“ abc”)=>
否(“ myhome / groundfloor / livingroom / temperature”,条件1 )=> true
(“ myhome / groundfloor / kitchen / temperature”,criteria1)=> true
(“ myhome / groundfloor / livingroom / brightness”,criteria1)=> false
(“ myhome / firstfloor / livingroom / temperature”,条件1)= > false
(“车库/地面/冰箱/温度”,criteria1)=> false
(“ myhome /地面/客厅/客厅/温度”,criteria2)=> true
(“ myhome /地面/厨房/厨房/亮度”,criteria2)=> true
(“车库/地面/冰箱/温度/更多/特定/领域”,criteria2)=> true
(“ myhome /一楼/客厅/温度/温度”,criteria2)=> false
(“ myhome /地下室/角落/温度”,criteria2)=>否
(“音乐/ kei $ ha /最新”,“ + / kei $ ha / +”)=> true


@HyperNeutrino,这是一个很好的问题。我在栅栏上。主题a/b/c将不匹配的标准a/b,所以我倾向于说没有
帕特里克

4
是否保证/,+和#永远不会出现在主题部分?
乔纳森·艾伦,

我在链接的博客中看到“另外,正斜杠本身就是一个有效的话题”,但没有提及+和#,因此我想这两个可以。
乔纳森·艾伦,

1
@JonathanAllan来自docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/…:可以在主题过滤器中使用通配符,但不得在主题名称中使用
Nick Kennedy,

2
@NickKennedy-很好的挖掘,但是我们真的不需要。
乔纳森·艾伦,

Answers:


3

果冻,20字节

ṣ€”/ZṖF”#eƊ¿œiÐḟ”+ZE

接受字符列表的单子链接[topic, pattern],分别返回10匹配或不匹配。

在线尝试!或见一个测试套件

怎么样?

ṣ€”/ZṖF”#eƊ¿œiÐḟ”+ZE - Link: list of lists of characters, [topic, pattern]
 €                   - for each:
ṣ                    -   split at occurrences of:
  ”/                 -     '/' character
    Z                - transpose (any excess of topic is kept)
           ¿         - while...
          Ɗ          - ...condition: last three links as a monad:
       ”#            -   '#' character
         e           -   exists in:
      F              -     flatten
     Ṗ               - ...do: pop the tail off
              Ðḟ     - filter discard those for which:
            œi       -   first multi-dimensional index of: ([] if not found, which is falsey)
                ”+   -     '+' character
                  Z  - transpose
                   E - all equal?

2

红宝石,65字节

正则表达式解决方案。我添加Regex.escape了一个标准名称,以防它恰好是类似com.java/string[]/\n正则表达式的东西或愚蠢的东西。

->s,c{s=~/^#{Regexp.escape(c).sub('\#','.*').gsub'\+','[^/]*'}$/}

在线尝试!

非正则表达式解决方案,77字节

使用一种很好的简单拆分,压缩和匹配技术。我首先开发了此程序,然后才意识到即使使用Regex.escaperegex解决方案,它总会变得更短。

->s,c{s.split(?/).zip(c.split ?/).all?{|i,j|i==j||'+#'[j||9]||!j&&c[-1]==?#}}

在线尝试!


.*?应该代替[^/]*
基金莫妮卡的诉讼

@NicHartley将触发a/+/d与主题a/b/c/d
值墨水的

嗯,会的。将其包装在一个原子组中可以解决此问题,但是要长两个字节。那好吧。
基金莫妮卡的诉讼


1

Python 3,72个字节

lambda a,b:bool(re.match(b.translate({43:"[^/]+",35:".+"}),a))
import re

在线尝试!

这个问题可以简单地简化为正则表达式匹配,尽管另一种更有趣的方法可能会产生更好的结果。

编辑我想出了一个不使用正则表达式的107字节解决方案。我不知道它是否可以小于72,或者我只是没有看到正确的方法。但是,仅split-zip结构似乎太大。在线尝试!


2
如果序列包含任何其他正则表达式字符,则可能会失败。即使当前的测试用例中都没有包含任何类似于正则表达式的东西,我也会注意这一点。
价值墨水

...就像f('myhome/ground$floor/livingroom/temperature', 'myhome/ground$floor/+/temperature')失败了
乔纳森·艾伦

正如Value Ink所说,+/kei$ha/+不匹配music/kei$ha/latest
查斯·布朗


1

Haskell,76 73 71 67字节

(a:b)#(c:d)=a=='+'&&b#snd(span(/='/')d)||a=='#'||a==c&&b#d
a#b=a==b

在线尝试!

编辑:-4字节感谢@cole。


1
a#b=a==b似乎少工作了几个字节,除非我丢失了某些东西
科尔

@cole:是的,可以。非常感谢!
NIMI

1

Clojure107 91 76 65 102字节

一个匿名函数,返回主题主题为真和nil假(在Clojure中有效)。

(defn ?[t c](every? #(#{"#""+"(% 0)}(% 1))(apply #(map vector % %2)(map #(re-seq #"[^/]+" %) [t c]))))

107 102工作
91 76 65所有与败字符正则表达式


...而我对您的问题的评论也很贴切
乔纳森·艾伦

@JonathanAllan,的确是,除了+和#不会出现在主题字符串中:)
Patrick

我认为这对于主题music/kei$ha/latest和条件+/kei$ha/+(应该匹配并且是有效的ASCII)失败。
Chas Brown

@ChasBrown,正确,用^代替$; 谢谢。
Patrick

1
尝试在替换之前的模式之前使用'\ Q',在模式之后使用'\
Jonathan Allan


0

Python 3,99 88字节

不使用正则表达式。在乔纳森·艾伦和查斯·布朗的协助下。

f=lambda s,p:p in(s,'#')or p[:1]in(s[:1],'+')and f(s[1:],p['+'!=p[:1]or(s[:1]in'/')*2:])

f=lambda s,p:s==p or'#'==p[0]or p[0]in(s[0]+'+')and f(s[1:],p['+'!=p[0]or(s[0]=='/')*2:])节省12。但是,这无法处理某些边缘情况,例如f('abc/ijk/x', 'abc/+/xyz')f('abc/ijk/xyz', 'abc/+/x'),可以用f=lambda s,p:s==p or'#'==p[:1]or p[:1]in(s[:1]+'+')and f(s[1:],p['+'!=p[:1]or(s[:1]=='/')*2:])
Jonathan Allan

对于f('abc','ab')和失败f('abc/de','abc')(两者都应返回False,但是存在IndexError)。
查斯·布朗

...or p[:1]in(s[:1],'+')and...修复了边缘情况@ChasBrown,我指出了2字节的开销。
乔纳森·艾伦,

无法尾随'+'(例如f('a/b', 'a/+')),但可以用0字节固定...or(s[:1]in'/')*2:])
乔纳森·艾伦

在线尝试始终建议!
Chas Brown

0

木炭,36字节

≔⪪S/θ≔⪪S/ηF∧№η#⊟η≔…θLηθF⌕Aη+§≔θι+⁼θη

在线尝试!链接是详细版本的代码。匹配的输出-(炭笔的隐式输出true),没有匹配则为零。说明:

≔⪪S/θ

在上分割主题/

≔⪪S/η

/s 上拆分条件。

F∧№η#⊟η≔…θLηθ

如果条件包含(即以)结尾,#则将其删除并将主题修剪为新的条件长度。

F⌕Aη+§≔θι+

如果条件包含+,则将主题中的该元素替换为+

⁼θη

将主题与条件进行比较,然后隐式打印结果。


0

视网膜0.8.2,42字节

%`$
/
+`^([^/]+/)(.*¶)(\1|\+/)
$2
^¶$|¶#/$

在线尝试!说明:

%`$
/

/两行都加后缀a 。

+`^([^/]+/)(.*¶)(\1|\+/)
$2

重复删除主题和条件相等的第一个元素,或者条件元素为(happy)+

^¶$|¶#/$

如果条件仅仅是#(与/之前添加的)条件,则条件匹配,否则,此时主题和条件都应为空。




0

JavaScript,69 66字节

t=>s=>new RegExp(s.split`+`.join`[^/]+`.split`#`.join`.+`).test(t)

在线尝试!


这对于主题music/kei$ha/latest和条件+/kei$ha/+(应该匹配并且是有效的ASCII)失败。
Chas Brown

0

Python 3中149个 148字节的

def f(t,c):t,c=t.split('/'),c.split('/');return all([c[i]=='+'or c[i]==t[i]or c[i]=='#'for i in range(len(c))])and not(len(c)!=len(t)and c[-1]!='#')

在线尝试!


0

05AB1E,21 字节

ε'/¡}ζʒ'+å≠}˜'#¡н2ôøË

按顺序输入清单[criteria, topic]

在线尝试验证所有测试用例

说明:

ε                      # Map both strings in the implicit input-list to:
 '/¡                  '#  Split the string on "/"
                       #   i.e. ["+/+/A/B/#","z/y/A/B/x/w/v/u"]
                       #    → [["+","+","A","B","#"],["z","y","A","B","x","w","v","u"]]
                     # After the map: zip/transpose the two string-lists,
                       # with space as (default) filler
                       #  → [["+","z"],["+","y"],["A","A"],["B","B"],["#","x"],[" ","w"],
                       #     [" ","v"],[" ","u"]]
      ʒ    }           # Filter each pair by:
       '+å≠           '#  Only keep those which do NOT contain a "+"
                       #   → [["A","A"],["B","B"],["#","x"],[" ","w"],[" ","v"],[" ","u"]]
            ˜          # Flatten the filtered list
                       #  → ["A","A","B","B","#","x"," ","w"," ","v"," ","u"]
             '#¡      '# Split the list by "#"
                       #  → [["A","A","B","B"],["x"," ","w"," ","v"," ","u"]]
                н      # Only keep the first part
                       #  → ["A","A","B","B"]
                 2ô    # Split this back into pairs of two
                       #  → [["A","A"],["B","B"]]
                   ø   # Zip/transpose them back
                       #  → [["A","B"],["A","B"]]
                    Ë  # And check if both inner lists are equal
                       #  → 1 (truthy)
                       # (after which the result is output implicitly)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.