Определение «владельца» текста, редактируемого несколькими пользователями - PullRequest
21 голосов
/ 08 января 2009

Возможно, вы заметили, что теперь мы показываем сводку по редактированию в вики-сообщениях сообщества:

Сообщество вики
220 ревизий, 48 пользователей

Я также хотел бы показать пользователю, который «владеет большинством» окончательного содержимого, отображаемого на странице, в процентах от оставшегося текста:

Сообщество вики
220 ревизий, 48 пользователей
кроноз 87%

Да, могут быть верхние (n) "владельцы", но сейчас я хочу топ 1.

Предположим, у вас есть эта структура данных, список пар пользователь / текст, упорядоченных в хронологическом порядке ко времени публикации:

User Id     Post-Text
-------     ---------
12          The quick brown fox jumps over the lazy dog.
27          The quick brown fox jumps, sometimes.
30          I always see the speedy brown fox jumping over the lazy dog.

Кто из этих пользователей наиболее "владеет" окончательным текстом?

Я ищу разумный алгоритм - он может быть приблизительным, он не должен быть идеальным - чтобы определить владельца. Идеально выражается в процентах.

Обратите внимание, что мы должны учитывать правки, удаления и вставки, поэтому окончательный результат кажется разумным и правильным. В качестве тестового корпуса вы можете использовать любую публикацию stackoverflow с достойной историей ревизий (не просто повторной маркировкой, но частыми изменениями тела сообщения). Вот хороший, с 15 ревизиями от 14 разных авторов. Кто такой "владелец"?

https://stackoverflow.com/revisions/327973/list

Нажмите «просмотреть исходный код», чтобы получить необработанный текст каждой ревизии.

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

Приветствуются решения на любом языке , но я предпочитаю решения, которые

  1. Довольно легко перевести на c #.
  2. Без зависимостей.
  3. Поместите простоту перед эффективностью.

Чрезвычайно редко для публикации в SO более 25 ревизий. Но это должно «чувствовать» точно, поэтому, если вы оценили внесенные изменения, вы согласитесь с окончательным решением. Я рекомендую вам проверить свой алгоритм на постах переполнения стека с историями изменений и посмотреть, согласны ли вы с окончательным выводом.


Я развернул следующую аппроксимацию, которую вы можете увидеть в действии для каждой новой сохраненной ревизии в сообщениях сообщества Wiki

  • делать разность на основе строки каждой ревизии, где изменяется основной текст
  • сумма строк вставки и удаления для каждой ревизии как "editcount"
  • каждый идентификатор пользователя получает сумму "editcount", которую они внесли
  • автор первой ревизии получает 2x * "editcount" в качестве начального балла в качестве основного авторского бонуса
  • для определения окончательного процента владения: общее количество отредактированных строк каждого пользователя, поделенное на общее количество отредактированных строк во всех ревизиях

(Существуют также некоторые защитные условия для простых простых условий, таких как 1 ревизия, только 1 автор и т. Д. При использовании различий на основе строк достаточно быстро выполнить пересчет для всех ревизий; в типичном случае, скажем, 10 ревизий это ~ 50 мс .)

Это неплохо работает в моем тестировании. Это немного ломается, когда у вас есть небольшие 1 или 2 посты, которые редактируют несколько человек, но я думаю, что это неизбежно. Принятие ответа Джоэла Нили как наиболее близкого по духу к тому, с чем я пошел, и отказался от всего остального, что казалось работоспособным.

Ответы [ 15 ]

25 голосов
/ 08 января 2009

Я думаю, что идея в корне ошибочна.

Если кто-то напишет блестящий анализ с ужасным написанием и неясными примерами, и я скопирую его, отредактирую, я создал 60% работы? Явно нет; результат является производной, где большая часть значения исходит от первоначального плаката. Полезная мера невозможна на основе количества символов или слов, но требует тщательного семантического анализа на уровне AI.

Помимо этого, поиск кредита, основанный на «праве собственности» на статьи, вероятно, был бы совершенно бесполезным и анти-вики. Например, в Википедии люди, которые ведут себя так, как будто они владеют статьями, являются одним из самых разрушительных факторов.

