您的位置:首页 > 编程语言 > Java开发

Lucene价格区间(范围)搜索两种方法分析

2010-08-04 11:44 411 查看
Lucene API可以有两种方法进行价格区间搜索。(我们采用第二种方法)

1.      
采用RangeFilter过滤器进行价格区间搜索。
从名称上可以很容易地看出来,RangeFilter是用于过滤在一定范围内出现的文档
在JavaDoc中,对它是这样解释的。
A Filter that restricts search results to a
range of values in a given field.
中文翻译为:
该Filter用于将检索结果限定在某个给定的Field值的范围内。
具体用法是:
RangeFilter filter = new RangeFilter(“DerivedPrice”,”200”,”500”,true,true);
searcher.search(query,filter);
上述代码表示搜索价格区间(DerivedPrice为索引中价格域)在200到500的结果。
搜索结果为:
300.0,350.0,400.0,4000.0
问题出来了,为什么4000.0也在搜索结果中?
这是因为,Lucene在按照范围搜索时,将字段值都作为字符串,并按照字典顺序对字符串进行排序,由于4排在2和5之间,所以4000.0也出现在搜索结果中。当然排序还会对小数点进行排序,这样将导致搜索结果不是我们想要的结果。
怎么解决?
修改索引补零,即对索引中的价格域DerivedPrice进行补零操作,首先要对所有价格有清晰的整体认识,我们索引中的价格都为200,或200.5或200.00之类的数据,即小数点后最多2位,而且价格最大差不过10万,为保证万无一失我们先将索引原数据中的所有价格*100,这样就去掉了小数点,然后对所有价格不够10位的前面全部补零,这样可以接受的最大价格为100万。
例如:123变为   0000012300 

123.5 变为 0000012350  
123.55变为0000012355
然后重建索引,再采用我们上述的RangeFilter即可得到我们想要的范围搜索结果了。同时,搜出的结果价格要首先转换为Float,然后除于10才是真正的价格。
优点:可以采用Lucene的内部范围搜索机制,速度快,也省心。
缺点:需要对索引进行修改重建,而且搜出的结果需重新进行价格转换。
2.      
借用价格排序方法Sort进行升降序排列,然后采用二分查找法查找价格区间的两个端点,最后取得两个端点之间的值即为落在价格区间的搜索结果。
下面是价格区间封装类PriceRange:
/**
* 价格区间封装类,包括价格区间最小值,最大值和搜索后的升降序排列标示 默认为false升
*序排列,true标示降序排列
 *
 * @author 路卫杰
 * @version 1.0, 2010-7-30
 */
public class PriceRange {
    /** 价格区间最小值 */
    private float lower;
 
    /** 价格区间最大值 */
    private float upper;
 
    /** 价格区间排序方式,默认为false升序,true表示升序 */
    private boolean orderFlag = false;
 
    /** 价格区间排序方式,默认为false升序,true表示升序 */
    private Sort priceSort;
 
    /** 升序排列 */
    public final static boolean ASCENDING = false;
 
    /** 降序排列 */
    public final static boolean DESCENDING = true;
 
    /**
     * 构造方法
     *
     * @param lower
     *           
价格区间最低值
     * @param upper
     *           
价格区间最高值
     */
    public PriceRange(float lower, float upper) {
       if (lower <= upper) {
           this.lower = lower;
           this.upper = upper;
       } else {
           this.lower = upper;
           this.upper = lower;
       }
       this.priceSort = new Sort(new SortField("DerivedPrice", SortField.FLOAT,this.orderFlag));
//下面是本项目的价格Sort初始化方法,为方便交流,上面是Lucene的价格Sort生成方法
//SortBy.getSort(SortBy.PRICE, this.orderFlag);
 
    }
 
    /**
     * 构造方法
     *
     * @param lower
     *           
价格区间最低值
     * @param upper
     *           
价格区间最高值
     * @param orderFlag
     *           
价格区间搜索排序方法,false为升序,true为降序
     */
    public PriceRange(float lower, float upper, boolean orderFlag) {
       if (lower <= upper) {
           this.lower = lower;
           this.upper = upper;
       } else {
           this.lower = upper;
           this.upper = lower;
       }
       this.orderFlag = orderFlag;
       this.priceSort = new Sort(new SortField("DerivedPrice", SortField.FLOAT,this.orderFlag));
//下面是本项目的价格Sort初始化方法,为方便交流,上面是Lucene的价格Sort生成方法
//SortBy.getSort(SortBy.PRICE, this.orderFlag);
 
 
    }
   
