【问题标题】:How to match a string of a certain length with a regex如何用正则表达式匹配一定长度的字符串
【发布时间】:2009-06-02 05:55:06
【问题描述】:

对于我的一个项目,我正在尝试实现 BitTorrent 协议的一小部分,可以在 here 找到。具体来说,我想使用它的“Bencoding”部分,这是一种安全编码数据以通过套接字传输的方法。格式如下:

8:a string => "a string"
i1234e => 1234
l1:a1:be => ['a', 'b']
d1:a1:b3:one3:twoe => {'a':'b', 'one':two}

编码部分很简单,但解码变得相当麻烦。例如,如果我有一个字符串列表,我无法将它们分成单独的字符串。我尝试了几种不同的解决方案,包括PyParsing 和自定义令牌解析器。我目前正在尝试使用正则表达式,它似乎进展顺利,但我仍然挂断字符串问题。我目前的正则表达式是:

(?P<length>\d+):(?P<contents>.{\1})

但是,我似乎无法将第一组用作第二组的长度。有什么好的方法可以做到这一点吗?还是我在处理这一切时都错了,而答案就在我面前?

【问题讨论】:

  • 不确定答案,但原始的 Bit Torrent 客户端是开源的。它甚至在 Python 中!所以你可以试试看:bittorrent.cvs.sourceforge.net/viewvc/bittorrent/BitTorrent
  • "现在你有两个问题!" ::rimshot::
  • 感谢您的链接,MatrixFrog。我想我将只导入该文件,并在我的程序中使用原始实现。

标签: python regex


【解决方案1】:

您为此使用的任何解析器都需要是有状态的(即记住内容),而正则表达式总的来说是无状态的。它们不适合这项工作。

如果这些是您唯一需要担心的数据类型,我想我会为每种数据类型编写自定义解析器,在读取第一个字符后将控制权传递给相应的解析器。

其实我现在想实现一个,但已经晚了。

好吧,我决定写一个实现:

from StringIO import StringIO
import string

inputs = ["10:a stringly",
         "i1234e" ,
         "l1:a1:be",
         "d1:a1:b3:one3:twoe"]

# Constants
DICT_TYPE = 'd'
LIST_TYPE = 'l'
INT_TYPE  = 'i'
TOKEN_EOF = ''
TOKEN_END = 'e'
COLON     = ':'


class BadTypeIndicatorException(Exception):pass


def read_int(stream):

   s = ""

   while True:
      ch = stream.read(1)
      if ch not in [TOKEN_EOF, TOKEN_END, COLON]:
         s += ch
      else:
         break

   return s


def tokenize(stream):

   s = ""

   while True:

      ch = stream.read(1)

      if ch == TOKEN_END or ch == TOKEN_EOF:
         return 

      if ch == COLON:
         length = int(s)
         yield stream.read(length)
         s = ""

      else:
         s += ch


def parse(stream):

   TYPE = stream.read(1)

   if TYPE in string.digits:
      length = int( TYPE + read_int(stream) )
      return stream.read(length)

   elif TYPE is INT_TYPE: 
      return int( read_int(stream) )

   elif TYPE is LIST_TYPE: 
      return list(tokenize(stream))

   elif TYPE is DICT_TYPE:
      tokens = list(tokenize(stream))
      return dict(zip(tokens[0::2], tokens[1::2]))

   else: 
      raise BadTypeIndicatorException



for input in inputs:
   stream = StringIO(input)
   print parse(stream)

【讨论】:

  • 正则表达式是有状态的。正则表达式和不同解析器之间的唯一区别是正则表达式只有固定的、有限的状态。事实上,这是定义常规语言的一种常用方法:任何可以使用固定的、有限数量的状态进行解析的语言。
  • @Dietrich - 我明白你在说什么,但实际上我们谈论的是“状态”这个词的两种完全不同的含义。现代编程中的这个词最常使用,因为我使用它 - 一些过程会记住操作之间的事情。在正则表达式中,我们可以称其为上下文,而正则表达式大体上被设计为与上下文无关。
  • 我会选择这个作为答案,但我决定不重新发明轮子,所以我使用了上面 MatrixFrog 链接到的 BitTorrent 实现。否则,我可能会使用你的实现,或者基于它的东西。
【解决方案2】:

如果你解析字符串两次,你就可以做到。应用第一个正则表达式来获取长度。连接第二个正则表达式中的长度以形成有效的表达式。

