PHP项目如何实现Jaccard相似度?

wen java案例 2

本文目录导读:

PHP项目如何实现Jaccard相似度?

  1. 基础实现(数组方式)
  2. 文本相似度计算
  3. 高级实现(支持权重和多种数据类型)
  4. 性能优化版本(处理大数据集)
  5. 实用示例:文档推荐系统
  6. 注意事项

我来介绍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);

注意事项

  1. 预处理:确保数据已经过适当的清洗和标准化
  2. 性能优化:对于大规模数据,使用哈希表(array_flip)提高效率
  3. 内存管理:处理大型数据集时注意内存使用
  4. 数据稀疏性:Jaccard相似度对稀疏数据敏感,考虑使用余弦相似度作为替代

这些实现可以根据具体需求进行调整和优化。

抱歉,评论功能暂时关闭!