PHP检测重复文本

时间:2015-07-27 00:35:24

标签: php algorithm

我有一个网站,用户可以在其中提供有关自己的说明。

大多数用户都会写一些合适的内容,但有些用户只需复制/粘贴相同的文本多次(以创建相当数量的文本)。

  例如:"爱一个和平爱一个和平爱一个和平爱一个和平爱一个和平爱一个和平"

有没有一种很好的方法可以用PHP检测重复文本?

我目前唯一的概念是将文本分成单独的单词(由空格分隔),然后查看单词是否重复超过集合限制。注意:我不能100%确定如何编写此解决方案。

关于检测重复文本的最佳方法的想法?或者如何编写上述想法?

9 个答案:

答案 0 :(得分:20)

这是一个基本的文本分类问题。有lots articles repetitive如何确定某些文字是垃圾邮件/非垃圾邮件,如果您真的想了解详细信息,我建议您进行挖掘。你需要在这里做很多事情可能有点过头了。

当然,一种方法是评估为什么你要求人们输入更长的时间,但我会假设你已经决定强迫人们输入更多的文字是要走的路。

这是我要做的事情的概述:

  1. 为输入字符串构建单词出现的直方图
  2. 研究一些有效和无效文本的直方图
  3. 想出一个将直方图分类为有效的公式
  4. 这种方法需要你弄清楚两套之间的不同之处。直觉上,我预计垃圾邮件会显示较少的独特单词,如果您绘制直方图值,曲线下方的较高区域会集中于顶部单词。

    这里有一些示例代码可以帮助您:

    {
      "IPBlock":"1.2.0.0",
      "IPAddress":"1.2.3.4",
      "device":"device21"
    }
    

    当您在某些重复字符串上运行此代码时,您将看到其中的差异。这是您给出的示例字符串中$str = 'Love a and peace love a and peace love a and peace love a and peace love a and peace love a and peace'; // Build a histogram mapping words to occurrence counts $hist = array(); // Split on any number of consecutive whitespace characters foreach (preg_split('/\s+/', $str) as $word) { // Force all words lowercase to ignore capitalization differences $word = strtolower($word); // Count occurrences of the word if (isset($hist[$word])) { $hist[$word]++; } else { $hist[$word] = 1; } } // Once you're done, extract only the counts $vals = array_values($hist); rsort($vals); // Sort max to min // Now that you have the counts, analyze and decide valid/invalid var_dump($vals); 数组的图:

    Martin Luther King Jr.'s bio

    将其与维基百科的mlk的前两段进行比较:

    stats

    长尾表示许多独特的单词。还有一些重复,但总的形状显示出一些变化。

    仅供参考,如果您要进行大量数学运算,如标准偏差,分布建模等,PHP可以安装https://cloud.google.com/appengine/docs/python/sockets/ssl_support个包。

答案 1 :(得分:13)

您可以使用正则表达式,如下所示:

if (preg_match('/(.{10,})\\1{2,}/', $theText)) {
    echo "The string is repeated.";
}

说明:

  • (.{10,})查找并捕获至少10个字符长的字符串
  • \\1{2,}查找第一个字符串至少2次

可能的调整以满足您的需求:

  • 10更改为更高或更低的数字,以匹配更长或更短的重复字符串。我只是以10为例。
  • 如果您想要捕捉一次重复(love and peace love and peace),请删除{2,}。如果要捕获更多重复次数,请增加2
  • 如果您不关心重复发生的次数,只发生重复次数,请删除,中的{2,}

答案 2 :(得分:10)

我认为你是在正确的轨道上打破字符串,看着重复的话。

以下是一些代码虽然不使用PCRE并利用PHP本机字符串函数(str_word_countarray_count_values):

<?php
    $words = str_word_count("Love a and peace love a and peace love a and peace love a and peace love a and peace love a and peace", 1);
    $words = array_count_values($words);

  var_dump($words);
  /*
  array(5) {
    ["Love"]=>
    int(1)
    ["a"]=>
    int(6)
    ["and"]=>
    int(6)
    ["peace"]=>
    int(6)
    ["love"]=>
    int(5)
  }
  */