20 голосов
/ 08 января 2009

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

  1. A, оригинальному постеру принадлежит 100% поста.

  2. Когда B, второй постер, вносит изменения, например, такие как 90% текста без изменений, владение A: 90%, B: 10%.

  3. Теперь C, третье лицо, меняет 50% текста. (A: 45%, B: 5%, C: 50%)

    Другими словами, когда постер вносит изменения, в которых x% изменяется, а y = (100-x)% не изменяется, тогда этот постер теперь владеет x% текста, а все предыдущие владения умножаются на y%.

    Чтобы было интересно, предположим ...

  4. A редактирует 20%. Тогда А владеет «новыми» 20%, а остаточные владения теперь умножаются на 80%, оставляя (А: 36%, В: 4%, С: 40%). Таким образом, «чистое» владение (A: 56%, B: 4%, C: 40%).

Применение этого к вашему образцу (327973) со всем округленным до ближайшего процента:

Версия 0: оригинальный пост.

  • Пол Ойстер: 100%

Версия 1. Ваш текущий инструмент сравнения показывает чистое добавление текста, поэтому все эти символы принадлежат второму постеру.

  • Пол Ойстер: 91%
  • один: 9%

Версия 2: разница показывает замену слова. Новое слово принадлежит третьему постеру, а оставшийся текст принадлежит предыдущим постерам.

  • Пол Ойстер: 90%
  • Один: 9%
  • Blogbeard: 1%

Версия 3: редактирование только по тегам. Поскольку ваш вопрос касался текста, я игнорирую теги.

  • Пол Ойстер: 90%
  • Один: 9%
  • Blogbeard: 1%

Версия 4: Добавление текста.

  • Пол Ойстер: 45%
  • Один: 4%
  • Blogbeard: 1%
  • Марк Харрисон: 50%

Надеюсь, этого достаточно, чтобы придать смысл этому предложению. У него есть пара ограничений, но я подвожу их к вашему утверждению, что приближение приемлемо. ; -)

  1. Он грубо распределяет эффект изменений по всем прежним владельцам. Если A публикует сообщения, B выполняет чистое добавление, а C редактирует половину того, что добавил B, этот упрощенный подход просто применяет владение C ко всей записи, не пытаясь определить, какое предыдущее владение было изменено больше всего.

  2. Он учитывает добавления или изменения, но не дает права собственности на удаление, поскольку средство удаления добавляет 0% к оставшемуся тексту. Вы можете рассматривать это как ошибку или особенность. Я выбрал дверь № 2.

Обновление: Немного больше о проблеме № 1 выше. Я считаю, что для полного отслеживания права собственности на часть редактируемого поста потребуется одна из двух вещей (поле для веб-страницы недостаточно велико для формального доказательства; -):

  • Изменение способа хранения текста для отражения владения отдельными частями текста (например, А владеет словами 1-47, В владеет словами 48-59, А владеет словами 60-94, ...), применяя подход «сколько остается» в моем предложении для каждой порции и обновление данных о долевом владении.

  • С учетом всех версий от первой до текущей (фактически, пересчет данных о долевом владении на лету).

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

5 голосов
/ 08 января 2009

Вот как я вижу право собственности на пример сообщения (обратите внимание, я вообще забыл добавить теги к тексту, поэтому простые изменения тегов здесь не учитываются, но могут быть легко добавлены):

Хлоп по лбу : Чувак, я использовал номера ревизий вместо номеров пользователей. Результаты переделаны ниже:

User 38193 owns 42% (922 / 2171) of the final post
User 2635 owns 28% (625 / 2171) of the final post
User 116 owns 24% (529 / 2171) of the final post
User 745 owns 3% (76 / 2171) of the final post
User 13005 owns 0% (11 / 2171) of the final post
User 18941 owns 0% (5 / 2171) of the final post
User 8562 owns 0% (3 / 2171) of the final post

53 ms

Таким образом, согласно моему алгоритму, пользователь 38193 (@ Пол Ойстер ) владеет 42% поста, тогда как пост 2635 (@ Simucal ) имел 28%, а пользователь 116 ( @ Марк Харрисон ) имеет 24%, остальные незначительны.

