确定多个用户编辑的文本的“所有者”

时间:2009-01-08 13:31:06

标签: algorithm language-agnostic text diff

您可能已经注意到我们现在在社区Wiki帖子上显示编辑摘要:

  

社区维基   220个修订版,48个用户

我还想向“最拥有”页面上显示的最终内容的用户显示剩余文本的百分比:

  

社区维基   220个修订,48个用户
   kronoz 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帖子(不仅仅是重新标记,而是频繁更新帖子)作为测试语料库。这是一个很好的,有14个不同作者的15个修订版。谁是“所有者”?

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

点击“查看来源”以获取每个修订版的原始文本。

我应该警告你,一个纯粹的算法解决方案最终可能会成为Longest Common Substring Problem的一种形式。但正如我所提到的,如果它们运作良好,近似值和估计值也很好。

欢迎使用任何语言的解决方案,但我更喜欢

的解决方案
  1. 很容易翻译成c#。
  2. 没有依赖关系。
  3. 在效率之前简化。
  4. SO上的帖子超过25次修订是非常罕见的。但它应该“感觉”准确,所以如果你对编辑进行观察,你就会同意最终的决定。我鼓励你在带有修订历史的堆栈溢出帖子上测试你的算法,看看你是否同意最终输出。


    我现在已经部署了以下近似值,您可以在社区Wiki帖子中为每个保存的修订版看到这些近似值

    • 执行正文更改的每个修订版本line based diff
    • 将每个修订的插入和删除行相加为“editcount”
    • 每个用户ID获得他们贡献的“editcount”的总和
    • 第一修订版作者获得2x *“editcount”作为初始分数,作为主要作者奖励
    • 确定最终所有权百分比:每个用户编辑的行数总计除以所有修订版中已编辑行的总数

    (对于常见的简单条件,例如1个修订版,只有1个作者等,也有一些保护条款。基于行的差异使得所有修订版的重新计算速度相当快;在典型的情况下,例如10个修订版,它是~50ms 。)

    这在我的测试中运作得相当好。当你有几个人编辑的1或2个小帖子时,它会分解一点,但我认为这是不可避免的。接受Joel Neely的答案与我的精神最接近,并且赞同其他一切似乎可行的事情。

15 个答案:

答案 0 :(得分:25)

我认为这个想法存在根本缺陷。

如果有人用糟糕的拼写和不清楚的例子写出精彩的分析,并且我复制了大量编辑,我创作了60%的作品吗?显然不是;结果是衍生品,其中大部分价值来自初始海报。基于字符或单词计数不可能有用的措施,但需要强大的AI级语义分析。

除此之外,根据文章的“所有权”寻求信用可能完全没有帮助和反维基。例如,在维基百科上,表现得好像拥有文章的人是最具破坏性的影响之一。

答案 1 :(得分:20)

