Как я могу отслеживать положение символов после удаления элементов из строки? - PullRequest
3 голосов
/ 21 февраля 2010

Допустим, у меня есть следующая строка:

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

Теперь я написал регулярное выражение для удаления определенных элементов из приведенной выше строки, в этом примере всех пробелов, всех периодов и всех запятых.

У меня осталось следующая преобразованная строка:

 "mydogjumpsandheisaverygooddog"

Теперь я хочу построить k-грамм этой строки. Допустим, я должен был взять 5 граммов вышеуказанной строки, это выглядело бы как:

  mydog ydogj dogju ogjum gjump jumps umpsa ...

У меня проблема в том, что для каждого килограмма я хочу отслеживать исходную позицию символа в первом исходном тексте, который я перечислил.

Таким образом, «mydog» будет иметь начальную позицию «0» и конечную позицию «11». Тем не менее, у меня нет сопоставления между исходным текстом и измененным текстом. Итак, я понятия не имею, где конкретная k-грамма начинается и заканчивается относительно исходного неизмененного текста. Для моей программы важно отслеживать.

Я создаю список k-грамм вроде этого:

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

, где start и end - это позиции в исходном тексте (вверху), а текст соответствует тексту килограмма после изменений.

Может кто-нибудь указать мне правильное направление для лучшего решения этой проблемы?

Ответы [ 4 ]

5 голосов
/ 21 февраля 2010

Вот как бы я решил эту проблему в 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) или, что еще хуже, malloc и free, такое решение было бы очень утомительным.

5 голосов
/ 21 февраля 2010

Не используйте регулярное выражение API замены для выполнения замены. Используйте регулярные выражения, чтобы найти места, которые вы хотите изменить, сделайте мод самостоятельно и сохраняйте смещение. Одна из форм, которую я использовал, это массив целых чисел размером с исходную строку, в котором хранятся значения «n символов удалены», но есть множество других возможностей.

Базовая структура данных здесь представляет собой массив пар. Каждая пара содержит смещение и коррекцию. В зависимости от временного / пространственного компромисса, вы можете предпочесть распространять информацию по структуре данных, равной исходной строке.

2 голосов
/ 21 февраля 2010

Решение 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>. Не уверен, что Норман говорит о языках с 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();
}

Функции add и flush немного утомительны, они должны объединить 5 пар в структуру KGram, а затем выполнить *dest++ = kgram. Пользователь может передать, например, pushback_iterator через vector<KGram> в качестве выходного итератора. Кстати, '5' и 'char' также могут быть параметрами шаблона.

1 голос
/ 21 февраля 2010

Это можно сделать за один проход без необходимости создавать промежуточные пары символ-позиция:

(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)))))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...