一些调整可能是:

  • 设置要忽略的常用字词列表
  • 查看单词的顺序(上一页和下一页),而不仅仅是出现次数

答案 3 :(得分:6)

另一个想法是使用substr_count迭代:

$str = "Love a and peace love a and peace love a and peace love a and peace love a and peace love a and peace";

$rep = "";

$str = strtolower($str);
for($i=0,$len=strlen($str),$pattern=""; $i<$len; ++$i) {
  $pattern.= $str[$i];
  if(substr_count($str,$pattern)>1)
    $rep = strlen($rep)<strlen($pattern) ? $pattern : $rep;
  else
    $pattern = "";
}

// warn if 20%+ of the string is repetitive
if(strlen($rep)>strlen($str)/5) echo "Repetitive string alert!";
else echo "String seems to be non-repetitive.";

echo " Longest pattern found: '$rep'";

哪个会输出

Repetitive string alert! Longest pattern found: 'love a and peace love a and peace love a and peace'

答案 4 :(得分:4)

// 3 examples of how you might detect repeating user input

// use preg_match

// pattern to match agains
$pattern = '/^text goes here$/';

// the user input
$input = 'text goes here';

// check if its match
$repeats = preg_match($pattern, $input);

if ($repeats) {
    var_dump($repeats);
} else {
    // do something else
}

// use strpos

$string = 'text goes here';
$input = 'text goes here';
$repeats = strpos($string, $input);

if ($repeats !== false) {
    # code...
    var_dump($repeats);
} else {
    // do something else
}

// or you could do something like:
function repeatingWords($str)
{
    $words = explode(' ', trim($str));  //Trim to prevent any extra blank
    if (count(array_unique($words)) == count($words)) {
        return true; //Same amount of words
    }

    return false;
}

$string = 'text goes here. text goes here. ';

if (repeatingWords($string)) {
    var_dump($string);
} else {
    // do something else
}

答案 5 :(得分:3)

我认为找到重复单词的方法会很混乱。很可能你会在真实描述中得到重复的单词&#34;我真的,真的,真的,就像冰淇淋,特别是香草冰淇淋&#34;。

更好的方法是分割字符串以获取单词,找到所有唯一单词,添加唯一单词的所有字符计数,并设置太多限制。比如,您需要100个字符描述,需要大约60个单词中的唯一字符。

复制@ ficuscr的方法

$words = str_word_count("Love a and peace love a and peace love a and peace love a and peace love a and peace love a and peace", 1);
$total = 0;
foreach ($words as $key => $count) { $total += strlen($key) }

答案 6 :(得分:3)

以下是您在说明中寻找的功能代码:

this.$.userEmail.invalid

答案 7 :(得分:3)

我不确定打击这样的问题是否是一个好主意。如果一个人想在垃圾场上放垃圾,他们总会想出如何做到这一点。但我会忽略这个事实并将这个问题作为算法挑战来解决:

  

有一个字符串S,它由子串组成(可以出现)   多次且不重叠)找到它所包含的子串。

定义是louse,我假设字符串已经转换为小写。

首先是一种更简单的方式:

使用具有简易DP编程解决方案的longest common subsequence的修改。但是,不是在两个不同的序列中找到子序列,而是可以找到关于相同字符串LCS(s, s)的字符串的最长公共子序列。

一开始听起来很愚蠢(肯定是LCS(s, s) == s),但实际上我们并不关心答案,我们关心它得到的DP矩阵。

让我们看一下示例:s = "abcabcabc",矩阵为:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 0, 0, 1, 0, 0, 1, 0, 0]
[0, 0, 2, 0, 0, 2, 0, 0, 2, 0]
[0, 0, 0, 3, 0, 0, 3, 0, 0, 3]
[0, 1, 0, 0, 4, 0, 0, 4, 0, 0]
[0, 0, 2, 0, 0, 5, 0, 0, 5, 0]
[0, 0, 0, 3, 0, 0, 6, 0, 0, 6]
[0, 1, 0, 0, 4, 0, 0, 7, 0, 0]
[0, 0, 2, 0, 0, 5, 0, 0, 8, 0]
[0, 0, 0, 3, 0, 0, 6, 0, 0, 9]

