【问题标题】:How can I keep track of character positions after I remove elements from a string?从字符串中删除元素后,如何跟踪字符位置?
【发布时间】:2010-02-20 22:01:26
【问题描述】:

假设我有以下字符串:

 "my ., .,dog. .jumps. , .and..he. .,is., .a. very .,good, .dog"  
  1234567890123456789012345678901234567890123456789012345678901 <-- char pos

现在,我编写了一个正则表达式来从上面的字符串中删除某些元素,在这个例子中,所有的空格、所有的句点和所有的逗号。

我留下了以下转换后的字符串:

 "mydogjumpsandheisaverygooddog"

现在,我想构造这个字符串的 k-gram。假设我要取 5 克上述字符串,它看起来像:

  mydog ydogj dogju ogjum gjump jumps umpsa ...

我遇到的问题是,对于每个 k-gram,我想在我列出的第一个源文本中跟踪其 原始字符位置

因此,“mydog”的起始位置为“0”,结束位置为“11”。但是,我在源文本和修改后的文本之间没有映射。所以,我不知道一个特定的 k-gram 相对于原始的、未修改的文本在哪里开始和结束。这对我的程序跟踪很重要。

我正在创建一个这样的 k-gram 列表:

public class Kgram
{
    public int start;  
    public int end;  
    public int text;  
}

其中startend 是源文本中的位置(顶部),文本是修改后的k-gram 文本的位置。

谁能指出我正确的方向以找到解决此问题的最佳方法?

