コピペのチェック

前処理

以下のチェックをする前に,Wordのdocxファイルは docx2txt を使ってテキストに直しておく:

for x in *.docx; do docx2txt.pl $x; done

それ以外のものも UTF-8 テキストに変換しておく。

その1

コピペのチェックが流行りなので,やってみた。ただし,ネット検索するのでなく,相互剽窃のチェックだけである。Rのstringdistパッケージを使う。

# レポート(テキストファイル)のファイル名(tmp/*.txt の場合)
names = dir("tmp", "*.txt", full.names=TRUE)

library(stringdist)

n = length(names)
s = character(n)
for (i in 1:n) {
  s[i] = readChar(names[i], 2000) # 頭2000文字分読む(Unicode)
}
for (i in 1:(n-1)) {
  for (j in (i+1):n) {
    ni = nchar(s[i])
    nj = nchar(s[j])
    d = stringdist(s[i], s[j], method='lv') - abs(ni - nj)
    if (d < 100) {
      cat(names[i], names[j], ni, nj, d, "\n")
    }
  }
}

上ではLevenshtein距離から文字数の差の絶対値を引いたものを使っている。どのような指標が良いかは考え中。

[追記] i をもとにして j を作った場合,j のどれくらいの割合が i かを示す量として,(max(ni, nj) - d) / nj が良いかもしれない。

その2

文字列 $x$ の Kolmogorov complexity(ざっくり言えば,データ圧縮後の最小ファイルサイズ)を $K(x)$ とし,文字列 $x$,$y$ をつなぎ合わせた文字列を $x+y$ とすると,

\[ \max(K(x),K(y)) \leq K(x+y) \leq K(x) + K(y) \]

が成り立つ。中辺 $K(x+y)$ が左辺に近いほど $x$,$y$ は似ている。これを使ってレポートの類似性を調べることができる(かもしれない)。$K(x)$ の代わりに gzip(オプション -9)で圧縮後のファイルサイズを使う:

K = numeric(n)
for (i in 1:n) {
  K[i] = as.numeric(system(paste("cat", names[i], "| gzip -9 -c | wc -c"), intern=T))
}
for (i in 1:(n-1)) {
  for (j in (i+1):n) {
    Kij = as.numeric(system(paste("cat", names[i], names[j], "| gzip -9 -c | wc -c"), intern=T))
    # Kij が max(K[i],K[j]) と K[i]+K[j] のどちらに近いか調べて報告
  }
}

この種のものは2002年に Language Trees and Zipping というペーパーが(よりによって)Physical Review Letters に掲載されてからちょっとだけ流行した。


Last modified: