package cn.edu.bjut.textmining.util; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Shared utility for Chapter 5 demos. The input documents are assumed to be pre-tokenized with * spaces. */ public final class TextVectorizer { private TextVectorizer() {} public static double[][] tfIdf(List documents) { Map vocabulary = new LinkedHashMap<>(); List> tokenized = new ArrayList<>(); for (String document : documents) { List tokens = tokenize(document); tokenized.add(tokens); for (String token : tokens) { vocabulary.putIfAbsent(token, vocabulary.size()); } } int n = documents.size(); int v = vocabulary.size(); int[] documentFrequency = new int[v]; for (List tokens : tokenized) { Set uniqueTokens = new HashSet<>(tokens); for (String token : uniqueTokens) { documentFrequency[vocabulary.get(token)]++; } } double[][] vectors = new double[n][v]; for (int i = 0; i < n; i++) { Map termFrequency = new HashMap<>(); for (String token : tokenized.get(i)) { termFrequency.put(token, termFrequency.getOrDefault(token, 0) + 1); } for (Map.Entry entry : termFrequency.entrySet()) { int index = vocabulary.get(entry.getKey()); double idf = Math.log((n + 1.0) / (documentFrequency[index] + 1.0)) + 1.0; vectors[i][index] = entry.getValue() * idf; } normalize(vectors[i]); } return vectors; } public static List readDocuments(Path path) { try { List lines = Files.readAllLines(path, StandardCharsets.UTF_8); List documents = new ArrayList<>(); for (String line : lines) { String trimmed = line.trim(); if (!trimmed.isEmpty() && !trimmed.startsWith("#")) { documents.add(trimmed); } } return documents; } catch (IOException e) { throw new UncheckedIOException("读取文本失败:" + path.toAbsolutePath(), e); } } public static double cosine(double[] a, double[] b) { if (a.length != b.length) { throw new IllegalArgumentException("向量维度不同"); } double dot = 0.0; double normA = 0.0; double normB = 0.0; for (int i = 0; i < a.length; i++) { dot += a[i] * b[i]; normA += a[i] * a[i]; normB += b[i] * b[i]; } if (normA == 0.0 || normB == 0.0) { return 0.0; } return Math.max(-1.0, Math.min(1.0, dot / (Math.sqrt(normA) * Math.sqrt(normB)))); } public static double cosineDistance(double[] a, double[] b) { return 1.0 - cosine(a, b); } public static void normalize(double[] vector) { double sum = 0.0; for (double value : vector) { sum += value * value; } double norm = Math.sqrt(sum); if (norm == 0.0) { return; } for (int i = 0; i < vector.length; i++) { vector[i] /= norm; } } public static double[] centroid(double[][] data, int[] labels, int targetLabel) { int d = data[0].length; double[] center = new double[d]; int count = 0; for (int i = 0; i < data.length; i++) { if (labels[i] == targetLabel) { count++; for (int j = 0; j < d; j++) { center[j] += data[i][j]; } } } if (count == 0) { return center; } for (int j = 0; j < d; j++) { center[j] /= count; } normalize(center); return center; } private static List tokenize(String document) { String[] parts = document.trim().split("\\s+"); List tokens = new ArrayList<>(); for (String part : parts) { if (!part.trim().isEmpty()) { tokens.add(part); } } return tokens; } }