【问题讨论】:

    标签: algorithm language-agnostic string


    【解决方案1】:

    请勿使用正则表达式“替换”API 进行替换。仅使用正则表达式来查找您要修改的地方,自己做 mod,并维护偏移映射。我使用的一种形式是与原始字符串一样大的整数数组,在此处存储“删除的 n 个字符”值,但还有许多其他可能性。

    这里的基本数据结构是一对数组。每对包含一个偏移量和一个校正量。根据时间/空间的权衡,您可能更愿意将信息分散到与原始字符串一样大的数据结构中。

    【讨论】:

    • 一个与原始源文本一样大的 int 数组,还是与修改后的源文本一样大?如果我有一个与源文本一样大的整数数组,为什么这里删除的 n 个字符的数量会大于 1?因为每个字符都有一个 int 位置?还是我误解了?你能详细说明一下吗?
    【解决方案2】:

    这是我在 Haskell 中解决这个问题的方法:

    kgramify k string =
      let charsWithPos = zip string [1..]  -- attach original position to each char
          goodCWP      = filter (not o isWhitePeriodOrComma o fst) charsWithPos -- drop nasty chars
          groups       = takeEveryK k goodCWP -- clump remaining chars in groups of size k
          posnOfGroup g = (snd (head g), map fst g) -- position of first char with group
      in  map posnOfGroup groups
    

    非正式英语:

    1. 用位置标记每个字符
    2. 过滤掉不感兴趣的(字符、位置)对
    3. 取出剩余的对列表并将它们分组到长度为k的列表列表中
    4. 对于每个内部列表,获取第一个字符的位置,并将其与所有字符的列表配对(位置被删除)

    在任何函数式语言中,如 Clean、Haskell、ML 或 Scheme,这种事情都非常容易。在具有显式内存分配的语言 (new) 甚至更糟的情况下,mallocfree,这样的解决方案将非常乏味。

    【讨论】:

    • 见鬼,如果您知道 J 或 APL,那么使用需要使用空格键的语言进行编程是乏味且冗长的 ;-)
    • @Steve:我很想看到 APL 版本——肯定会解决这个问题。我想我已经超过 25 年没有写过 APL 了……
    • 不幸的是,我从来没有找到正确学习 APL 所需的时间或三英尺宽的键盘。
    【解决方案3】:

    一个 C 解决方案,以表明正如 Norman Ramsey 所说,它非常乏味。它将过滤器作为带有上下文的回调,只是为了踢球,但在您的情况下,您可以将 0 作为过滤器数据和 not_wspc 作为过滤器:

    int not_wspc(void *, char c) {
        if isspace((unsigned char)c) return 0;
        if ((c == '.') || (c == ',')) return 0;
        return 1;
    }
    
    typedef struct {
        char c;
        int pos;
    } charwithpos;
    
    KGram *foo(const char *input, int (*filter)(void *,char), void *filterdata) {
        size_t len = strlen(input);
        charwithpos *filtered = malloc(len * sizeof(*filtered));
        assert(filtered);
    
        // combine Norman's zip and filter steps
        charwithpos *current = filtered
        for (size_t i = 0; i < len; ++i) {
            if (filter(filterdata, input[i])) {
                current->c = input[i];
                current->pos = i;
                ++current;
            }
        }
        size_t shortlen = (current - filtered);
    
        // wouldn't normally recommend returning malloced data, but
        // illustrates the point.
        KGram *result = malloc((shortlen / 5 + 1) * sizeof(*result));
        assert(result);
    
        // take each 5 step
        KGram *currentgram = result;
        current = filtered;
        for (size_t i = 0; i < shortlen; ++i) {
            currentgram->text[i%5] = current->c;
            if ((i % 5) == 0) {
                currentgram->start = current->pos;
            } else if ((i % 5) == 4) {
                currentgram->end = current->pos;
                ++currentgram;
            }
            ++current;
        }
        if (shortlen % 5) != 0 {
            currentgram->end = filtered[shortlen-1].pos;
            currentgram->text[shortlen%5] = 0;
        }
    
        free(filtered);
        return(result);
    }
    

    或者类似的东西,我实际上无法编译和测试它。显然,这有一个明显的弱点,filtered 一次只能看到一个字符,这意味着它不能应用回溯算法。您可以通过将整个字符串传递给过滤器来绕过它,这样如果有必要,它可以在第一次调用时做很多工作,并存储结果以在所有其余调用中返回。但是,如果您需要将类似正则表达式的逻辑应用于任意类型,那么 C 可能不适合使用。

    这是 C++ 解决方案的开端,甚至没有使用 &lt;functional&gt;。不确定 Norman 对 new 的语言有什么看法:仅仅因为该语言拥有它并不意味着您必须使用它 ;-)

    template <typename OutputIterator>
    struct KGramOutput {
        OutputIterator dest;
        KGram kgram;
        KGramOutput(OutputIterator dest) : dest(dest) {}
        void add(char, size_t);
        void flush(void);
    };
    
    template <typename InputIterator, typename OutputIterator, typename Filter>
    void foo(InputIterator first, InputIterator last, OutputIterator dest, Filter filter) {
        size_t i = 0;
        KGramOutput<OutputIterator> kgram(dest);
        while (first != last) {
            if (filter(*first)) kgram.add(*first, i);
            ++first;
            ++i;
        }
        kgram.flush();
    }
    

    addflush 函数有点乏味,它们必须将 5 对绑定到一个 KGram 结构中,然后执行 *dest++ = kgram。例如,用户可以通过 vector&lt;KGram&gt; 传递 pushback_iterator 作为输出迭代器。顺便说一句,“5”和“char”也可以是模板参数。

    【讨论】:

      【解决方案4】:

      这可以一次性完成,无需构造中间字符-位置对:

      (defclass k-gram ()
        ((start :reader start :initarg :start)
         (end :accessor end)
         (text :accessor text)))
      
      (defmethod initialize-instance :after ((k-gram k-gram) &rest initargs &key k)
        (declare (ignorable initargs))
        (setf (slot-value k-gram 'text) (make-array k :element-type 'character)))
      
      (defun k-gramify (string k ignore-string)
        "Builds the list of complete k-grams with positions from the original
         text, but with all characters in ignore-string ignored."
        (loop
           for character across string
           for position upfrom 0
           with k-grams = ()
           do (unless (find character ignore-string)
                (push (make-instance 'k-gram :k k :start position) k-grams)
                (loop
                   for k-gram in k-grams
                   for i upfrom 0 below k
                   do (setf (aref (text k-gram) i) character
                            (end k-gram) (1+ position))))
           finally (return (nreverse (nthcdr (- k 1) k-grams)))))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-11-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-09
        • 1970-01-01
        • 1970-01-01
        • 2017-12-24
        相关资源
        最近更新 更多