Из ревизий мы можем видеть, что Павел, который является первоначальным автором, по-прежнему владеет большей частью вопроса, а Симукал и Марк приходят с хорошим номером. 2 и 3. Это соответствует редакции №. 1 (оригинальный пост), № 14, который является большим редактором Simucal, и который выглядит так, как будто он довольно неплохо показывает недостаток в моем алгоритме (см. Ниже), и nr. 5, где Марк добавил скрипт bash.

Так как я пришел к этому ответу? Ну, у алгоритма есть недостаток, но я вернусь к нему, но вот как это происходит:

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

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

Поскольку алгоритм diff работает с байтами, незначительные текстовые изменения, такие как добавление пропущенных знаков препинания или букв, должны оказывать незначительное влияние на владение, и почти весь исходный текст по-прежнему следует отнести к первоначальному автору. Однако в некоторых случаях он будет использовать операцию «добавленные данные», даже если был добавлен только один байт из-за внутренней оптимизации. Алгоритм и его реализация изначально создавались для обработки различий в файлах и создания наименьших возможных исправлений между версиями файлов, а иногда оптимизируют незначительные шаги в пользу объединения его в одноуровневую операцию, если это уменьшит размер файла.

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

Исходный код для этой реализации можно найти здесь , для Visual Studio 2008. Обратите внимание, что решение не делает ничего похожего на скриншот или что-то в этом роде, а содержимое публикации жестко закодировано в исходном коде в Класс TestData, правильно экранированный для кавычек и т. Д. Чтобы заменить текст, вам нужно изменить этот файл или реализовать способ чтения содержимого извне программы.

Во всяком случае, вот алгоритм более подробно.


  1. Создайте массив целых чисел, если исходная ревизия (на самом деле, я закодировал его в байты через UTF8, и это длина, которую я использовал). Заполните этот массив идентификатором пользователя первоначального автора , теперь он владеет 100% ревизии, каждый символ / байт его
  2. Сравните первую ревизию со следующей ревизией. Это сравнение даст список операций. Эти операции можно использовать для получения исходной ревизии, применения к ней операций, и вы получите следующую ревизию (подробнее об этом ниже).
  3. Представьте, что исходный пост - это массив подготовленных вами идентификаторов пользователей (который в настоящее время содержит только набор значений, равных идентификатору первого автора), и примените к нему операции. Это создаст новый список идентификаторов, некоторые из них будут первоначальным автором, некоторые будут вторым автором.
  4. Сохраняйте вторую ревизию и этот новый список идентификаторов пользователей и выполняйте шаги 2 + 3 между этими данными, следующей ревизией, следующей ревизией и т. Д., Пока не дойдете до дна

На данный момент у вас есть список идентификаторов пользователей, который сообщает вам, какой пользователь добавил каждый символ.

Операции из сравнения - одна из двух:

  • Вставить новые байты
  • Скопировать несколько байтов

Как используется результат сравнения, вы берете старое содержимое (первое сообщение) и применяете к нему операции, а затем производите следующую ревизию. Это в основном diff.

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

Позвольте мне привести пример:

Оригинальное сообщение:

This is the first post

Следующее сообщение:

This is the next post, it is based on the first post.

Список операций будет таким:

  1. Копировать 12 символов 'Это'
  2. Вставить «следующий»
  3. Скопируйте 5 символов 'post'
  4. Вставьте 17 символов ', он основан на'
  5. Скопируйте 14 символов 'первый пост'
  6. Вставить 1 символ '.'

Если бы я вместо этого работал с идентификаторами пользователя, я бы сначала получил этот массив:

0000000000000000000000
This is the first post

Теперь я применяю операции, и вместо каждой вставки я вставляю 1:

00000000000011110000011111111111111111000000000000001
This is the next post, it is based on the first post.

Теперь я просто считаю, сколько у меня 0 и 1:

  1. 0: 31
  2. 1: 22 * ​​1100 *

