/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Range.java * Copyright (C) 1999-2012 University of Waikato, Hamilton, New Zealand * */ package weka.core; import java.io.Serializable; import java.util.Enumeration; import java.util.Vector; /** * Class representing a range of cardinal numbers. The range is set by a * string representation such as: <P> * * <code> * first-last * 1,2,3,4 * </code> <P> * or combinations thereof. The range is internally converted from * 1-based to 0-based (so methods that set or get numbers not in string * format should use 0-based numbers). * * @author Len Trigg (trigg@cs.waikato.ac.nz) * @version $Revision: 8034 $ */ public class Range implements Serializable, RevisionHandler, CustomDisplayStringProvider { /** for serialization. */ static final long serialVersionUID = 3667337062176835900L; /** Record the string representations of the columns to delete. */ /*@non_null spec_public@*/Vector m_RangeStrings = new Vector(); /** Whether matching should be inverted. */ /*@spec_public@*/ boolean m_Invert; /** The array of flags for whether an column is selected. */ /*@spec_public@*/boolean [] m_SelectFlags; /** Store the maximum value permitted in the range. -1 indicates that no upper value has been set */ /*@spec_public@*/ int m_Upper = -1; /** Default constructor. */ //@assignable this.*; public Range() { } /** * Constructor to set initial range. * * @param rangeList the initial range * @throws IllegalArgumentException if the range list is invalid */ public Range(/*@non_null@*/ String rangeList) { setRanges(rangeList); } /** * Sets the value of "last". * * @param newUpper the value of "last" */ public void setUpper(int newUpper) { if (newUpper >= 0) { m_Upper = newUpper; setFlags(); } } /** * Gets whether the range sense is inverted, i.e. all <i>except</i> * the values included by the range string are selected. * * @return whether the matching sense is inverted */ //@ensures \result <==> m_Invert; public /*@pure@*/boolean getInvert() { return m_Invert; } /** * Sets whether the range sense is inverted, i.e. all <i>except</i> * the values included by the range string are selected. * * @param newSetting true if the matching sense is inverted */ public void setInvert(boolean newSetting) { m_Invert = newSetting; } /** * Gets the string representing the selected range of values. * * @return the range selection string */ public /*@non_null pure@*/String getRanges() { StringBuffer result = new StringBuffer(m_RangeStrings.size()*4); boolean first = true; char sep = ','; for (int i = 0; i < m_RangeStrings.size(); i++) { if (first) { result.append((String)m_RangeStrings.elementAt(i)); first = false; } else { result.append(sep + (String)m_RangeStrings.elementAt(i)); } } return result.toString(); } /** * Sets the ranges from a string representation. Note that setUpper() * must be called afterwards for ranges to be actually set internally. * * @param rangeList the comma separated list of ranges. The empty * string sets the range to empty. * @throws IllegalArgumentException if the rangeList was not well formed */ //@requires rangeList != null; //@assignable m_RangeStrings,m_SelectFlags; public void setRanges(String rangeList) { Vector<String> ranges = new Vector<String> (10); // Split the rangeList up into the vector while (!rangeList.equals("")) { String range = rangeList.trim(); int commaLoc = rangeList.indexOf(','); if (commaLoc != -1) { range = rangeList.substring(0, commaLoc).trim(); rangeList = rangeList.substring(commaLoc + 1).trim(); } else { rangeList = ""; } if (!range.equals("")) { ranges.addElement(range); } } m_RangeStrings = ranges; m_SelectFlags = null; } /** * Gets whether the supplied cardinal number is included in the current * range. * * @param index the number of interest * @return true if index is in the current range * @throws RuntimeException if the upper limit of the range hasn't been defined */ //@requires m_Upper >= 0; //@requires 0 <= index && index < m_SelectFlags.length; public /*@pure@*/ boolean isInRange(int index) { if (m_Upper == -1) { throw new RuntimeException("No upper limit has been specified for range"); } if (m_Invert) { return !m_SelectFlags[index]; } else { return m_SelectFlags[index]; } } /** * Constructs a representation of the current range. Being a string * representation, the numbers are based from 1. * * @return the string representation of the current range */ public /*@non_null pure@*/ String toString() { if (m_RangeStrings.size() == 0) { return "Empty"; } String result ="Strings: "; Enumeration enu = m_RangeStrings.elements(); while (enu.hasMoreElements()) { result += (String)enu.nextElement() + " "; } result += "\n"; result += "Invert: " + m_Invert + "\n"; try { if (m_Upper == -1) { throw new RuntimeException("Upper limit has not been specified"); } String cols = null; for (int i = 0; i < m_SelectFlags.length; i++) { if (isInRange(i)) { if (cols == null) { cols = "Cols: " + (i + 1); } else { cols += "," + (i + 1); } } } if (cols != null) { result += cols + "\n"; } } catch (Exception ex) { result += ex.getMessage(); } return result; } /** * Gets an array containing all the selected values, in the order * that they were selected (or ascending order if range inversion is on). * * @return the array of selected values * @throws RuntimeException if the upper limit of the range hasn't been defined */ //@requires m_Upper >= 0; public /*@non_null@*/ int [] getSelection() { if (m_Upper == -1) { throw new RuntimeException("No upper limit has been specified for range"); } int [] selectIndices = new int [m_Upper + 1]; int numSelected = 0; if (m_Invert) { for (int i = 0; i <= m_Upper; i++) { if (!m_SelectFlags[i]) { selectIndices[numSelected++] = i; } } } else { Enumeration enu = m_RangeStrings.elements(); while (enu.hasMoreElements()) { String currentRange = (String)enu.nextElement(); int start = rangeLower(currentRange); int end = rangeUpper(currentRange); for (int i = start; (i <= m_Upper) && (i <= end); i++) { if (m_SelectFlags[i]) { selectIndices[numSelected++] = i; } } } } int [] result = new int [numSelected]; System.arraycopy(selectIndices, 0, result, 0, numSelected); return result; } /** * Creates a string representation of the indices in the supplied array. * * @param indices an array containing indices to select. * Since the array will typically come from a program, indices are assumed * from 0, and thus will have 1 added in the String representation. * @return the string representation of the indices */ public static /*@non_null pure@*/String indicesToRangeList(/*@non_null@*/ int []indices) { StringBuffer rl = new StringBuffer(); int last = -2; boolean range = false; for(int i = 0; i < indices.length; i++) { if (i == 0) { rl.append(indices[i] + 1); } else if (indices[i] == last) { range = true; } else { if (range) { rl.append('-').append(last); range = false; } rl.append(',').append(indices[i] + 1); } last = indices[i] + 1; } if (range) { rl.append('-').append(last); } return rl.toString(); } /** Sets the flags array. */ protected void setFlags() { m_SelectFlags = new boolean [m_Upper + 1]; Enumeration enu = m_RangeStrings.elements(); while (enu.hasMoreElements()) { String currentRange = (String)enu.nextElement(); if (!isValidRange(currentRange)) { throw new IllegalArgumentException("Invalid range list at " + currentRange); } int start = rangeLower(currentRange); int end = rangeUpper(currentRange); for (int i = start; (i <= m_Upper) && (i <= end); i++) { m_SelectFlags[i] = true; } } } /** * Translates a single string selection into it's internal 0-based equivalent. * * @param single the string representing the selection (eg: 1 first last) * @return the number corresponding to the selected value */ protected /*@pure@*/ int rangeSingle(/*@non_null@*/ String single) { if (single.toLowerCase().equals("first")) { return 0; } if (single.toLowerCase().equals("last")) { return m_Upper; } int index = Integer.parseInt(single) - 1; if (index < 0) { index = 0; } if (index > m_Upper) { index = m_Upper; } return index; } /** * Translates a range into it's lower index. * * @param range the string representation of the range * @return the lower index of the range */ protected int rangeLower(/*@non_null@*/ String range) { int hyphenIndex; if ((hyphenIndex = range.indexOf('-')) >= 0) { return Math.min(rangeLower(range.substring(0, hyphenIndex)), rangeLower(range.substring(hyphenIndex + 1))); } return rangeSingle(range); } /** * Translates a range into it's upper index. Must only be called once * setUpper has been called. * * @param range the string representation of the range * @return the upper index of the range */ protected int rangeUpper(/*@non_null@*/ String range) { int hyphenIndex; if ((hyphenIndex = range.indexOf('-')) >= 0) { return Math.max(rangeUpper(range.substring(0, hyphenIndex)), rangeUpper(range.substring(hyphenIndex + 1))); } return rangeSingle(range); } /** * Determines if a string represents a valid index or simple range. * Examples: <code>first last 2 first-last first-4 4-last</code> * Doesn't check that a < b for a-b * * @param range the string to check * @return true if the range is valid */ protected boolean isValidRange(String range) { if (range == null) { return false; } int hyphenIndex; if ((hyphenIndex = range.indexOf('-')) >= 0) { if (isValidRange(range.substring(0, hyphenIndex)) && isValidRange(range.substring(hyphenIndex + 1))) { return true; } return false; } if (range.toLowerCase().equals("first")) { return true; } if (range.toLowerCase().equals("last")) { return true; } try { int index = Integer.parseInt(range); if ((index > 0) && (index <= m_Upper + 1)){ return true; } return false; } catch (NumberFormatException ex) { return false; } } /** * Returns the revision string. * * @return the revision */ public String getRevision() { return RevisionUtils.extract("$Revision: 8034 $"); } /** * Returns the custom display string. * * @return the string */ public String toDisplay() { if (getInvert()) return "inv(" + getRanges() + ")"; else return getRanges(); } /** * Main method for testing this class. * * @param argv one parameter: a test range specification */ public static void main(String [] argv) { try { if (argv.length == 0) { throw new Exception("Usage: Range <rangespec>"); } Range range = new Range(); range.setRanges(argv[0]); range.setUpper(9); range.setInvert(false); System.out.println("Input: " + argv[0] + "\n" + range.toString()); int [] rangeIndices = range.getSelection(); for (int i = 0; i < rangeIndices.length; i++) System.out.print(" " + (rangeIndices[i] + 1)); System.out.println(""); } catch (Exception ex) { System.out.println(ex.getMessage()); } } }