목차
算法介绍" >算法介绍
算法的原理
BSBI
SPIMI
算法的代码实现
算法小结
데이터 베이스 MySQL 튜토리얼 倒排索引构建算法BSBI和SPIMI

倒排索引构建算法BSBI和SPIMI

Jun 07, 2016 pm 04:13 PM
소개하다 짓다 연산 색인

算法介绍 在信息搜索领域,构建索引一直是是一种非常有效的方式,但是当搜索引擎面对的是海量数据的时候,你如果要从茫茫人海的数据中去找出数据,显然这不是一个很好的办法。于是倒排索引这个概念就被提了出来。再说倒排索引概念之前,先要理解一下,一般的

算法介绍

在信息搜索领域,构建索引一直是是一种非常有效的方式,但是当搜索引擎面对的是海量数据的时候,你如果要从茫茫人海的数据中去找出数据,显然这不是一个很好的办法。于是倒排索引这个概念就被提了出来。再说倒排索引概念之前,先要理解一下,一般的索引检索信息的方式。比如原始的数据源假设都是以文档的形式被分开,文档1拥有一段内容,文档2也富含一段内容,文档3同样如此。然后给定一个关键词,要搜索出与此关键词相关的文档,自然而然我们联想到的办法就是一个个文档的内容去比较,判断是否含有此关键词,如果含有则返回这个文档的索引地址,如果不是接着用后面的文档去比,这就有点类似于字符串的匹配类似。很显然,当数据量非常巨大的时候,这种方式并不适用。原来的这种方式可以理解为是索引-->关键词,而倒排索引的形式则是关键词--->索引位置,也就是说,给出一个关键词信息,我能立马根据倒排索引的信息得出他的位置。当然,这里说的是倒排索引最后要达到的效果,至于是用什么方式实现,就不止一种了,本文所述的就是其中比较出名的BSBI和SPIMI算法。

算法的原理

这里首先给出一个具体的实例来了解一般的构造过程,先避开具体的实现方式,给定下面一组词句。

Doc1:Mike spoken English Frequently at home.And he can write English every day.

Doc2::Mike plays football very well.

首先我们必须知道,我们需要的是一些关键的信息,诸如一些修饰词等等都需要省略,动词的时态变化等都需要还原,如果代词指的是同个人也能够省略,于是上面的句子可以简化成

Doc1:Mike spoken English home.write English.

Doc2:Mike play football.

下面进行索引的倒排构建,因为Mike出现在文档1和文档2 中,所以Mike:{1, 2}后面的词的构造同样的道理。最后的关系就会构成词对应于索引位置的映射关系。理解了这个过程之后呢,可以介绍一下本文主要要说的BSBI(基于磁盘的外部排序构建索引)和SPIMI(内存单遍扫描构建索引)算法了,一般来说,后者比前者常用。

BSBI

此算法的主要步骤如下:

1、将文档中的词进行id的映射,这里可以用hash的方法去构造

2、将文档分割成大小相等的部分。

3、将每部分按照词ID对上文档ID的方式进行排序

4、将每部分排序好后的结果进行合并,最后写出到磁盘中。

5、然后递归的执行,直到文档内容全部完成这一系列操作。

这里有一张示意图:

\

在算法的过程中会用到读缓冲区和写缓冲区,至于期间的大小多少如何配置都是看个人的,我在后面的代码实现中也有进行设置。至于其中的排序算法的选择,一般建议使用效果比较好的快速排序算法,但是我在后面为了方便,直接用了自己更熟悉的冒泡排序算法,这个也看个人。

SPIMI

接下来说说SPIMI算法,就是内存单遍扫描算法,这个算法与上面的算法一上来就有直接不同的特点就是他无须做id的转换,还是采用了词对索引的直接关联。还有1个比较大的特点是他不经过排序,直接按照先后顺序构建索引,算法的主要步骤如下:

1、对每个块构造一个独立的倒排索引。

2、最后将所有独立的倒排索引进行合并就OK了。

本人为了方便就把这个算法的实现简洁化了,直接在内存中完成所有的构建工作。望读者稍加注意。SPIMI相对比较的简单,这里就不给出截图了。

算法的代码实现

首先是文档的输入数据,采用了2个一样的文档,我也是实在想不出有更好的测试数据了

doc1.txt:

 

Mike studyed English hardly yesterday
He got the 100 at the last exam
He thinks English is very interesting
로그인 후 복사

doc2.txt:

Mike studyed English hardly yesterday
He got the 100 at the last exam
He thinks English is very interesting
로그인 후 복사
下面是文档信息预处理类PreTreatTool.java:

package InvertedIndex;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文档预处理工具类
 * 
 * @author lyq
 * 
 */
public class PreTreatTool {
	// 一些无具体意义的过滤词
	public static String[] FILTER_WORDS = new String[] { "at", "At", "The",
			"the", "is", "very" };

	// 批量文档的文件地址
	private ArrayList<String> docFilePaths;
	// 输出的有效词的存放路径
	private ArrayList<String> effectWordPaths;

	public PreTreatTool(ArrayList<String> docFilePaths) {
		this.docFilePaths = docFilePaths;
	}

	/**
	 * 获取文档有效词文件路径
	 * 
	 * @return
	 */
	public ArrayList<String> getEFWPaths() {
		return this.effectWordPaths;
	}

	/**
	 * 从文件中读取数据
	 * 
	 * @param filePath
	 *            单个文件
	 */
	private ArrayList<String> readDataFile(String filePath) {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();
		ArrayList<String> words = new ArrayList<>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		// 将每行词做拆分加入到总列表容器中
		for (String[] array : dataArray) {
			for (String word : array) {
				words.add(word);
			}
		}

		return words;
	}

	/**
	 * 对文档内容词汇进行预处理
	 */
	public void preTreatWords() {
		String baseOutputPath = "";
		int endPos = 0;
		ArrayList<String> tempWords = null;
		effectWordPaths = new ArrayList<>();

		for (String filePath : docFilePaths) {
			tempWords = readDataFile(filePath);
			filterWords(tempWords, true);

			// 重新组装出新的输出路径
			endPos = filePath.lastIndexOf(".");
			baseOutputPath = filePath.substring(0, endPos);

			writeOutOperation(tempWords, baseOutputPath + "-efword.txt");
			effectWordPaths.add(baseOutputPath + "-efword.txt");
		}
	}

	/**
	 * 
	 * 对文档中的词语进行过滤操作
	 * 
	 * @param words
	 *            待处理文档词语
	 * @param canRepeated
	 *            有效词是否可以重复
	 */
	private void filterWords(ArrayList<String> words, boolean canRepeated) {
		boolean isFilterWord;
		// 做形容词匹配
		Pattern adjPattern;
		// 做动词时态的匹配
		Pattern formerPattern;
		// 数字匹配
		Pattern numberPattern;
		Matcher adjMatcher;
		Matcher formerMatcher;
		Matcher numberMatcher;
		ArrayList<String> deleteWords = new ArrayList<>();

		adjPattern = Pattern.compile(".*(ly$|ful$|ing$)");
		formerPattern = Pattern.compile(".*ed$");
		numberPattern = Pattern.compile("[0-9]+(.[0-9]+)?");

		String w;
		for (int i = 0; i < words.size(); i++) {
			w = words.get(i);
			isFilterWord = false;

			for (String fw : FILTER_WORDS) {
				if (fw.equals(w)) {
					deleteWords.add(w);
					isFilterWord = true;
					break;
				}
			}

			if (isFilterWord) {
				continue;
			}

			adjMatcher = adjPattern.matcher(w);
			formerMatcher = formerPattern.matcher(w);
			numberMatcher = numberPattern.matcher(w);

			// 将词语统一小写字母化
			w = w.toLowerCase();

			// 如果是形容词,副词形式的或是纯数字的词,则进行过滤
			if (adjMatcher.matches() || numberMatcher.matches()) {
				deleteWords.add(w);
			} else if (formerMatcher.matches()) {
				// 如果是ed结尾表明是动词的在时态方面的变化,进行变化,转为原有动词的形式,截去最末尾2个额外添加的后缀词
				w = w.substring(0, w.length() - 2);
			}
			
			words.set(i, w);
		}

		// 进行无效词的过滤
		words.removeAll(deleteWords);
		deleteWords.clear();

		String s1;
		String s2;

		// 进行词语的去重
		for (int i = 0; i < words.size() - 1; i++) {
			s1 = words.get(i);

			for (int j = i + 1; j < words.size(); j++) {
				s2 = words.get(j);

				// 找到存在相同的词了,就挑出循环
				if (s1.equals(s2)) {
					deleteWords.add(s1);
					break;
				}
			}
		}

		// 删除多余重复的词语
		words.removeAll(deleteWords);
		words.addAll(deleteWords);
	}