Пользователь 0 владеет 31 / (31 + 22) постом, а пользователь 1 владеет 22 / (31 + 22) постом.

Переводится в процентах: пользователю 0 принадлежит 58%, пользователю 1 принадлежит 42%.

Теперь проблема с этим алгоритмом заключается в откатах и ​​добавлении обратно потерянного / удаленного контента.

Например, если у вас есть пользователи A, B и C, и пользователь A публикует что-то, что действительно отключает пользователя B, пользователь B входит и удаляет все, и добавляет просто «ЭТО СЛУЧАЙ». Когда пользователь C видит это, он редактирует сообщение и добавляет обратно все, что опубликовал A, возможно, с исправлениями. Пользователь C теперь владеет 100% поста.

Я не знаю, как решить вышеуказанную проблему.

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


Применительно к примеру «Быстрая коричневая лиса», перенумеровав пользователей до 1-3, я получаю это:

User 3 owns 75% (45 / 60) of the final post
User 1 owns 25% (15 / 60) of the final post

Обратите внимание, что пользователь 2, который добавил только часть «иногда», которая впоследствии была удалена, удаляется из списка.

Идентификаторы сообщений:

The quick brown fox jumps over the lazy dog.
11111111111111111111111111111111111111111111 (44 = 100%)

The quick brown fox jumps, sometimes.
1111111111111111111111111222222222222 (25 / 12 ~ 68% / 32%)

I always see the speedy brown fox jumping over the lazy dog.
333333333333333333333331111111111111113333333333333333333333 (45 / 15 = 75% / 25%)

Вещи, с которыми справится алгоритм:

Если я что-то копирую при создании нового поста, алгоритм должным образом приписывает скопированные элементы, даже если теперь я копирую части, которые я также добавил. Пример: * * тысяча сто двадцать семь

This is the first post, which is cool
1111111111111111111111111111111111111

This is the second post, which is the second post.
11111111111122222211111111111111112222222222111111
                               ^-- copied ------^

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

Кроме того, если я просто перенесу материал с начального поста, только второстепенные кусочки, например, несколько процентов, будут моими. Поскольку в основе лежит алгоритм diff-алгоритма, иногда стоимость вывода операции копирования всего за 1 или пару байтов перевешивает стоимость простой вставки необработанного кода, поэтому иногда он будет обрабатывать короткую последовательность байтов как вставленную, хотя они могли быть скопированы из исходного поста.

4 голосов
/ 08 января 2009

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

Вот код C # для расчета расстояний.

4 голосов
/ 08 января 2009

Если я правильно понимаю ваш вопрос, похоже, вы пытаетесь сделать то, что IBM сделала некоторое время назад с исследовательским проектом в Википедии. А именно, видеть, кто вносит изменения в текст, где наиболее приемлемы для других пользователей и как общий текст меняется с течением времени. Название проекта было поток истории и поток истории - как он работает дает довольно хороший обзор того, как работал их алгоритм.

3 голосов
/ 08 января 2009

Никто не владеет этим. Присвоение прав собственности нарушает дух "вики сообщества" и может привести к контрпродуктивным войнам редактирования.

3 голосов
/ 08 января 2009

Это сработает, если вы захотите реализовать / использовать алгоритм diff, в отличие от difflib Python - вам, вероятно, придется делать какие-то различия в любом случае. Этот фрагмент называет пользователя с наибольшим количеством оттока текста победителем.

Извините за жесткое кодирование.

#!/usr/bin/env python

import collections
import difflib
import logging
import pprint
import urllib2
import re