早点看到你的推文。从327973链接的显示中可以看出,您已经有了单步差异。基于此,我将专注于多编辑组合:

  1. A,原始海报拥有该职位的100%。

  2. 当B,第二张海报进行编辑时,例如90%的文本没有变化,所有权为A:90%,B:10%。

  3. 现在C,第三方,改变了50%的文字。 (答:45%,B:5%,C:50%)

    换句话说,当海报进行编辑以使x%改变且y =(100-x)%不变时,该海报现在拥有文本的x%,并且所有先前的所有权都乘以y%。

    为了让它变得有趣,现在假设......

  4. A进行20%的编辑。然后A拥有“新”20%,剩余所有权现在乘以80%,留下(A:36%,B:4%,C:40%)。因此,“净”所有权(A:56%,B:4%,C:40%)。

  5. 将此应用于您的样本(327973),所有内容均四舍五入到最接近的百分比:

    版本0:原帖。

    • Paul Oyster:100%

    版本1:您当前的差异工具显示纯文本添加,因此所有这些字符都属于第二张海报。

    • Paul Oyster:91%
    • onebyone:9%

    版本2:差异显示单词的替换。新单词属于第三张海报,其余文字属于之前的海报。

    • Paul Oyster:90%
    • onebyone:9%
    • Blogbeard:1%

    版本3:仅标记编辑。由于你的问题是关于文本的,我忽略了这些标签。

    • Paul Oyster:90%
    • onebyone:9%
    • Blogbeard:1%

    第4版:添加文字。

    • Paul Oyster:45%
    • onebyone:4%
    • Blogbeard:1%
    • Mark Harrison:50%

    我希望这足以让人理解这个提议。它确实有一些限制,但我在你的声明中将这些限制在可接受的近似值中。 ; - )

    1. 它强行在所有先前所有者之间分配变更的影响。如果A帖子,B做了纯粹的添加,而C编辑了B添加的一半,这种简单的方法只是在整个帖子中应用C的所有权,而不试图解析哪个以前的所有权发生了最大的改变。

    2. 它考虑了添加或更改,但没有为删除提供任何所有权信用,因为删除器会将剩余文本添加0%。您可以将此视为错误或功能。我选择了2号门。

    3. 更新:更多关于上述问题#1。我认为,完全跟踪编辑的帖子部分的所有权将需要两件事之一(网页的边距不足以进行正式证明;):

      • 更改文本的存储方式以反映文本各个部分的所有权(例如A拥有单词1-47,B拥有单词48-59,A拥有单词60-94,......),我的提案中对每个部分的“剩余多少”方法,以及更新部分所有权数据。

      • 考虑从头到尾的所有版本(实际上,即时重新计算部分所有权数据)。

      所以这是一个很好的例子,可以在快速和肮脏的近似(以精度为代价),整个数据库的更改(以空间为代价)之间进行权衡,或者每次计算都要看在整个历史中(以时间为代价)。

答案 2 :(得分:5)

答案 3 :(得分:4)

如果我正确地理解了您的问题,看起来您正在尝试做IBM在Wikipedia研究项目中所做的工作。即,查看哪些文本对其他用户最常接受的文本的修订以及整体文本如何随时间变化。该项目的名称为history flowhistory flow - how it works对其算法的工作原理进行了很好的概述。

答案 4 :(得分:4)

如何简单地计算每个人对先前版本的编辑的Levenshtein distance。然后将每个用户的距离分数相加,并计算用户所有用户距离分数之和的百分比。

Here's some C# code to calculate the distances.

答案 5 :(得分:3)

离开我的头顶我会做这样的事情:

  • 我认为计算单词而不是行或字符是有意义的
  • 将原始修订标记为单词,并将作者附加到每个
  • 逐步修订修订历史记录,并在添加文字时将作者附加到其中。
  • 如果删除了单词,请忘记它们。
  • 如果更改了单词,您可以将它们计为删除/插入,或者具有某种基于字符的阈值,以便拼写更正不会归因于新作者。
  • 您最终会得到一份针对最初编写这些文字的人员列表

答案 6 :(得分:3)

如果你想在Python difflib中实现/利用差异算法,这将有效 - 在任何情况下你都可能需要做某种差异。此代码段调用最多文本的用户称为赢家。

请原谅我的硬编码。

#!/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 docs

  

时间:基本的Ratcliff-Obershelp   算法是最差的立方时间   案例和二次时间   预期案例。 SequenceMatcher是   最坏情况下的二次时间和   具有预期的案例行为依赖性   在一个复杂的方式上有多少   序列共有的元素;   最佳案例时间是线性的。

另一个好处是,此获胜者计算是线性的,因此您可以缓存原始结果并对新编辑执行增量更新,尽管初始化负载很大。

答案 7 :(得分:3)

没有人拥有它。授予所有权违反了“社区维基”的精神,并可能导致适得其反的编辑战争。

答案 8 :(得分:2)

一个问题是删除字符是否与添加字符一样有效。使用Diff可能效果很好,但并不总是如此,添加最多的人是最有效的编辑器(有些人可以用10个字符写出其他人用来编写页面的内容)。

所以我认为你有两个因素:

  • 变动量
  • 变革质量

因此,我很想写一些简单地将添加/删除的单词映射到积分的东西。除此之外还有某种品质因素(这必须是一个外部参数),然后可以在排序中使用组合值。