注意那里漂亮的对角线。如您所见,第一个对角线以3结尾,第二个以6结束,第三个以9结尾(我们原来的DP解决方案,我们不在乎)。

这不是巧合。希望在查看有关如何构造DP矩阵的更多细节之后,您可以看到这些对角线对应于重复的字符串。

以下是s = "aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas"的示例 enter image description here 并且矩阵中的最后一行是: [0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 17, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 34, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 51, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 68]

当你看到大数字(17,34,51,68)时,对应于对角线的末端(那里也有一些噪音,因为我特意添加了像aaa这样的小重复字母。)

这表明我们可以找到最大两个数字gcd(68, 51) = 17的{​​{3}},这将是我们重复子字符串的长度。

这只是因为我们知道整个字符串由重复的子字符串组成,我们知道它从第0个位置开始(如果我们不知道它我们需要找到偏移量)。

我们走了:字符串是"aaabasdfwasfsdtas"

P.S。此方法可让您查找重复内容,即使它们稍有修改。

对于想在这里玩游戏的人来说,这是一个python脚本(它是在喧嚣中创建的,所以随时可以改进):

def longest_common_substring(s1, s2):
    m = [[0] * (1 + len(s2)) for i in xrange(1 + len(s1))]
    longest, x_longest = 0, 0
    for x in xrange(1, 1 + len(s1)):
        for y in xrange(1, 1 + len(s2)):
            if s1[x - 1] == s2[y - 1]:
                m[x][y] = m[x - 1][y - 1] + 1
                if m[x][y] > longest:
                    longest = m[x][y]
            else:
                m[x][y] = 0
    return m

s = "aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas"
m = longest_common_substring(s, s)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
M = np.array(m)
print m[-1]
arr = np.asarray(M)
plt.imshow(arr, cmap = cm.Greys_r, interpolation='none')
plt.show()

我告诉了这个简单的方法,并且忘了写下这条路。 现在已经很晚了,所以我只想解释一下这个想法。实施更难,我不确定它是否会给你更好的结果。但这是:

使用gcd的算法(您需要实现longest repeated substringtrie,这在php中并不容易。)

之后:

s = "aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas"
s1 = largest_substring_algo1(s)

实施suffix tree。实际上它并不是最好的(只是为了显示这个想法),因为它没有使用上面提到的数据结构。 ss1的结果是:

aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas
aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaa

如您所见,它们之间的区别实际上是重复的子字符串。

答案 8 :(得分:2)

你手上有一个棘手的问题,主要是因为你的要求有点不清楚。

您表示您想要禁止重复的文字,因为它“不好”。

考虑一下谁将罗伯特弗罗斯特斯的最后一节放在伍兹的一个下雪的晚上在他们的个人资料中停止:

These woods are lovely, dark and deep
but I have promises to keep
and miles to go before I sleep
and miles to go before I sleep

你可能会认为这很好,但确实有重复。什么是好的,什么是坏的? (请注意,这不是一个实现问题,你只是在寻找一种定义“糟糕重复”的方法)

直接检测重复因此证明是棘手的。所以,让我们转向技巧。

压缩通过获取冗余数据并将其压缩为更小的数据来实现。非常重复的文本很容易压缩。你可以执行的一个技巧是获取文本,压缩它,并查看压缩率。然后将允许的比例调整为您认为可接受的比例。

实现:

$THRESHOLD = ???;
$bio = ???;
$zippedbio = gzencode($bio);
$compression_ratio = strlen($zippedbio) / strlen($bio);
if ($compression_ratio >= $THRESHOLD) {
  //ok;
} else {
  //not ok;
}

本问题/答案中的示例中的几个实验结果:

  • “爱和平,爱和平,爱和平,爱和平,爱和平,和平爱和平”:0.3960396039604
  • “这些树林很可爱,黑暗而深沉 但我保证会保留 离我睡觉还早着呢 我睡觉前要去的地方“:0.78461538461538
  • “aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas”:0.58823529411765

建议一个约0.6的阈值,然后拒绝它过于重复。