本文目录导读:

我来介绍PHP实现Jaccard相似度的几种方法:
基础实现(数组方式)
<?php
/**
* 基础的Jaccard相似度计算
* @param array $set1 第一个集合
* @param array $set2 第二个集合
* @return float 相似度值(0-1)
*/
function jaccardSimilarity(array $set1, array $set2): float {
// 确保元素唯一
$set1 = array_unique($set1);
$set2 = array_unique($set2);
// 计算交集
$intersection = array_intersect($set1, $set2);
$intersectionCount = count($intersection);
// 计算并集
$unionCount = count($set1) + count($set2) - $intersectionCount;
// 处理空集合情况
if ($unionCount === 0) {
return 1.0; // 两个空集合视为相同
}
return $intersectionCount / $unionCount;
}
// 使用示例
$setA = ['apple', 'banana', 'orange', 'grape'];
$setB = ['apple', 'banana', 'kiwi', 'mango'];
$similarity = jaccardSimilarity($setA, $setB);
echo "Jaccard相似度: " . round($similarity, 4) . "\n"; // 输出: 0.3333
文本相似度计算
<?php
/**
* 文本Jaccard相似度计算
* @param string $text1 第一个文本
* @param string $text2 第二个文本
* @param string $delimiter 分词分隔符
* @return float 相似度值
*/
function textJaccardSimilarity(string $text1, string $text2, string $delimiter = ' '): float {
// 分词
$words1 = explode($delimiter, $text1);
$words2 = explode($delimiter, $text2);
// 去除空白和标点
$words1 = array_filter(array_map('trim', $words1));
$words2 = array_filter(array_map('trim', $words2));
return jaccardSimilarity($words1, $words2);
}
// 使用示例
$text1 = "The quick brown fox jumps over the lazy dog";
$text2 = "The quick brown fox jumps over the sleeping cat";
echo "文本相似度: " . round(textJaccardSimilarity($text1, $text2), 4) . "\n";
高级实现(支持权重和多种数据类型)
<?php
class JaccardSimilarityCalculator {
/**
* 计算Jaccard相似度(支持权重)
* @param array $set1
* @param array $set2
* @param array $weights 权重数组(可选)
* @return float
*/
public function weightedJaccard(array $set1, array $set2, array $weights = []): float {
$set1 = array_unique($set1);
$set2 = array_unique($set2);
if (empty($set1) && empty($set2)) {
return 1.0;
}
$intersection = array_intersect($set1, $set2);
$union = array_unique(array_merge($set1, $set2));
if (!empty($weights)) {
return $this->calculateWeighted($intersection, $union, $weights);
}
return count($intersection) / count($union);
}
/**
* 计算加权Jaccard
*/
private function calculateWeighted(array $intersection, array $union, array $weights): float {
$intersectionWeight = 0;
$unionWeight = 0;
foreach ($union as $element) {
$weight = $weights[$element] ?? 1;
$unionWeight += $weight;
if (in_array($element, $intersection)) {
$intersectionWeight += $weight;
}
}
return $unionWeight > 0 ? $intersectionWeight / $unionWeight : 0;
}
/**
* 计算多集合的Jaccard相似度
* @param array ...$sets
* @return float
*/
public function multiSetJaccard(array ...$sets): float {
if (count($sets) < 2) {
return 1.0;
}
$sets = array_map('array_unique', $sets);
// 计算交集(所有集合共有的元素)
$intersection = call_user_func_array('array_intersect', $sets);
// 计算并集(所有集合的元素)
$union = call_user_func_array('array_merge', $sets);
$union = array_unique($union);
return count($union) > 0 ? count($intersection) / count($union) : 0;
}
}
// 使用示例
$calculator = new JaccardSimilarityCalculator();
// 基本使用
$set1 = ['a', 'b', 'c', 'd'];
$set2 = ['a', 'b', 'e', 'f'];
echo "基本相似度: " . $calculator->weightedJaccard($set1, $set2) . "\n";
// 加权使用
$weights = ['a' => 2, 'b' => 1, 'c' => 3];
echo "加权相似度: " . $calculator->weightedJaccard($set1, $set2, $weights) . "\n";
// 多集合
$set3 = ['a', 'b', 'g'];
echo "多集合相似度: " . $calculator->multiSetJaccard($set1, $set2, $set3) . "\n";
性能优化版本(处理大数据集)
<?php
/**
* 高性能Jaccard相似度实现
* 使用哈希表提高效率
*/
class OptimizedJaccard {
/**
* 使用哈希表计算Jaccard相似度
* @param array $set1
* @param array $set2
* @return float
*/
public static function calculate(array $set1, array $set2): float {
// 转换为哈希集合
$hashSet1 = array_flip($set1);
$hashSet2 = array_flip($set2);
$intersectionCount = 0;
// 遍历较小的集合以提高性能
if (count($hashSet1) <= count($hashSet2)) {
foreach ($hashSet1 as $element => $_) {
if (isset($hashSet2[$element])) {
$intersectionCount++;
}
}
} else {
foreach ($hashSet2 as $element => $_) {
if (isset($hashSet1[$element])) {
$intersectionCount++;
}
}
}
$totalUnique = count($hashSet1) + count($hashSet2) - $intersectionCount;
return $totalUnique > 0 ? $intersectionCount / $totalUnique : 1.0;
}
}
// 性能测试
$largeSet1 = range(1, 10000);
$largeSet2 = range(5000, 15000);
$startTime = microtime(true);
$similarity = OptimizedJaccard::calculate($largeSet1, $largeSet2);
$endTime = microtime(true);
echo "相似度: " . round($similarity, 4) . "\n";
echo "执行时间: " . round(($endTime - $startTime) * 1000, 2) . "ms\n";
实用示例:文档推荐系统
<?php
class DocumentRecommender {
private $documents = [];
public function addDocument(string $id, string $content): void {
$this->documents[$id] = $this->tokenize($content);
}
private function tokenize(string $text): array {
// 简单的分词(实际应用中使用更复杂的分词器)
$text = strtolower($text);
$text = preg_replace('/[^\w\s]/', '', $text);
return array_unique(explode(' ', $text));
}
/**
* 查找相似文档
* @param string $docId 文档ID
* @param int $limit 返回结果数量
* @return array 相似度排序的文档列表
*/
public function findSimilar(string $docId, int $limit = 5): array {
if (!isset($this->documents[$docId])) {
return [];
}
$targetTokens = $this->documents[$docId];
$results = [];
foreach ($this->documents as $id => $tokens) {
if ($id === $docId) continue;
$similarity = jaccardSimilarity($targetTokens, $tokens);
$results[$id] = $similarity;
}
// 降序排序
arsort($results);
return array_slice($results, 0, $limit);
}
}
// 使用示例
$recommender = new DocumentRecommender();
$recommender->addDocument('doc1', 'PHP is a popular scripting language for web development');
$recommender->addDocument('doc2', 'Python is versatile for data science and web development');
$recommender->addDocument('doc3', 'JavaScript runs in the browser for interactive web pages');
$similarDocs = $recommender->findSimilar('doc1', 2);
print_r($similarDocs);
注意事项
- 预处理:确保数据已经过适当的清洗和标准化
- 性能优化:对于大规模数据,使用哈希表(array_flip)提高效率
- 内存管理:处理大型数据集时注意内存使用
- 数据稀疏性:Jaccard相似度对稀疏数据敏感,考虑使用余弦相似度作为替代
这些实现可以根据具体需求进行调整和优化。