class OwnageDeterminer(object):

    add_coefficient = 1
    remove_coefficient = .5

    def __init__(self, edits):
        self.edits = edits
        self.counts_by_username = {}

    def __call__(self):
        edits, counts_by_username = self.edits, self.counts_by_username
        for i, edit in enumerate(edits):
            username = edit['username']
            unique_counts = {'added': 0, 'removed': 0}
            existing_text = edits[i-1]['text'] if i > 0 else ''
            new_text = edits[i]['text']
            for char_diff in difflib.ndiff(existing_text, new_text):
                if char_diff.startswith('+'):
                    unique_counts['added'] += 1
                elif char_diff.startswith('-'):
                    unique_counts['removed'] += 1
            user_counts = counts_by_username.get(username, collections.defaultdict(int))
            user_counts['removed'] += self.remove_coefficient * unique_counts['removed']
            user_counts['added'] += self.add_coefficient * unique_counts['added']
            counts_by_username[username] = user_counts
        winner = None
        winning_score = 0
        score_by_username = {}
        for username, counts in counts_by_username.iteritems():
            score = counts['removed'] + counts['added']
            if score > winning_score:
                winner = username
                winning_score = score
            score_by_username[username] = score
        logging.debug('Scores: %s', pprint.pformat(score_by_username))
        return winner


if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    site = urllib2.urlopen('http://stackoverflow.com/revisions/327973/list')
    contents = site.read()
    regex = re.compile(r'(/revisions/viewmarkup/\d+).*?/users/\d+/([\w-]+)',
                       re.MULTILINE|re.DOTALL)
    revisions = regex.findall(contents)
    print revisions
    edits = []
    for reluri, username in sorted(revisions, key=lambda t: t[0]):
        text = urllib2.urlopen('http://stackoverflow.com{0}'.format(reluri)).read()
        edit = {'username': username, 'text': text}
        edits.append(edit)
    od = OwnageDeterminer(edits)
    print od()

Выход:

DEBUG:root:Scores: {'blorgbeard': 0.5,
 'dave-markle': 0.5,
 'dbr': 1172.0,
 'gatekiller': 69.5,
 'joseph-ferris': 0.0,
 'lkessler': 0.0,
 'mark-harrison': 592.0,
 'mdb': 3.0,
 'onebyone-livejournal-com': 0.0,
 'paul-oyster': 482.0,
 'rob-wells': 0.0,
 'simucal': 1070.5,
 'skiphoppy': 0.0,
 'thesoftwarejedi': 701.0}
dbr

Документы Difflib по сложности:

Сроки: Базовая Рэтклифф-Обершельп Алгоритм кубического времени в худшем случай и квадратичное время в ожидаемый случай. SequenceMatcher - это квадратичное время для наихудшего случая и зависит от ожидаемого поведения сложным образом на сколько элементы, которые имеют общие последовательности; Наилучшее время линейно.

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

3 голосов
/ 08 января 2009

С макушки головы я бы сделал что-то вроде этого:

  • Я думаю, что подсчет слов в отличие от строк или символов имеет смысл
  • Разметьте оригинальную ревизию на слова и прикрепите автора к каждому
  • Пошаговая история изменений, и по мере добавления слов прикрепляйте к ним автора.
  • Если слова удалены, просто забудьте их.
  • Если слова изменены, вы можете считать их как удаление / вставку или иметь какой-либо символьный порог, чтобы исправления орфографии не приписывались новому автору.
  • В итоге вы получите список слов против того, кто их первоначально написал
2 голосов
/ 10 января 2009

Как насчет этой идеи?

  • Отображается имя оригинального постера, пока пост не будет изменен более чем на x% от исходного текста
  • Этот процент отображается как «x пользователи, y правки, z% от оригинала»
  • Последнее существующее на данный момент редактирование может также иметь разное количество - так, например, проясняется, что первое редактирование вашего вопроса было незначительным добавлением

Суть в том, что после определенного количества изменений не имеет значения, кому оно принадлежит. Я согласен с мнением пользователей о том, что попытка назначить «владельцев» вики-контента неэффективна.

2 голосов
/ 09 января 2009

Это вики - почему бы не позволить каждому редактору выбрать значение своего изменения? Укажите в раскрывающемся списке что-то вроде ...

Пожалуйста, уточните ваши изменения:

  1. Несколько незначительных правок (орфография, грамматика и т. Д.)
    • Множество мелких правок
    • Несколько существенных правок (изменения конкретных фактов, цифр и т. Д.)
    • Много значимых правок
    • Несколько основных правок (изменения в тезисе, заключение и т. Д.)
    • Множество основных правок

Затем используйте объединенные ответы для оценки владения.

...