2015-07-27 21 views
17

Mam stronę, na której użytkownicy mogą umieścić opis o sobie.Wykryj PHP zduplikowany tekst

Większość użytkowników pisze coś odpowiedniego, ale niektórzy po prostu kopiują/wklejają ten sam tekst kilka razy (aby stworzyć wrażenie dużej ilości tekstu).

np: „miłość i pokój miłość i pokój miłość i pokój miłość i pokój miłość i miłość spokój a i spokój”

Czy jest to dobry sposób, aby wykryć powtarzające tekst PHP?

Jedyne pojęcie, jakie obecnie posiadam, to rozbicie tekstu na oddzielne słowa (ograniczone spacją), a następnie sprawdzenie, czy słowo jest powtarzane częściej niż zestaw ograniczony. Uwaga: Nie jestem w 100% pewny, jak zakodować to rozwiązanie.

Myśli o najlepszym sposobie wykrywania duplikatów tekstu? Albo jak zakodować powyższy pomysł?

Odpowiedz

17

Jest to podstawowy problem klasyfikacja tekstu. Istnieje lots z articles, gdzie można dowiedzieć się, czy jakiś tekst jest spamem, a nie spamem, który poleciłbym wkopać, jeśli naprawdę chcesz poznać szczegóły. Wiele z tego jest prawdopodobnie przesadą, jeśli chodzi o to, co musisz tutaj zrobić.

Jednym z podejść byłoby oszacowanie, dlaczego wymagasz od ludzi wprowadzenia dłuższego biosu, ale założę się, że już zdecydowałeś, że zmuszanie ludzi do wprowadzania więcej tekstu jest drogą do zrobienia.

Oto zarys tego, co chciałbym zrobić:

  1. Budowanie histogram wystąpień słownych w ciągu wejściowego
  2. studiować histogramy jakiejś ważnej i nieważnego tekstu
  3. wymyślić formułę klasyfikowanie histogramu jako prawidłowego lub nie

Podejście to wymagałoby ustalenia, co różni się pomiędzy tymi dwoma zestawami. Intuicyjnie spodziewam się, że spam będzie zawierał mniej unikalnych słów, a jeśli wykreślisz wartości histogramu, wyższy obszar pod krzywą skupi się w kierunku górnych słów.

Oto niektóre przykładowy kod, aby można było:

$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); 

Po uruchomieniu tego kodu na niektórych powtarzających się ciągów, zobaczysz różnicę. Oto wykres tablicy $vals z przykładowym ciągiem dałeś:

repetitive

Porównaj to z dwóch pierwszych akapitach Martin Luther King Jr.'s bio z Wikipedii:

mlk

Długi ogon wskazuje wiele unikalne słowa. Ciągle jest kilka powtórzeń, ale ogólny kształt pokazuje pewną odmianę.

FYI, PHP ma pakiet stats, który można zainstalować, jeśli zamierzasz wykonywać wiele zadań matematycznych, takich jak odchylenie standardowe, modelowanie dystrybucji itp.

+0

Powiązane: http://venturebeat.com/2015/07/26/watch-this-brilliant-visualization-of-words-in-the-angielski-language/ –

+1

Nie próbuję krytykować podejścia (wiem, że to będzie działać świetnie). Ale tutaj jest kilka pytań: 1) w jaki sposób można znaleźć duplikat frazy (tak, to składa się z n-słów, które znaleźliśmy, ale są n! Różne możliwości). 2) co byś zrobił, gdyby osoba napisała tekst bez spacji. –

13

Można użyć wyrażenia regularnego, tak:

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

Objaśnienie:

  • (.{10,}) wyszukuje i rejestruje ciąg znaków, który wynosi co najmniej 10 znaków
  • \\1{2,} wygląda na pierwszy ciąg co najmniej 2 razy więcej

Możliwe poprawki dostosowane do Twoich potrzeb:

  • Zmień 10 na wyższą lub niższą liczbę, aby dopasować dłuższe lub krótsze powtarzające się ciągi. Użyłem tylko 10 jako przykładu.
  • Jeśli chcesz uchwycić nawet jedno powtórzenie (love and peace love and peace), usuń {2,}. Jeśli chcesz złapać większą liczbę powtórzeń, zwiększ wartość 2.
  • Jeśli nie obchodzi Cię, ile razy wystąpiło powtórzenie, tylko że ono występuje, usuń , w {2,}.
+0