不确定如何在 python 中完成,但 C# 中的示例是:

string regex = "^[A-Za-z0-9_]{1," + length + "}$"

匹配长度为 1 的字符数,可以是字母数字或 _,其中长度是从仅检索长度的先前正则表达式确定的。

希望这会有所帮助:)

【讨论】:

    【解决方案3】:

    您需要分两步完成此操作。对于这样简单的解析问题,正则表达式实际上有点矫枉过正。这是我的做法:

    def read_string(stream):
        pos = stream.index(':')
        length = int(stream[0:pos])
        string = stream[pos+1:pos+1+length]
        return string, stream[pos+1+length:]
    

    这是一种函数式的解析方式,它返回解析的值和流的其余部分。

    对于列表,也许:

    def read_list(stream):
        stream = stream[1:]
        result = []
        while stream[0] != 'e':
            obj, stream = read_object(stream)
            result.append(obj)
        stream = stream[1:]
        return result
    

    然后您将定义一个 read_object 来检查流的第一个字符并适当地分派。

    【讨论】:

    • 任意长度的流上的切片语法可能不是一个好主意。
    【解决方案4】:

    你使用了错误的工具来完成这项工作...这需要某种状态保持,一般来说,正则表达式是无状态的。


    可以在here 找到我在 PERL 中执行的 bdecoding(和 bencoding)的示例实现。

    对该函数如何工作的解释(因为我从来没有评论过它[哎呀]):

    基本上你需要做的是设置一个递归函数。这个函数接受一个字符串引用(所以它可以被修改)并返回“某物”(这意味着它可以是一个数组、一个哈希表、一个 int 或一个字符串)。

    函数本身只是检查字符串中的第一个字符,并据此决定要做什么:

    • 如果是i,则解析出i和第一个e之间的所有文本,并尝试根据允许的规则。
    • 如果是数字,则读取直到 : 之前的所有数字,然后从字符串中读取那么多字符。

    列表和字典是开始变得有趣的地方...如果有一个 ld 作为第一个字符,那么你需要去掉 @ 987654323@/d,然后将当前字符串传回函数,这样就可以开始解析列表或字典中的元素了。然后只需将返回的值存储在适当结构中的适当位置,直到您点击 e,然后返回您剩下的结构。

    请记住,我实现的功能是破坏性的。当函数返回时传入的字符串是空的,因为它是通过引用传递的,或者更准确地说,它将没有任何它解析和返回的内容(这就是它可以递归使用的原因:任何它不处理的东西都会留下未触及)。不过,在大多数初始调用的情况下,这应该会处理所有事情,除非您一直在做一些奇怪的事情,所以上述情况成立。

    【讨论】:

    • Python 字符串是不可变的,所以你必须做一些不同的事情。
    • 也许传递一个偏移变量或什么?或者循环执行。不过,我的大脑大部分时间都在递归地工作。
    【解决方案5】:

    伪代码,没有语法检查:

    define read-integer (stream):
        let number 0, sign 1:
            if string-equal ('-', (c <- read-char (stream))):
                sign <- -1
              else:
                number <- parse-integer (c)
            while number? (c <- read-char (stream)):
                number <- (number * 10) + parse-integer (c)
            return sign * number
    
    define bdecode-string (stream):
        let count read-integer (stream):
            return read-n-chars (stream, count)
    
    define bdecode-integer (stream):
        ignore read-char (stream)
        return read-integer (stream)
    
    define bdecode-list (stream):
        ignore read-char (stream)
        let list []:
            while not string-equal ('e', peek-char (stream)):
                append (list, bdecode (stream))
            return list
    
    define bdecode-dictionary (stream):
        let list bdecode-list stream:
            return dictionarify (list)
    
    define bdecode (stream):
        case peek-char (stream):
            number? => bdecode-string (stream)
            'i' => bdecode-integer (stream)
            'l' => bdecode-list (stream)
            'd' => bdecode-dictionary (stream)
    

    【讨论】:

    • 我不知道为什么有人不赞成这个,但我只是检查了原始的 bittorrent 是如何做到的(感谢 MatrixFrog 的链接),几乎就是这样,加上错误检查,它以不同的方式处理流。
    猜你喜欢
    • 2019-01-04
    • 2015-04-02
    • 2013-11-04
    • 2018-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 2012-02-21
    相关资源
    最近更新 更多