本文目录导读:

我来详细介绍如何在PHP项目中实现Dice系数。
基础Dice系数实现
函数实现
<?php
/**
* 计算两个字符串的Dice系数
* @param string $str1 字符串1
* @param string $str2 字符串2
* @param int $ngram N-gram的大小(通常为2)
* @return float Dice系数(0-1之间)
*/
function diceCoefficient($str1, $str2, $ngram = 2) {
// 空字符串处理
if (empty($str1) && empty($str2)) {
return 1.0;
}
if (empty($str1) || empty($str2)) {
return 0.0;
}
// 生成N-gram集合
$ngrams1 = generateNgrams($str1, $ngram);
$ngrams2 = generateNgrams($str2, $ngram);
// 计算交集
$intersection = array_intersect($ngrams1, $ngrams2);
// Dice系数公式: 2 * |A∩B| / (|A| + |B|)
$dice = (2 * count($intersection)) / (count($ngrams1) + count($ngrams2));
return $dice;
}
/**
* 生成N-gram数组
* @param string $str 输入字符串
* @param int $n N-gram大小
* @return array N-gram数组
*/
function generateNgrams($str, $n = 2) {
$ngrams = [];
$length = mb_strlen($str);
// 处理短字符串
if ($length < $n) {
return [$str];
}
for ($i = 0; $i <= $length - $n; $i++) {
$ngrams[] = mb_substr($str, $i, $n);
}
return $ngrams;
}
使用示例
// 基本使用
$similarity = diceCoefficient("hello", "hallo");
echo "相似度: " . $similarity; // 输出: 0.5
// 使用不同N-gram大小
$similarity = diceCoefficient("abc", "abd", 2); // bi-gram
echo "Bi-gram相似度: " . $similarity;
$similarity = diceCoefficient("abc", "abd", 3); // tri-gram
echo "Tri-gram相似度: " . $similarity;
优化版本(带缓存和错误处理)
<?php
class DiceSimilarity {
private $cache = [];
private $defaultNgram = 2;
/**
* 计算Dice系数(带缓存)
*/
public function calculate($str1, $str2, $ngram = null) {
$ngram = $ngram ?? $this->defaultNgram;
$cacheKey = md5($str1 . $str2 . $ngram);
// 检查缓存
if (isset($this->cache[$cacheKey])) {
return $this->cache[$cacheKey];
}
// 验证输入
if (!$this->validateInput($str1, $str2)) {
return 0.0;
}
// 计算N-gram
$ngrams1 = $this->generateNgramsOptimized($str1, $ngram);
$ngrams2 = $this->generateNgramsOptimized($str2, $ngram);
// 计算交集(优化版本)
$intersection = $this->countIntersection($ngrams1, $ngrams2);
// 计算Dice系数
$dice = (2 * $intersection) / (count($ngrams1) + count($ngrams2));
// 缓存结果
$this->cache[$cacheKey] = $dice;
return $dice;
}
/**
* 验证输入
*/
private function validateInput($str1, $str2) {
if (!is_string($str1) || !is_string($str2)) {
throw new InvalidArgumentException('输入必须是字符串');
}
return true;
}
/**
* 优化的N-gram生成
*/
private function generateNgramsOptimized($str, $n) {
$ngrams = [];
$length = mb_strlen($str);
if ($length < $n) {
return [$str]; // 返回单个元素
}
for ($i = 0; $i <= $length - $n; $i++) {
$ngrams[] = mb_substr($str, $i, $n);
}
return $ngrams;
}
/**
* 高效计算交集(使用哈希表)
*/
private function countIntersection($arr1, $arr2) {
// 使用较短的数组构建哈希表
if (count($arr1) > count($arr2)) {
list($arr1, $arr2) = [$arr2, $arr1];
}
// 构建统计映射
$countMap = [];
foreach ($arr1 as $item) {
$countMap[$item] = ($countMap[$item] ?? 0) + 1;
}
// 计算交集
$intersection = 0;
foreach ($arr2 as $item) {
if (isset($countMap[$item]) && $countMap[$item] > 0) {
$intersection++;
$countMap[$item]--;
}
}
return $intersection;
}
/**
* 批量计算相似度
*/
public function calculateBatch(array $pairs, $ngram = null) {
$results = [];
foreach ($pairs as $key => $pair) {
if (count($pair) >= 2) {
$results[$key] = $this->calculate($pair[0], $pair[1], $ngram);
}
}
return $results;
}
}
高级应用:文本相似度匹配
<?php
class TextMatcher {
private $dice;
private $threshold;
public function __construct($threshold = 0.6) {
$this->dice = new DiceSimilarity();
$this->threshold = $threshold;
}
/**
* 文本预处理
*/
private function preprocess($text) {
// 转小写
$text = mb_strtolower($text);
// 去除标点符号
$text = preg_replace('/[^\p{L}\p{N}\s]/u', '', $text);
// 去除多余空格
$text = preg_replace('/\s+/', ' ', $text);
return trim($text);
}
/**
* 在候选列表中查找最匹配的文本
*/
public function findBestMatch($query, array $candidates) {
$query = $this->preprocess($query);
$bestMatch = null;
$bestScore = 0;
foreach ($candidates as $candidate) {
$processedCandidate = $this->preprocess($candidate);
$score = $this->dice->calculate($query, $processedCandidate);
if ($score > $bestScore && $score >= $this->threshold) {
$bestScore = $score;
$bestMatch = $candidate;
}
}
return [
'match' => $bestMatch,
'score' => $bestScore
];
}
/**
* 查找所有超过阈值的匹配
*/
public function findAllMatches($query, array $candidates) {
$query = $this->preprocess($query);
$matches = [];
foreach ($candidates as $candidate) {
$processedCandidate = $this->preprocess($candidate);
$score = $this->dice->calculate($query, $processedCandidate);
if ($score >= $this->threshold) {
$matches[] = [
'text' => $candidate,
'score' => $score
];
}
}
// 按分数降序排序
usort($matches, function($a, $b) {
return $b['score'] <=> $a['score'];
});
return $matches;
}
}
完整使用示例
<?php
// 引入所有类
require_once 'DiceSimilarity.php';
require_once 'TextMatcher.php';
// 1. 基本使用
$dice = new DiceSimilarity();
$similarity = $dice->calculate("中国北京", "中国上海");
echo "字符串相似度: " . $similarity . "\n";
// 2. 批量计算
$pairs = [
['hello', 'hallo'],
['test', 'text'],
['name', 'names']
];
$results = $dice->calculateBatch($pairs);
print_r($results);
// 3. 文本匹配
$matcher = new TextMatcher(0.5);
$candidates = [
"北京天安门",
"上海外滩",
"广州塔",
"万里长城"
];
// 查找最佳匹配
$result = $matcher->findBestMatch("天安门广场", $candidates);
echo "最佳匹配: " . print_r($result, true) . "\n";
// 查找所有匹配
$allMatches = $matcher->findAllMatches("北京", $candidates);
echo "所有匹配:\n";
print_r($allMatches);
// 4. 中文文本匹配示例
$chineseMatcher = new TextMatcher(0.4);
$products = [
"苹果iPhone 15 Pro Max",
"苹果iPhone 14 Pro",
"华为Mate 60 Pro",
"小米14 Ultra"
];
$searchResult = $chineseMatcher->findAllMatches("苹果手机", $products);
echo "搜索产品结果:\n";
print_r($searchResult);
性能优化建议
<?php
/**
* 高性能Dice系数实现(用于大规模数据)
*/
class FastDiceSimilarity {
private $cache = [];
/**
* 使用位运算优化计算
*/
public function fastCalculate($str1, $str2) {
// 快速过滤
if (abs(mb_strlen($str1) - mb_strlen($str2)) > 5) {
return 0.0;
}
// 生成Bi-gram
$ngrams1 = $this->fastNgram($str1);
$ngrams2 = $this->fastNgram($str2);
// 使用位图加速交集计算(仅适用于小字符集)
$intersection = count(array_intersect($ngrams1, $ngrams2));
return (2 * $intersection) / (count($ngrams1) + count($ngrams2));
}
/**
* 快速N-gram生成
*/
private function fastNgram($str, $n = 2) {
$ngrams = [];
$len = strlen($str);
if ($len < $n) {
return [$str];
}
// 使用位移操作
for ($i = 0; $i <= $len - $n; $i++) {
$ngrams[] = substr($str, $i, $n);
}
return $ngrams;
}
}
// 使用示例
$fastDice = new FastDiceSimilarity();
$result = $fastDice->fastCalculate("testing", "test");
注意事项
- 字符编码:使用
mb_*函数处理多字节字符(如中文) - 性能优化:对于大数据集,建议使用缓存和批量处理
- 阈值选择:根据不同应用场景调整相似度阈值
- 预处理:去除标点、统一大小写等预处理步骤可能提高准确性
这个实现提供了从基础到高级的Dice系数计算方案,可以根据项目需求选择合适的版本。