Myślę, że to działa lepiej w ten sposób: '. * (. {10,}) \ 1 {2,}', tylko '. *' Na początku https://regex101.com/r/eV3cH1/1 – baao

+0

@michael Nie trzeba prowadzić ". *'; po prostu spowolni to. –

+0

Nie pasuje ona do pierwszej L kapitału bez niej. Jestem po prostu ciekawy, ponieważ znalazłem dobre pytanie i odpowiedź i uczę się sam regex. Czy możesz wyjaśnić trochę swoje regex? Dzięki! Btw. Jestem podnoszącym na duchu :) – baao

9

myślę, że jesteś na dobrej drodze uszkodzi ciąg i patrząc na powtarzanych słów.

Oto kod chociaż który nie korzysta z PCRE i wykorzystuje PHP funkcje natywne String (str_word_count i array_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) 
    } 
    */ 

Niektóre szczypie mogą być:

  • konfiguracji wykaz wspólne słowa, które należy zignorować:
  • spójrz na kolejność słów (poprzednia i następna), nie tylko liczba wystąpień
+1

Nie wiedziałem o 'str_word_count'. Dzięki za wskazówkę! –

3

Myślę, że podejście polegające na znajdowaniu zduplikowanych słów będzie kłopotliwe. Najprawdopodobniej otrzymasz zduplikowane słowa w prawdziwych opisach "Naprawdę, naprawdę, naprawdę, jak lody, szczególnie lody waniliowe".

Lepszym podejściem jest rozdzielenie łańcucha, aby uzyskać słowa, znaleźć wszystkie unikalne słowa, dodać wszystkie liczby znaków unikalnych słów i ustawić to za pewne ograniczenie. Powiedz, że potrzebujesz 100 opisów postaci, wymaga to około 60 unikalnych znaków ze słów.

Kopiowanie podejście @ ficuscr za

$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) } 
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 
} 
3

Oto kod funkcji, której szukasz w opisie:

<?php 
function duplicate(){ 
    $txt = strtolower("Love a and peace love a and peace love a and peace love a and peace love a and peace love a and peace"); 
    $strings = explode(" ",$txt); 
    $set = 2 ; 
    for($i=0;$i < sizeof($strings);$i++){ 
     $count = 0; 
     $current = $strings[$i]; 
     for($j=$i+1;$j < sizeof($strings);$j++){ 
      if($strings[$j]!==$current){ 
       continue; 
      }else if($count<$set){ 
       $count++; 
      }else{ 
       echo ("String ".$current." repeated more than ".$set." times\n"); 
      } 
     } 
    } 
} 
echo("Hello World!\n"); 
duplicate(); 
?> 
5

Innym pomysłem byłoby wykorzystanie substr_count iteracja:

$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'"; 

które wyjście

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

Nie jestem pewien, czy dobrze jest zwalczyć taki problem. Jeśli ktoś chce umieścić śmieci w polu aboutme, zawsze będzie wpadał na pomysł, jak to zrobić. Ale będę ignorować ten fakt i zwalczania tego problemu jako algorytmicznego wyzwanie:

uwzględniając ciąg S, która składa się z podciągów (który może pojawić wiele razy i nie pokrywających) znaleźć podciąg składać się z.

Definicja to wesz i zakładam, że ciąg jest już konwertowany na małe litery.

Pierwszy prostszy sposób:


Zastosowanie modyfikacja longest common subsequence który ma proste rozwiązanie programowania DP. Ale zamiast znajdować podsekcję w dwóch różnych sekwencjach, można znaleźć najdłuższy wspólny podciąg ciągu w odniesieniu do tego samego ciągu LCS(s, s).

Na początku brzmi głupio (z pewnością LCS(s, s) == s), ale w rzeczywistości nie zależy nam na odpowiedzi, zależy nam na macierzy DP, którą otrzymujemy. wygląd

Miejmy na przykład: s = "abcabcabc" i matryca jest:

[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] 

Uwaga ładne przekątne tam. Jak widać pierwsze przekątne kończy się na 3, drugie na 6 i na trzecim z 9 (nasze oryginalne rozwiązanie DP, którego nie obchodzi).

To nie jest zbieg okoliczności. Mam nadzieję, że po dokładniejszym przeanalizowaniu budowy matrycy DP można zauważyć, że przekątne odpowiadają zduplikowanym ciągom znaków.

Oto przykład dla s = "aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas" enter image description here oraz bardzo ostatni wiersz w macierzy jest: [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].

Jak widać duże liczby (17, 34, 51, 68) odpowiadają końcowi przekątnych (jest tam także trochę szumu, ponieważ specjalnie dodałem małe duplikaty, takie jak aaa).

Które sugerują, że możemy po prostu znaleźć gcd z największych dwóch liczb gcd(68, 51) = 17, które będą długości naszego wielokrotnego podciągu.

Tylko dlatego, że wiemy, że cały ciąg składa się z powtarzających się podciągów, wiemy, że zaczyna się on od 0-tej pozycji (jeśli nie wiemy, musimy znaleźć offset).

I oto: ciąg znaków to "aaabasdfwasfsdtas".

P.S. Ta metoda pozwala znaleźć powtórzenia, nawet jeśli są nieznacznie zmodyfikowane.

Dla osób, które chciałyby się bawić o to skrypt Pythona (który powstał w pośpiechu więc nie krępuj się poprawić):

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

Mówiłem o łatwy sposób, a Zapomniałem pisać o trudnej drodze. Robi się późno, więc wyjaśnię ten pomysł. Wdrożenie jest trudniejsze i nie jestem pewien, czy da lepsze wyniki. Ale tutaj jest:

Użyj algorytmu dla longest repeated substring (będziesz musiał zaimplementować trie lub suffix tree co nie jest łatwe w php).

Po tym:

s = "aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas" 
s1 = largest_substring_algo1(s) 

Took realizację largest_substring_algo1 from here. W rzeczywistości nie jest najlepszy (tylko do pokazania idei), ponieważ nie korzysta z wyżej wymienionych struktur danych. Wyniki dla s i s1 są:

aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas 
aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaa 

Jak widać różnica między nimi jest właściwie podciąg, który został powielony.

+0

Co jest tak trudnego w konkretnym implementowaniu drzewa trie lub sufiksu w PHP (co nie sprowadza się do tego, że trudno jest zaimplementować * coś * w PHP)? – Martijn

+0

Nie powiedziałem, że jest "tak ciężko". Powiedziałem, że to "nie jest łatwe". Rozumiem przez to, że jest to zdecydowanie możliwe, ale dla przeciętnej osoby nie zorientowanej na algebrację zajmie to dużo czasu. Dlaczego tak jest? Tylko dlatego, że jeśli chcesz zaimplementować go w C/C++/Pythonie, możesz skorzystać z kilku tutoriali/implementacji/instrukcji krok po kroku. I nie jest trudno go modyfikować/zrozumieć. Zawsze łatwiej jest napisać coś, gdy 50 osób napisało to przed tobą. P.S. jeśli jesteś tak rozczarowany wyrażeniem - możesz je usunąć. –

2

Masz trudny problem na rękach, przede wszystkim dlatego, że Twoje wymagania są niejasne.

Wskazujesz, że chcesz zabronić powtarzania tekstu, ponieważ jest "zły".

Rozważmy kogoś, kto stawia ostatnią zwrotkę Robert mrozy przystając pod lasem w śnieżny wieczór w swoim profilu:

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 

Można rozważyć to dobre, ale to ma powtórzeń. Co jest dobre, a co złe? (zauważ, że to jeszcze nie jest problem z implementacją, po prostu szukasz sposobu na zdefiniowanie "złych powtórzeń").

Bezpośrednie wykrywanie duplikatów okazuje się w ten sposób trudne. Przerzućmy więc sztuczki.

Kompresja działa, pobierając nadmiarowe dane i kompresując je w coś mniejszego. Bardzo powtarzalny tekst byłby bardzo łatwo skompresowany. Sztuczka, którą możesz wykonać, to wziąć tekst, zip go i spojrzeć na współczynnik kompresji. Następnie dostosuj dozwolony współczynnik do czegoś, co uznasz za zadowalające.

realizacja:

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

Kilka wyników doświadczalnych z przykładów znalezionych w tej kwestii/odpowiedzi:

  • „miłość i pokój miłość i pokój miłość i miłość spokój a i spokój kochajcie a pokój miłujcie a pokój ": 0.3960396039604
  • „Te lasy są piękne, ciemne i głębokie ale mam zobowiązuje się do zachowania i mil zanim zasnę i mile pójść przed śpię”: 0.78461538461538
  • „aaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtasaaabasdfwasfsdtas”: +0,58823529411765

sugeruje wartość progową około 0,6 przed odrzuceniem jej jako zbyt monotonne.

+0

Sprytne użycie programu gzip! –