如何用PHP项目实现Dice系数?

wen java案例 3

本文目录导读:

如何用PHP项目实现Dice系数?

  1. 基础Dice系数实现
  2. 优化版本(带缓存和错误处理)
  3. 高级应用:文本相似度匹配
  4. 完整使用示例
  5. 性能优化建议
  6. 注意事项

我来详细介绍如何在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");

注意事项

  1. 字符编码:使用 mb_* 函数处理多字节字符(如中文)
  2. 性能优化:对于大数据集,建议使用缓存和批量处理
  3. 阈值选择:根据不同应用场景调整相似度阈值
  4. 预处理:去除标点、统一大小写等预处理步骤可能提高准确性

这个实现提供了从基础到高级的Dice系数计算方案,可以根据项目需求选择合适的版本。

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