    /**
     * 生成价格比lower更高的PriceRange对象
     *
     * @param lower
     *           
价格区间最低值
     *           

     * @return 价格比lower更高的PriceRange对象
     */
    public static PriceRange
More(float lower) {
       return new PriceRange(lower,
Float.MAX_VALUE);
    }
 
    /**
     * 生成价格比lower更高的PriceRange对象
     *
     * @param lower
     *           
价格区间最低值
     * @param orderFlag
     *           
价格区间搜索排序方法,false为升序,true为降序
     *           

     * @return 价格比lower更高的PriceRange对象
     */
    public static PriceRange
More(float lower, boolean orderFlag) {
       return new PriceRange(lower,
Float.MAX_VALUE, orderFlag);
    }
 
    /**
     * 生成价格比upper更低的PriceRange对象
     *
     * @param upper
     *           
价格区间最高值
     *           

     * @return 价格比upper更低的PriceRange对象
     */
    public static PriceRange
Less(float upper) {
       return new PriceRange(Float.MIN_VALUE, upper);
    }
   
    /**
     * 生成价格比upper更低的PriceRange对象
     *
     * @param upper
     *           
价格区间最高值
     * @param orderFlag
     *           
价格区间搜索排序方法,false为升序,true为降序
     *           

     * @return 价格比upper更低的PriceRange对象
     */
    public static PriceRange
Less(float upper, boolean orderFlag) {
       return new PriceRange(Float.MIN_VALUE, upper,
orderFlag);
    }
 
    public float getLower() {
       return lower;
    }
 
    public void setLower(float lower) {
       this.lower = lower;
    }
 
    public float getUpper() {
       return upper;
    }
 
    public void setUpper(float upper) {
       this.upper = upper;
    }
 
    public boolean isOrderFlag() {
       return orderFlag;
    }
 
    public void setOrderFlag(boolean orderFlag) {
       this.orderFlag = orderFlag;
    }
 
    public Sort getPriceSort() {
       return priceSort;
    }
 
    public void setPriceSort(Sort priceSort) {
       this.priceSort = priceSort;
    }
 
}
 
 
下面是对按照价格排序后的结果进行二分查找,查找价格区间两个端点在结果中的位置。
    /**
     * 二分查找算法,查找Hits对象中DerivedPrice值为value的Document在Hits中的位置
     *
     * @param hits
     *           
Hits对象
     * @param value
     *           
要查找的值
     * @param orderFlag
     *           
原数组升降序标示,false表示为升序排列,true表示为降序排列
     * @param flag
     *           
如果没有此值,orderFlag为false前提下:true表示取比其大的值的位置,false表示取比其小的值的位置;
     *           
orderFlag为true前提下:true表示取比其小的值的位置,false表示取比其大的值的位置。
     *
     * @throws IOException
     * @throws CorruptIndexException
     * @throws NumberFormatException
     *
     * @return Hits对象中DerivedPrice值为value的Document在Hits中的位置,如果value值不在hits中,若flag为true返回比value大的值的位置,若flag为false返回比value小的值的位置
     */
    private int binarySearch(Hits hits, float value, boolean orderFlag,
           boolean flag) throws NumberFormatException, CorruptIndexException,
           IOException {
       int low = 0;
       int high = hits.length() - 1;
 
       // 根据orderFlag标示循环进行二分查找
       if (orderFlag) {// 原数组为降序情况
           while (low <= high) {
              int mid = (low + high) / 2;
              float midVal = Float.parseFloat(hits.doc(mid).get(
                     "DerivedPrice"));
              if (midVal > value)
                  low = mid + 1;
              else if (midVal < value)
                  high = mid - 1;
              else {
                  // 查找第一次出现的位置
                  if (!flag) {
                     int i = mid - 1;
                     for (; i > 0; i--) {
                         if (Float.parseFloat(hits.doc(i)
                                .get("DerivedPrice")) == value)
                            continue;
                         else
                            break;
                     }
                     return ++i;
                  } else {
                     int i = mid + 1;
                     for (; i < hits.length(); i++) {
                         if (Float.parseFloat(hits.doc(i)
                                .get("DerivedPrice")) == value)
                            continue;
                         else
                            break;
                     }
                     return --i;
                  }
              }
 
           }
           // 如果flag为false返回比其大的值的位置,否则返回比其小的值的位置
           if (!flag)
              return (low < hits.length() ? low : hits.length() - 1);
           else
              return (high >= 0 ? high : 0);
       } else {// 原数组为升序情况
           while (low <= high) {
              int mid = (low + high) / 2;
              float midVal = Float.parseFloat(hits.doc(mid).get(
                     "DerivedPrice"));
 
              if (midVal < value)
                  low = mid + 1;
              else if (midVal > value)
                  high = mid - 1;
              else {
                  // 查找第一次出现的位置
                  if (flag) {
                     int i = mid - 1;
                     for (; i > 0; i--) {
                         if (Float.parseFloat(hits.doc(i)
                                .get("DerivedPrice")) == value)
                            continue;
                         else
                            break;
                     }
                     return ++i;
                  } else {
                     int i = mid + 1;
                     for (; i < hits.length(); i++) {
                         if (Float.parseFloat(hits.doc(i)
                                .get("DerivedPrice")) == value)
                            continue;
                         else
                            break;
                     }
                     return --i;
                  }
              }
 
           }
           // 如果flag为true返回比其大的值的位置,否则返回比其小的值的位置
           if (flag)
              return (low < hits.length() ? low : hits.length() - 1);
           else
              return (high >= 0 ? high : 0);
       }
 
    }
 
 