你可以根据项目编辑之前的距离来产生某种原始的“品质因子”。因此,如果我写的内容在第12次编辑之前没有改变,那么它就不会错误(相对于质量较低的东西,只要它被添加就会改变)。

答案 9 :(得分:2)

这是一个维基 - 为什么不让每个编辑选择他或她改变的意义?提供类似......的下拉列表。

请对您的编辑进行限定:

  1. 一些小编辑(拼写,语法等)
    • 许多次要编辑
    • 一些重要的编辑(对特定事实,数字等的更改)
    • 许多重要的编辑
    • 一些主要的编辑(改变论文,结论等)
    • 许多主要编辑
  2. 然后使用合并后的回复来估算所有权。

答案 10 :(得分:2)

这个想法怎么样?

  • 显示原始海报的名称,直到帖子的更改次数超过原始文本的x%
  • 此百分比显示为“x users,y edits,z%of original”
  • 当前存在的最后一个编辑内容也可能有差异量 - 例如,它清楚地表明您的问题的第一次编辑是次要添加

关键在于经过一定程度的改变后谁拥有它并不重要。我同意用户的说法,试图将“所有者”分配给维基内容会产生反作用。

答案 11 :(得分:1)

字符数是棘手的问题。

怎么样:

Break latest revision into a set of sentences. 
//Sentence is any text fragment surrounded by punctuation

For each Sentence
    Find which user created that sentence. 
    Add 1 to the user who created the sentence 
    Add 1 to the number of sentences

For Each user 
    % ownership = Count for that user / Number of sentences. 

查找哪个用户创建了该句子。
如果你想要完全匹配,将句子与修订相匹配很容易,但我对部分匹配更满意。

要做这个部分匹配...

从句子片段中删除常用单词,并在每个修订的剥离版本中搜索该剥离的片段。最早的打击是所有者写的句子。

选项:(而不是常用字词剥离)

  • 对于句子中的每个单词,将每个单词缩小为第一个和最后一个单词。这会解决拼写错误等问题。
  • 仅使用句子片段中的第一个和最后两个单词。
  • 匹配80%的单词可能足以归属所有权。 (这对于计算机来说是一件困难的事情 - 它无法缓存)

剥离常用词

您已经为搜索执行此操作,因此库等已经存在。即使你使用别人的公式,你也许应该在开始之前使用CommonWordStrip每个版本。

答案 12 :(得分:1)

解决此问题的关键是获取有关编辑内容的额外信息。此媒介中提供的额外信息是对问题的投票和回复率。因此,如果有人进行编辑,导致问题得到很多赞成,评论和回复,那么他们的编辑非常有价值。特殊情况可能存在一个全新的未答复问题,该问题在很长一段时间内都会被编辑。

您应该使用Levenshtein距离算法查看帖子的更改量,然后根据问题在编辑后收到的投票,评论和答案的数量来加权编辑。

Let n = total revisions
Let m = the revision number of a poster
Let post[it] = array with text of post at revision 'it'
Let votes[it] = votes that revision 'it' received (also add bonus for comments/answers)
value = 0
for (it = m; it < n; ++it) {
  value += (Levenshtein(post[it-1], post[m]) / average_length_post) * (votes[it])
}

如果您计算每个帖子的值,则帖子的所有权是该用户所有修改的总值除以该帖子的所有编辑值的总和。

答案 13 :(得分:0)

不确定是否可行,但您可以计算添加的字符数。

示例:

  • 用户1提交400个字符的问题(用户1有400个400)
  • 用户2删除40并添加60(用户1有360,用户2有60)

如果您还原,则还应恢复为之前的用户/字符数。

但是...... 也许只是简单而公平地命名原始海报......

第二个想法,我编辑了很多,但我从不认为自己是tekst的“所有者”因为我只是改变了演示文稿(格式和语法)而不是内容。

答案 14 :(得分:0)

最初的概念必须给予重视。 拼写纠正必须给予重量。 语法/结构必须给予重量。 措辞必须给予重量。 简洁必须给予重视。 等...

权衡是唯一公平的方式,但也无法确定。