从字符串中删除元素后,如何跟踪字符位置?

时间:2010-02-20 22:01:27

标签: algorithm language-agnostic string

我们说我有以下字符串:

 "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文本。

有人能指出我正确的方向来解决这个问题吗?

4 个答案:

答案 0 :(得分:5)

请勿使用正则表达式'replace'API进行替换。仅使用regexp查找要修改的位置,自己执行mod,并维护偏移映射。我使用的一种形式是一个与原始字符串一样大的int数组,在这里存储'n chars deleted'值,但还有许多其他的可能性。

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

答案 1 :(得分:5)

以下是我在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. 的列表
  5. 对于每个内部列表,取第一个字符的位置,并将其与所有字符的列表配对(删除的位置)
  6. 在任何功能语言中,如Clean,Haskell,ML或Scheme,这种事情都很容易。在具有显式内存分配(new)或更差,mallocfree的语言中,这样的解决方案将非常繁琐。

答案 2 :(得分:2)

一个C解决方案,表明正如诺曼拉姆齐所说,这是非常繁琐的。它将过滤器作为带有上下文的回调,仅用于踢,但在您的情况下,您可以将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 ++解决方案的开端,甚至没有使用<functional>。不确定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。用户可以通过pushback_iterator作为输出迭代器传递vector<KGram>。顺便说一下'5'和'char'也可以是模板参数。

答案 3 :(得分:1)

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

(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)))))