对二分查找的调用如下:
/** 价格区间最小值在hits中的位置 */
           int low = binarySearch(hits, lower, orderFlag, true);
 
           /** 价格区间最大值在hits中的位置 */
           int up = binarySearch(hits, upper, orderFlag, false);
 
           // 价格区间是否有结果标志,默认isFlag为true
           boolean isFlag = true;
          
           // 如果orderFlag为false,即hits中价格为升序且low>up 或
           // 如果orderFlag为true,即原hits中价格为降序且low<up
           // 则表明价格区间中没有结果,将isFlag置false
           if ((!orderFlag && low > up) || (orderFlag
&& low < up))
              isFlag = false;
           int totalRecords = 0;
           if (low >= 0 && up >= 0  && isFlag) {
              // 如果up小于low,则进行交换
              if (up < low) {
                  int mid = up;
                  up = low;
                  low = mid;
              }
 
              // 在价格区间的总数目
              totalRecords
= up - low + 1;
             
}
 
             // 根据总数目生成Page对象
              page = PageUtil.createPage(page, totalRecords);
 
              // 此页的开始记录和结束记录
              int start = page.getBeginIndex();
              int end = start + page.getEveryPage();
 
              // 如果end大于totalRecords则取totalRecords值
              end = (end < totalRecords ? end : totalRecords);
 
              // 获取搜索结果
              for (int i = low + start; i < low + end; i++) {
                  Document doc = hits.doc(i);
                  OutShow outShow = new OutShow();
                  outShow.setProduct(doc.get("Product"));
                  outShow.setUrl(doc.get("Page_url"));
                  outShow.setDescription(doc.get("Description"));
                  outShow.setOnHand(doc.get("OnHand"));
                  outShow.setCharSet(doc.get("Charset"));
                  outShow.setCrawlertime(doc.get("Crawlertime"));
                  outShow.setAddress(doc.get("Address"));
                  outShow.setProvider(doc.get("Provider"));
                  outShow.setSite(doc.get("Site"));
                  float credit = Float
                         .valueOf(doc.get("DerivedCreditPoint") == null ? "0.0"
                                : doc.get("DerivedCreditPoint"));
                  outShow.setCreditPoint(credit);
                  float price1 = Float
                         .valueOf(doc.get("DerivedPrice") == null ? "0.0"
                                : doc.get("DerivedPrice"));
                  outShow.setPrice(price1);
                  int itemSold = Integer
                         .valueOf(doc.get("DerivedItemSold") == null ? "0"
                                : doc.get("DerivedItemSold"));
                  outShow.setItemSold(itemSold);
                  outShow.setScore(doc.getBoost());
                  outShow.setDerivedCatNum(doc.get("DerivedCatNum"));
                  outShow.setPictureUrl(doc.get("PictureUrl"));
 
                  list.add(outShow);
 
              }
 
           }
           System.out.println("共搜索到 " + totalRecords + " 个结果!");
 
       }
 
 
对PriceRange的调用如下:
PriceRange pRange=new
PriceRange(Float.parseFloat("2324"),Float.parseFloat("2340"));
search.search(page,pRange);
 
优点:不用修改索引,搜出的结果也无需对价格进行转换
缺点:无法在价格区间的搜索的同时对其它字段进行排序。由于本方法使用的是价格排序,然后对结果进行二分查找,所以无法同时对其它字段进行排序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息