	/**
	 * 将数据写出到磁盘文件操作,如果文件已经存在,则在文件尾部进行内容追加
	 * 
	 * @param buffer
	 *            当前写缓冲中的数据
	 * @param filePath
	 *            输出地址
	 */
	private void writeOutOperation(ArrayList<String> buffer, String filePath) {
		StringBuilder strBuilder = new StringBuilder();

		// 将缓冲中的数据组成字符写入到文件中
		for (String word : buffer) {
			strBuilder.append(word);
			strBuilder.append("\n");
		}

		try {
			File file = new File(filePath);
			PrintStream ps = new PrintStream(new FileOutputStream(file));
			ps.print(strBuilder.toString());// 往文件里写入字符串
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
로그인 후 복사
文档类Document.java:

package InvertedIndex;

import java.util.ArrayList;

/**
 * 文档类
 * @author lyq
 *
 */
public class Document {
	//文档的唯一标识
	int docId;
	//文档的文件地址
	String filePath;
	//文档中的有效词
	ArrayList<String> effectWords;
	
	public Document(ArrayList<String> effectWords, String filePath){
		this.effectWords = effectWords;
		this.filePath = filePath;
	}
	
	public Document(ArrayList<String> effectWords, String filePath, int docId){
		this(effectWords, filePath);
		this.docId = docId;
	}
}
로그인 후 복사
BSBI算法工具类BSBITool.java:

package InvertedIndex;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * BSBI基于磁盘的外部排序算法
 * 
 * @author lyq
 * 
 */
public class BSBITool {
	// 文档唯一标识ID
	public static int DOC_ID = 0;

	// 读缓冲区的大小
	private int readBufferSize;
	// 写缓冲区的大小
	private int writeBufferSize;
	// 读入的文档的有效词文件地址
	private ArrayList<String> effectiveWordFiles;
	// 倒排索引输出文件地址
	private String outputFilePath;
	// 读缓冲 1
	private String[][] readBuffer1;
	// 读缓冲2
	private String[][] readBuffer2;
	// 写缓冲区
	private String[][] writeBuffer;
	// 有效词与hashcode的映射
	private Map<String, String> code2word;

	public BSBITool(ArrayList<String> effectiveWordFiles, int readBufferSize,
			int writeBufferSize) {
		this.effectiveWordFiles = effectiveWordFiles;
		this.readBufferSize = readBufferSize;
		this.writeBufferSize = writeBufferSize;

		initBuffers();
	}

	/**
	 * 初始化缓冲区的设置
	 */
	private void initBuffers() {
		readBuffer1 = new String[readBufferSize][2];
		readBuffer2 = new String[readBufferSize][2];
		writeBuffer = new String[writeBufferSize][2];
	}

	/**
	 * 从文件中读取有效词并进行编码替换
	 * 
	 * @param filePath
	 *            返回文档
	 */
	private Document readEffectWords(String filePath) {
		long hashcode = 0;

		String w;
		Document document;
		code2word = new HashMap<String, String>();
		ArrayList<String> words;

		words = readDataFile(filePath);

		for (int i = 0; i < words.size(); i++) {
			w = words.get(i);

			hashcode = BKDRHash(w);
			hashcode = hashcode % 10000;

			// 将有效词的hashcode取模值作为对应的代表
			code2word.put(hashcode + "", w);
			w = hashcode + "";

			words.set(i, w);
		}

		document = new Document(words, filePath, DOC_ID);
		DOC_ID++;

		return document;
	}

	/**
	 * 将字符做哈希值的转换
	 * 
	 * @param str
	 *            待转换字符
	 * @return
	 */
	private long BKDRHash(String str) {
		int seed = 31; /* 31 131 1313 13131 131313 etc.. */
		long hash = 0;
		int i = 0;

		for (i = 0; i < str.length(); i++) {
			hash = (hash * seed) + (str.charAt(i));
		}

		return hash;

	}

	/**
	 * 根据输入的有效词输出倒排索引文件
	 */
	public void outputInvertedFiles() {
		int index = 0;
		String baseFilePath = "";
		outputFilePath = "";
		Document doc;
		ArrayList<String> tempPaths;
		ArrayList<String[]> invertedData1;
		ArrayList<String[]> invertedData2;

		tempPaths = new ArrayList<>();
		for (String filePath : effectiveWordFiles) {
			doc = readEffectWords(filePath);
			writeOutFile(doc);

			index = doc.filePath.lastIndexOf(".");
			baseFilePath = doc.filePath.substring(0, index);
			writeOutOperation(writeBuffer, baseFilePath + "-temp.txt");

			tempPaths.add(baseFilePath + "-temp.txt");
		}

		outputFilePath = baseFilePath + "-bsbi-inverted.txt";

		// 将中间产生的倒排索引数据进行总的合并并输出到一个文件中
		for (int i = 1; i < tempPaths.size(); i++) {
			if (i == 1) {
				invertedData1 = readInvertedFile(tempPaths.get(0));
			} else {
				invertedData1 = readInvertedFile(outputFilePath);
			}

			invertedData2 = readInvertedFile(tempPaths.get(i));

			mergeInvertedData(invertedData1, invertedData2, false,
					outputFilePath);

			writeOutOperation(writeBuffer, outputFilePath, false);
		}
	}

	/**
	 * 将文档的最终的倒排索引结果写出到文件
	 * 
	 * @param doc
	 *            待处理文档
	 */
	private void writeOutFile(Document doc) {
		// 在读缓冲区中是否需要再排序
		boolean ifSort = true;
		int index = 0;
		String baseFilePath;
		String[] temp;
		ArrayList<String> tempWords = (ArrayList<String>) doc.effectWords
				.clone();
		ArrayList<String[]> invertedData1;
		ArrayList<String[]> invertedData2;

		invertedData1 = new ArrayList<>();
		invertedData2 = new ArrayList<>();

		// 将文档的数据平均拆分成2份,用于读入后面的2个缓冲区中
		for (int i = 0; i < tempWords.size() / 2; i++) {
			temp = new String[2];
			temp[0] = tempWords.get(i);
			temp[1] = doc.docId + "";
			invertedData1.add(temp);

			temp = new String[2];
			temp[0] = tempWords.get(i + tempWords.size() / 2);
			temp[1] = doc.docId + "";
			invertedData2.add(temp);
		}

		// 如果是奇数个,则将最后一个补入
		if (tempWords.size() % 2 == 1) {
			temp = new String[2];
			temp[0] = tempWords.get(tempWords.size() - 1);
			temp[1] = doc.docId + "";
			invertedData2.add(temp);
		}

		index = doc.filePath.lastIndexOf(".");
		baseFilePath = doc.filePath.substring(0, index);
		mergeInvertedData(invertedData1, invertedData2, ifSort, baseFilePath
				+ "-temp.txt");
	}

	/**
	 * 合并读缓冲区数据写到写缓冲区中,用到了归并排序算法
	 * 
	 * @param outputPath
	 *            写缓冲区的写出的路径
	 */
	private void mergeWordBuffers(String outputPath) {
		int i = 0;
		int j = 0;
		int num1 = 0;
		int num2 = 0;
		// 写缓冲区下标
		int writeIndex = 0;

		while (readBuffer1[i][0] != null && readBuffer2[j][0] != null) {
			num1 = Integer.parseInt(readBuffer1[i][0]);
			num2 = Integer.parseInt(readBuffer2[j][0]);

			// 如果缓冲1小,则优先存缓冲1到写缓冲区中
			if (num1 < num2) {
				writeBuffer[writeIndex][0] = num1 + "";
				writeBuffer[writeIndex][1] = readBuffer1[i][1];

				i++;
			} else if (num2 < num1) {
				writeBuffer[writeIndex][0] = num2 + "";
				writeBuffer[writeIndex][1] = readBuffer1[j][1];

				j++;
			} else if (num1 == num2) {
				// 如果两个缓冲区中的数字一样,说明是同个有效词,先进行合并再写入
				writeBuffer[writeIndex][0] = num1 + "";
				writeBuffer[writeIndex][1] = readBuffer1[i][1] + ":"
						+ readBuffer2[j][1];

				i++;
				j++;
			}

			// 写的指针往后挪一位
			writeIndex++;

			// 如果写满写缓冲区时,进行写出到文件操作
			if (writeIndex >= writeBufferSize) {
				writeOutOperation(writeBuffer, outputPath);
				writeIndex = 0;
			}
		}

		if (readBuffer1[i][0] == null) {
			writeRemainReadBuffer(readBuffer2, j, outputPath);
		}

		if (readBuffer2[j][0] == null) {
			writeRemainReadBuffer(readBuffer1, j, outputPath);
		}
	}

	/**
	 * 将数据写出到磁盘文件操作,如果文件已经存在,则在文件尾部进行内容追加
	 * 
	 * @param buffer
	 *            当前写缓冲中的数据
	 * @param filePath
	 *            输出地址
	 */
	private void writeOutOperation(String[][] buffer, String filePath) {
		String word;
		StringBuilder strBuilder = new StringBuilder();

		// 将缓冲中的数据组成字符写入到文件中
		for (String[] array : buffer) {
			if (array[0] == null) {
				continue;
			}

			word = array[0];

			strBuilder.append(word);
			strBuilder.append(" ");
			strBuilder.append(array[1]);
			strBuilder.append("\n");
		}

		try {
			File file = new File(filePath);
			PrintStream ps = new PrintStream(new FileOutputStream(file));
			ps.print(strBuilder.toString());// 往文件里写入字符串
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * 将数据写出到磁盘文件操作,如果文件已经存在,则在文件尾部进行内容追加
	 * 
	 * @param buffer
	 *            当前写缓冲中的数据
	 * @param filePath
	 *            输出地址
	 * @param isCoded
	 *            是否以编码的方式输出
	 */
	private void writeOutOperation(String[][] buffer, String filePath, boolean isCoded) {
		String word;
		StringBuilder strBuilder = new StringBuilder();

		// 将缓冲中的数据组成字符写入到文件中
		for (String[] array : buffer) {
			if (array[0] == null) {
				continue;
			}

			if(!isCoded){
				word = code2word.get(array[0]);
			}else{
				word = array[0];
			}

			strBuilder.append(word);
			strBuilder.append(" ");
			strBuilder.append(array[1]);
			strBuilder.append("\n");
		}

		try {
			File file = new File(filePath);
			PrintStream ps = new PrintStream(new FileOutputStream(file));
			ps.print(strBuilder.toString());// 往文件里写入字符串
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 将剩余的读缓冲区中的数据读入写缓冲区中
	 * 
	 * @param remainBuffer
	 *            读缓冲区的剩余缓冲
	 * @param currentReadPos
	 *            当前的读取位置
	 * @param outputPath
	 *            写缓冲区的写出文件路径
	 */
	private void writeRemainReadBuffer(String[][] remainBuffer,
			int currentReadPos, String outputPath) {
		while (remainBuffer[currentReadPos][0] != null
				&& currentReadPos < readBufferSize) {
			removeRBToWB(remainBuffer[currentReadPos]);

			currentReadPos++;

			// 如果写满写缓冲区时,进行写出到文件操作
			if (writeBuffer[writeBufferSize - 1][0] != null) {
				writeOutOperation(writeBuffer, outputPath);
			}
		}

	}

	/**
	 * 将剩余读缓冲区中的数据通过插入排序的方式插入写缓冲区
	 * 
	 * @param record
	 */
	private void removeRBToWB(String[] record) {
		int insertIndex = 0;
		int endIndex = 0;
		long num1;
		long num2;
		long code = Long.parseLong(record[0]);

		// 如果写缓冲区目前为空,则直接加入
		if (writeBuffer[0][0] == null) {
			writeBuffer[0] = record;
			return;
		}

		// 寻找待插入的位置
		for (int i = 0; i < writeBufferSize - 1; i++) {
			if (writeBuffer[i][0] == null) {
				endIndex = i;
				break;
			}

			num1 = Long.parseLong(writeBuffer[i][0]);

			if (writeBuffer[i + 1][0] == null) {
				if (code > num1) {
					endIndex = i + 1;
					insertIndex = i + 1;
				}
			} else {
				num2 = Long.parseLong(writeBuffer[i + 1][0]);

				if (code > num1 && code < num2) {
					insertIndex = i + 1;
				}
			}
		}

		// 进行插入操作,相关数据进行位置迁移
		for (int i = endIndex; i > insertIndex; i--) {
			writeBuffer[i] = writeBuffer[i - 1];
		}
		writeBuffer[insertIndex] = record;
	}

	/**
	 * 将磁盘中的2个倒排索引数据进行合并
	 * 
	 * @param invertedData1
	 *            倒排索引为文件数据1
	 * @param invertedData2
	 *            倒排索引文件数据2
	 * @param isSort
	 *            是否需要对缓冲区中的数据进行排序
	 * @param outputPath
	 *            倒排索引输出文件地址
	 */
	private void mergeInvertedData(ArrayList<String[]> invertedData1,
			ArrayList<String[]> invertedData2, boolean ifSort, String outputPath) {
		int rIndex1 = 0;
		int rIndex2 = 0;

		// 重新初始化缓冲区
		initBuffers();

		while (invertedData1.size() > 0 && invertedData2.size() > 0) {
			readBuffer1[rIndex1][0] = invertedData1.get(0)[0];
			readBuffer1[rIndex1][1] = invertedData1.get(0)[1];

			readBuffer2[rIndex2][0] = invertedData2.get(0)[0];
			readBuffer2[rIndex2][1] = invertedData2.get(0)[1];

			invertedData1.remove(0);
			invertedData2.remove(0);
			rIndex1++;
			rIndex2++;

			if (rIndex1 == readBufferSize) {
				if (ifSort) {
					wordBufferSort(readBuffer1);
					wordBufferSort(readBuffer2);
				}

				mergeWordBuffers(outputPath);
				initBuffers();
			}
		}

		if (ifSort) {
			wordBufferSort(readBuffer1);
			wordBufferSort(readBuffer2);
		}

		mergeWordBuffers(outputPath);
		readBuffer1 = new String[readBufferSize][2];
		readBuffer2 = new String[readBufferSize][2];

		if (invertedData1.size() == 0 && invertedData2.size() > 0) {
			readRemainDataToRB(invertedData2, outputPath);
		} else if (invertedData1.size() > 0 && invertedData2.size() == 0) {
			readRemainDataToRB(invertedData1, outputPath);
		}
	}

	/**
	 * 剩余的有效词数据读入读缓冲区
	 * 
	 * @param remainData
	 *            剩余数据
	 * @param outputPath
	 *            输出文件路径
	 */
	private void readRemainDataToRB(ArrayList<String[]> remainData,
			String outputPath) {
		int rIndex = 0;
		while (remainData.size() > 0) {
			readBuffer1[rIndex][0] = remainData.get(0)[0];
			readBuffer1[rIndex][1] = remainData.get(0)[1];
			remainData.remove(0);

			rIndex++;

			// 读缓冲 区写满,进行写入到写缓冲区中
			if (readBuffer1[readBufferSize - 1][0] != null) {
				wordBufferSort(readBuffer1);

				writeRemainReadBuffer(readBuffer1, 0, outputPath);
				initBuffers();
			}
		}

		wordBufferSort(readBuffer1);

		writeRemainReadBuffer(readBuffer1, 0, outputPath);

	}

	/**
	 * 缓冲区数据进行排序
	 * 
	 * @param buffer
	 *            缓冲空间
	 */
【本文来自鸿网互联 (http://www.68idc.cn)】	private void wordBufferSort(String[][] buffer) {
		String[] temp;
		int k = 0;

		long num1 = 0;
		long num2 = 0;
		for (int i = 0; i < buffer.length - 1; i++) {
			// 缓冲区可能没填满
			if (buffer[i][0] == null) {
				continue;
			}

			k = i;
			for (int j = i + 1; j < buffer.length; j++) {
				// 缓冲区可能没填满
				if (buffer[j][0] == null) {
					continue;
				}
				// 获取2个缓冲区小块的起始编号值
				num1 = Long.parseLong(buffer[k][0]);
				num2 = Long.parseLong(buffer[j][0]);

				if (num2 < num1) {
					k = j;
				}
			}

			if (k != i) {
				temp = buffer[k];
				buffer[k] = buffer[i];
				buffer[i] = temp;
			}
		}
	}

	/**
	 * 从文件中读取倒排索引数据
	 * 
	 * @param filePath
	 *            单个文件
	 */
	private ArrayList<String[]> readInvertedFile(String filePath) {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		return dataArray;
	}

	/**
	 * 从文件中读取数据
	 * 
	 * @param filePath
	 *            单个文件
	 */
	private ArrayList<String> readDataFile(String filePath) {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();
		ArrayList<String> words = new ArrayList<>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		// 将每行词做拆分加入到总列表容器中
		for (String[] array : dataArray) {
			for (String word : array) {
				if (!word.equals("")) {
					words.add(word);
				}
			}
		}

		return words;
	}
}
로그인 후 복사
SPIMI算法工具类SPIMITool.java:

package InvertedIndex;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;

/**
 * SPIMI内存式单边扫描构建算法
 * @author lyq
 *
 */
public class SPIMITool {
	//倒排索引输出文件地址
	private String outputFilePath;
	// 读入的文档的有效词文件地址
	private ArrayList<String> effectiveWordFiles;
	// 内存缓冲区,不够还能够在增加空间
	private ArrayList<String[]> buffers;
	
	public SPIMITool(ArrayList<String> effectiveWordFiles){
		this.effectiveWordFiles = effectiveWordFiles;
	}
	
	/**
	 * 从文件中读取数据
	 * 
	 * @param filePath
	 *            单个文件
	 */
	private ArrayList<String> readDataFile(String filePath) {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();
		ArrayList<String> words = new ArrayList<>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		// 将每行词做拆分加入到总列表容器中
		for (String[] array : dataArray) {
			for (String word : array) {
				words.add(word);
			}
		}

		return words;
	}
 
	
	/**
	 * 根据已有的文档数据进行倒排索引文件的构建
	 * @param docs
	 * 文档集合
	 */
	private void writeInvertedIndex(ArrayList<Document> docs){
		ArrayList<String> datas;
		String[] recordData;
		
		buffers = new ArrayList<>();
		for(Document tempDoc: docs){
			datas = tempDoc.effectWords;
			
			for(String word: datas){
				recordData = new String[2];
				recordData[0] = word;
				recordData[1] = tempDoc.docId + "";
				
				addRecordToBuffer(recordData);
			}
		}
		
		//最后将数据写出到磁盘中
		writeOutOperation(buffers, outputFilePath);
	}
	
	/**
	 * 将新读入的数据记录读入到内存缓冲中,如果存在则加入到倒排记录表中
	 * @param insertedData
	 * 待插入的数据
	 */
	private void addRecordToBuffer(String[] insertedData){
		boolean isContained = false;
		String wordName;
		
		wordName = insertedData[0];
		for(String[] array: buffers){
			if(array[0].equals(wordName)){
				isContained = true;
				//添加倒排索引记录,以:隔开
				array[1] += ":" + insertedData[1];
				
				break;
			}
		}
		
		//如果没有包含,则说明是新的数据,直接添加
		if(!isContained){
			buffers.add(insertedData);
		}
	}
	
	/**
	 * 将数据写出到磁盘文件操作,如果文件已经存在,则在文件尾部进行内容追加
	 * @param buffer
	 * 当前写缓冲中的数据
	 * @param filePath
	 * 输出地址
	 */
	private void writeOutOperation(ArrayList<String[]> buffer, String filePath) {
		StringBuilder strBuilder = new StringBuilder();
		
		//将缓冲中的数据组成字符写入到文件中
		for(String[] array: buffer){
			strBuilder.append(array[0]);
			strBuilder.append(" ");
			strBuilder.append(array[1]);
			strBuilder.append("\n");
		}
		
		try {
			File file = new File(filePath);
			PrintStream ps = new PrintStream(new FileOutputStream(file));
			ps.println(strBuilder.toString());// 往文件里写入字符串
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * 构造倒排索引文件
	 */
	public void createInvertedIndexFile(){
		int docId = 1;
		String baseFilePath;
		String fileName;
		String p;
		int index1 = 0;
		int index2 = 0;
		Document tempDoc;
		ArrayList<String> words;
		ArrayList<Document> docs;
		
		outputFilePath = "spimi";
		docs = new ArrayList<>();
		p = effectiveWordFiles.get(0);
		//提取文件名称
		index1 = p.lastIndexOf("\\");
		baseFilePath = p.substring(0, index1+1);
		outputFilePath = baseFilePath + "spimi";
		
		for(String path: effectiveWordFiles){
			//获取文档有效词
			words = readDataFile(path);
			tempDoc = new Document(words, path, docId);
			
			docId++;
			docs.add(tempDoc);
			
			//提取文件名称
			index1 = path.lastIndexOf("\\");
			index2 = path.lastIndexOf(".");
			fileName = path.substring(index1+1, index2);
			
			outputFilePath += "-" + fileName;
		}
		outputFilePath += ".txt";
		
		//根据文档数据进行倒排索引文件的创建
		writeInvertedIndex(docs);
	}

}
로그인 후 복사
算法测试类Client.java:

package InvertedIndex;

import java.util.ArrayList;

/**
 * 倒排索引测试类
 * @author lyq
 *
 */
public class Client {
	public static void main(String[] args){
		//读写缓冲区的大小
		int readBufferSize;
		int writeBufferSize;
		String baseFilePath;
		PreTreatTool preTool;
		//BSBI基于磁盘的外部排序算法
		BSBITool bTool;
		//SPIMI内存式单边扫描构建算法
		SPIMITool sTool;
		//有效词文件路径
		ArrayList<String> efwFilePaths;
		ArrayList<String> docFilePaths;
		
		readBufferSize = 10;
		writeBufferSize = 20;
		baseFilePath = "C:\\Users\\lyq\\Desktop\\icon\\";
		docFilePaths = new ArrayList<>();
		docFilePaths.add(baseFilePath + "doc1.txt");
		docFilePaths.add(baseFilePath + "doc2.txt");
		
		//文档预处理工具类
		preTool = new PreTreatTool(docFilePaths);
		preTool.preTreatWords();
		
		//预处理完获取有效词文件路径
		efwFilePaths = preTool.getEFWPaths();
		bTool = new BSBITool(efwFilePaths, readBufferSize, writeBufferSize);
		bTool.outputInvertedFiles();
		
		sTool = new SPIMITool(efwFilePaths);
		sTool.createInvertedIndexFile();
	}
}
로그인 후 복사
算法的输出:

为了模拟出真实性,算法的输出都是以文件的形式。

首先是预处理类处理之后的有效词文件doc1-efword.txt和doc2-efword.txt:

mike
study
yesterday
got
last
exam
thinks
english
he
로그인 후 복사
可以看见,一些修饰词什么的已经被我过滤掉了。

下面是BSBI算法生成的中间文件,就是映射成编码的文件,也许你看了这些数值真实表示的是什么词语:

1426 0
1542 0
2540 0
3056 0
3325 0
4326 0
4897 0
6329 0
7327 0
로그인 후 복사
还有文档2的临时文件:

1426 1
1542 1
2540 1
3056 1
3325 1
4326 1
4897 1
6329 1
7327 1
로그인 후 복사
将这2个文档的信息进行合并最终输出的倒排索引文件为:

yesterday 0:1
mike 0:1
got 0:1
english 0:1
he 0:1
last 0:1
thinks 0:1
study 0:1
exam 0:1
로그인 후 복사
同样的SPIMI算法输出的结果:

mike 1:2
study 1:2
yesterday 1:2
got 1:2
last 1:2
exam 1:2
thinks 1:2
english 1:2
he 1:2
로그인 후 복사

 

算法小结

 

我在实现算法的过程中无疑低估了此算法的难度,尤其是BSBI的实现,因为中间读写缓冲区在做数据操作的时候,各种情况需要判断,诸如写缓冲区满了的时候要刷出到磁盘上,读缓冲区满的时候要通过归并排序移入读缓冲区中,这里面的判断实在过多,加上之前早期没有想到这个问题,导致算法可读性不是很好,就索性把缓冲区设大,先走通这个流程,所以这个算法大家还是以理解为主,就不要拿来实际运用了,同样对于SPIMI算法一样的道理,算法实现在这里帮助大家更好的理解吧,还有很多不足的地方。还有1点是文档内容预处理的时候,我只是象征性的进行过滤,真实的信息过滤实现复杂程度远远超过我所写的,这里包括了修饰词,时态词的变化,副词等等,这些有时还需要语义挖掘的一些知识来解决,大家意会即可。
 

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

CLIP-BEVFormer: BEVFormer 구조를 명시적으로 감독하여 롱테일 감지 성능을 향상시킵니다. CLIP-BEVFormer: BEVFormer 구조를 명시적으로 감독하여 롱테일 감지 성능을 향상시킵니다. Mar 26, 2024 pm 12:41 PM

위에 작성 및 저자의 개인적인 이해: 현재 전체 자율주행 시스템에서 인식 모듈은 중요한 역할을 합니다. 자율주행 시스템의 제어 모듈은 적시에 올바른 판단과 행동 결정을 내립니다. 현재 자율주행 기능을 갖춘 자동차에는 일반적으로 서라운드 뷰 카메라 센서, 라이더 센서, 밀리미터파 레이더 센서 등 다양한 데이터 정보 센서가 장착되어 다양한 방식으로 정보를 수집하여 정확한 인식 작업을 수행합니다. 순수 비전을 기반으로 한 BEV 인식 알고리즘은 하드웨어 비용이 저렴하고 배포가 용이하며, 출력 결과를 다양한 다운스트림 작업에 쉽게 적용할 수 있어 업계에서 선호됩니다.

C++에서 기계 학습 알고리즘 구현: 일반적인 과제 및 솔루션 C++에서 기계 학습 알고리즘 구현: 일반적인 과제 및 솔루션 Jun 03, 2024 pm 01:25 PM

C++의 기계 학습 알고리즘이 직면하는 일반적인 과제에는 메모리 관리, 멀티스레딩, 성능 최적화 및 유지 관리 가능성이 포함됩니다. 솔루션에는 스마트 포인터, 최신 스레딩 라이브러리, SIMD 지침 및 타사 라이브러리 사용은 물론 코딩 스타일 지침 준수 및 자동화 도구 사용이 포함됩니다. 실제 사례에서는 Eigen 라이브러리를 사용하여 선형 회귀 알고리즘을 구현하고 메모리를 효과적으로 관리하며 고성능 행렬 연산을 사용하는 방법을 보여줍니다.

C++sort 함수의 기본 원리와 알고리즘 선택을 살펴보세요. C++sort 함수의 기본 원리와 알고리즘 선택을 살펴보세요. Apr 02, 2024 pm 05:36 PM

C++정렬 함수의 맨 아래 계층은 병합 정렬을 사용하고 복잡도는 O(nlogn)이며 빠른 정렬, 힙 정렬 및 안정 정렬을 포함한 다양한 정렬 알고리즘 선택을 제공합니다.

인공지능이 범죄를 예측할 수 있을까? CrimeGPT의 기능 살펴보기 인공지능이 범죄를 예측할 수 있을까? CrimeGPT의 기능 살펴보기 Mar 22, 2024 pm 10:10 PM

인공지능(AI)과 법 집행의 융합은 범죄 예방 및 탐지의 새로운 가능성을 열어줍니다. 인공지능의 예측 기능은 범죄 행위를 예측하기 위해 CrimeGPT(범죄 예측 기술)와 같은 시스템에서 널리 사용됩니다. 이 기사에서는 범죄 예측에서 인공 지능의 잠재력, 현재 응용 프로그램, 직면한 과제 및 기술의 가능한 윤리적 영향을 탐구합니다. 인공 지능 및 범죄 예측: 기본 CrimeGPT는 기계 학습 알고리즘을 사용하여 대규모 데이터 세트를 분석하고 범죄가 발생할 가능성이 있는 장소와 시기를 예측할 수 있는 패턴을 식별합니다. 이러한 데이터 세트에는 과거 범죄 통계, 인구 통계 정보, 경제 지표, 날씨 패턴 등이 포함됩니다. 인간 분석가가 놓칠 수 있는 추세를 식별함으로써 인공 지능은 법 집행 기관에 권한을 부여할 수 있습니다.

탐지 알고리즘 개선: 고해상도 광학 원격탐사 이미지에서 표적 탐지용 탐지 알고리즘 개선: 고해상도 광학 원격탐사 이미지에서 표적 탐지용 Jun 06, 2024 pm 12:33 PM

01 전망 요약 현재로서는 탐지 효율성과 탐지 결과 간의 적절한 균형을 이루기가 어렵습니다. 우리는 광학 원격 탐사 이미지에서 표적 감지 네트워크의 효과를 향상시키기 위해 다층 특징 피라미드, 다중 감지 헤드 전략 및 하이브리드 주의 모듈을 사용하여 고해상도 광학 원격 감지 이미지에서 표적 감지를 위한 향상된 YOLOv5 알고리즘을 개발했습니다. SIMD 데이터 세트에 따르면 새로운 알고리즘의 mAP는 YOLOv5보다 2.2%, YOLOX보다 8.48% 우수하여 탐지 결과와 속도 간의 균형이 더 잘 이루어졌습니다. 02 배경 및 동기 원격탐사 기술의 급속한 발전으로 항공기, 자동차, 건물 등 지구 표면의 많은 물체를 묘사하기 위해 고해상도 광학 원격탐사 영상이 활용되고 있다. 원격탐사 이미지 해석에서 물체 감지

PyCharm 초보자 가이드: 대체 함수에 대한 종합 분석 PyCharm 초보자 가이드: 대체 함수에 대한 종합 분석 Feb 25, 2024 am 11:15 AM

PyCharm은 개발 효율성을 크게 향상시킬 수 있는 풍부한 기능과 도구를 갖춘 강력한 Python 통합 개발 환경입니다. 그 중 교체 기능은 개발 과정에서 자주 사용되는 기능 중 하나로, 개발자가 코드를 빠르게 수정하고 코드 품질을 향상시키는 데 도움을 줄 수 있습니다. 이 기사에서는 초보자가 이 기능을 더 잘 익히고 사용할 수 있도록 특정 코드 예제와 함께 PyCharm의 대체 기능을 자세히 소개합니다. 대체 기능 소개 PyCharm의 대체 기능은 개발자가 코드에서 지정된 텍스트를 빠르게 대체하는 데 도움이 될 수 있습니다.

58 초상화 플랫폼 구축에 알고리즘 적용 58 초상화 플랫폼 구축에 알고리즘 적용 May 09, 2024 am 09:01 AM

1. 58초상화 플랫폼 구축 배경 먼저, 58초상화 플랫폼 구축 배경에 대해 말씀드리겠습니다. 1. 기존 프로파일링 플랫폼의 전통적인 사고로는 더 이상 충분하지 않습니다. 사용자 프로파일링 플랫폼을 구축하려면 여러 비즈니스 라인의 데이터를 통합하여 정확한 사용자 초상화를 구축하는 데이터 웨어하우스 모델링 기능이 필요합니다. 그리고 알고리즘 측면의 기능을 제공해야 하며, 마지막으로 사용자 프로필 데이터를 효율적으로 저장, 쿼리 및 공유하고 프로필 서비스를 제공할 수 있는 데이터 플랫폼 기능도 있어야 합니다. 자체 구축한 비즈니스 프로파일링 플랫폼과 중간 사무실 프로파일링 플랫폼의 주요 차이점은 자체 구축한 프로파일링 플랫폼이 단일 비즈니스 라인에 서비스를 제공하고 필요에 따라 사용자 정의할 수 있다는 것입니다. 모델링하고 보다 일반적인 기능을 제공합니다. 2.58 Zhongtai 초상화 구성 배경의 사용자 초상화

Samsung S24ai 기능에 대한 자세한 소개 Samsung S24ai 기능에 대한 자세한 소개 Jun 24, 2024 am 11:18 AM

2024년은 AI 휴대폰 원년이다. AI 스마트 기술을 탑재해 휴대폰을 더욱 효율적이고 편리하게 사용할 수 있는 휴대폰이 늘어나고 있다. 최근 연초 출시된 갤럭시 S24 시리즈에서는 제너레이티브 AI 경험이 다시 한 번 향상됐다. 자세한 기능 소개는 아래에서 살펴보자. 1. 생성적 AI의 강력한 강화 Samsung Galaxy S24 시리즈는 Galaxy AI의 강화를 통해 많은 지능형 애플리케이션을 제공했습니다. 이러한 기능은 Samsung One UI6.1과 긴밀하게 통합되어 사용자가 언제든지 편리하고 지능적인 경험을 얻을 수 있습니다. 휴대전화의 성능과 사용 편의성을 향상시킵니다. 갤럭시 S24 시리즈가 개척한 원 앤 검색 기능은 사용자가 길게 누르기만 하면 되는 기능 중 하나입니다.

See all articles