Guava: Splitter and considering Escaping? - java

I am interested in the Splitting possibility of Guava:
Splitter.on("|").split("foo|bar|baz");
// => "foo", "bar", "baz"
This works correctly.
What now if I want to split on "|" but not between "[" and "]":
Splitter.on(something).split("foo|ba[r|ba]z");
// => "foo", "ba[r|ba]z"
From what I understood, it is not possible to define this "something" in Guava.
I found this:
Issue 799: Add google escape library to Guava. Is this related ?

The proper way to deal with this is to make a parser. It's really easy nowadays, just use a parser combinator, such as JParsec. You'll get something like this:
class ParserFactory {
Parser escapedSequence() {
return Parsers.between(Scanners.string("["),
Scanners.anyCharacterButNot("]"), Scanners.string("]"));
}
Parser chunk() {
return Parsers.or(escapedSequence(), Scanners.anyCharacterButNot("|"));
}
Parsers wholeThing() {
return Parsers.separatedBy(chunk().plus(), Scanners.string("|"));
}
}

Here's the code which works for given use case (Used existing Splitter code as a reference)
public class Splitter {
private final CharMatcher trimmer;
private final CharMatcher startTextQualifier;
private final CharMatcher endTextQualifier;
private final Strategy strategy;
private Splitter(Strategy strategy, CharMatcher trimmer, CharMatcher startTextQualifier, CharMatcher endTextQualifier) {
this.strategy = strategy;
this.trimmer = trimmer;
this.startTextQualifier = startTextQualifier;
this.endTextQualifier = endTextQualifier;
}
private Splitter(Strategy strategy) {
this(strategy, CharMatcher.NONE, CharMatcher.NONE, CharMatcher.NONE);
}
public Splitter trimResults(CharMatcher trimmer) {
checkNotNull(trimmer);
return new Splitter(strategy, trimmer, startTextQualifier, endTextQualifier);
}
public Splitter ignoreIn(CharMatcher startTextQualifier, CharMatcher endTextQualifier) {
checkNotNull(startTextQualifier);
checkNotNull(endTextQualifier);
return new Splitter(strategy, trimmer, startTextQualifier, endTextQualifier);
}
public Splitter ignoreIn(char startTextQualifier, char endTextQualifier) {
return ignoreIn(CharMatcher.is(startTextQualifier), CharMatcher.is(endTextQualifier));
}
public Splitter trimResults() {
return trimResults(CharMatcher.WHITESPACE);
}
public static Splitter on(final CharMatcher separatorMatcher) {
checkNotNull(separatorMatcher);
return new Splitter(new Strategy() {
#Override public SplittingIterator iterator(Splitter splitter, final CharSequence toSplit) {
return new SplittingIterator(splitter, toSplit) {
#Override int separatorStart(int start) {
boolean wrapped = false;
for (int i = start; i < toSplit.length(); i++) {
/**
* Suppose start text qualifier = '[' and end text qualifier = ']' then following code
* doesn't address cases for multiple start-end combinations i.e it doesn't see whether
* end is properly closed e.g. for configuration like - {#code
* Splitter.on("|")..ignoreIn('[', ']').split("abc|[abc|[def]ghi]|jkl")
* results -> abc, [abc|[def]ghi], jkl
}
*/
if (!wrapped && startTextQualifier.matches(toSplit.charAt(i))) {
wrapped = true;
} else if (wrapped && endTextQualifier.matches(toSplit.charAt(i))) {
wrapped = false;
}
if (!wrapped && separatorMatcher.matches(toSplit.charAt(i))) {
return i;
}
}
return -1;
}
#Override int separatorEnd(int separatorPosition) {
return separatorPosition + 1;
}
};
}
});
}
public static Splitter on(final String separator) {
checkArgument(!separator.isEmpty(), "The separator may not be the empty string.");
checkArgument(separator.length() <= 2, "The separator's max length is 2, passed - %s.", separator);
if (separator.length() == 1) {
return on(separator.charAt(0));
}
return new Splitter(new Strategy() {
#Override public SplittingIterator iterator(Splitter splitter, CharSequence toSplit) {
return new SplittingIterator(splitter, toSplit) {
#Override public int separatorStart(int start) {
int delimiterLength = separator.length();
boolean wrapped = false;
positions:
for (int p = start, last = toSplit.length() - delimiterLength; p <= last; p++) {
for (int i = 0; i < delimiterLength; i++) {
if (startTextQualifier.matches(toSplit.charAt(i))) {
wrapped = !wrapped;
}
if (!wrapped && toSplit.charAt(i + p) != separator.charAt(i)) {
continue positions;
}
}
return p;
}
return -1;
}
#Override public int separatorEnd(int separatorPosition) {
return separatorPosition + separator.length();
}
};
}
});
}
public static Splitter on(char separator) {
return on(CharMatcher.is(separator));
}
public Iterable<String> split(final CharSequence sequence) {
checkNotNull(sequence);
return new Iterable<String>() {
#Override public Iterator<String> iterator() {
return spliterator(sequence);
}
};
}
private Iterator<String> spliterator(CharSequence sequence) {
return strategy.iterator(this, sequence);
}
private interface Strategy {
Iterator<String> iterator(Splitter splitter, CharSequence toSplit);
}
private abstract static class SplittingIterator extends AbstractIterator<String> {
final CharSequence toSplit;
final CharMatcher trimmer;
final CharMatcher startTextQualifier;
final CharMatcher endTextQualifier;
/**
* Returns the first index in {#code toSplit} at or after {#code start}
* that contains the separator.
*/
abstract int separatorStart(int start);
/**
* Returns the first index in {#code toSplit} after {#code
* separatorPosition} that does not contain a separator. This method is only
* invoked after a call to {#code separatorStart}.
*/
abstract int separatorEnd(int separatorPosition);
int offset = 0;
protected SplittingIterator(Splitter splitter, CharSequence toSplit) {
this.trimmer = splitter.trimmer;
this.startTextQualifier = splitter.startTextQualifier;
this.endTextQualifier = splitter.endTextQualifier;
this.toSplit = toSplit;
}
#Override
protected String computeNext() {
if (offset != -1) {
int start = offset;
int separatorPosition = separatorStart(offset);
int end = calculateEnd(separatorPosition);
start = trimStartIfRequired(start, end);
end = trimEndIfRequired(start, end);
if (start != end)
return toSplit.subSequence(start, end).toString();
}
return endOfData();
}
private int calculateEnd(int separatorPosition) {
int end;
if (separatorPosition == -1) {
end = toSplit.length();
offset = -1;
} else {
end = separatorPosition;
offset = separatorEnd(separatorPosition);
}
return end;
}
private int trimEndIfRequired(int start, int end) {
while (end > start && trimmer.matches(toSplit.charAt(end - 1))) {
end--;
}
return end;
}
private int trimStartIfRequired(int start, int end) {
while (start < end && trimmer.matches(toSplit.charAt(start))) {
start++;
}
return start;
}
}
}
Small test -
public static void main(String[] args) {
Splitter splitter = Splitter.on("|").ignoreIn('[', ']');
System.out.println(Joiner.on(',').join(splitter.split("foo|ba[r|ba]z")));
// yields -> foo,ba[r|ba]z
}
Please note - this code isn't tested and does not address all the cases, feel free to modify as per your need.

Guava splitter is very powerful, it can handle regex separators, it can split into maps and more. But what you are trying to achieve is really out of scope of any generic parser.
You want a splitter with on/off switch. I believe the only way to do it is manually, something like this:
List<String> ls=new ArrayList<String>();
int b=0;
int j=0;
String str="foo|ba[r|ba]z";
int e=str.indexOf('|');
do{
if(b>j)
{
j=str.indexOf('[',j);
while(j>0 && e>=j){
j=str.indexOf(']',j);
if (j<0){
ls.add(str.substring(b));
return ;
}
j=str.indexOf('[',j);
}
}
ls.add(str.substring(b,e));
System.out.println(str.substring(b,e));
b=++e;
e=str.indexOf('|',e);
} while( e >= 0);
(Disclaimer: this code is just for giving an idea, it is not working)

Related

Java - TreeMap do not retrieves the key

I've topped with a problem I can not understand exactly what's happening here.
I operate with a TreeMap<Custom_class, TreeMap<Custom_class, Integer>>
Here is the fragment of code:
TreeMap<Coordenada, Integer> helper_tree;
boolean newLine = false;
for (Linea l : this.lineas) {
int helper = 0;
newLine = true;
Coordenada helper_co = null;
for (Coordenada c : l.getNodosLinea()) {
helper++;
if (!c.getEsEstacion() && !c.getEsCruce()) continue;
if (newLine) { map.putIfAbsent(c, new TreeMap<>()); helper_co = c; helper = 0; newLine = false; continue; }
helper_tree = new TreeMap<>();
helper_tree.put(helper_co, helper * 200);
map.put(c, helper_tree);
map.get(helper_co).put(c, helper * 200);
helper_co = c;
helper = 0;
}
}
In the execution the highlighted line fails, getting 0 entry for a key:
debug mode in intellij
And this is TreeMap structure:
TreeMap structure
I dont understand why in fails at .get(key) when the key Coordenada(12,2) is present. All before works just fine.
Coordenada class
public class Coordenada implements Comparable<Coordenada>{
private int[] coordenada = new int[2];
private boolean esEstacion = false;
private boolean esCruce = false;
public Coordenada(int[] coordenada){
this.coordenada[0] = coordenada[0];
this.coordenada[1] = coordenada[1];
}
public void setCoordenada(int[] coordenada) {
this.coordenada = coordenada;
}
public int[] getCoordenada() {
return coordenada;
}
public void switchEstacion(){
this.esEstacion = !this.esEstacion;
}
public void switchCruce() { this.esCruce = !this.esCruce; }
public boolean getEsEstacion() {
return this.esEstacion;
}
public boolean getEsCruce() { return this.esCruce; }
#Override
public boolean equals(Object coord){
Coordenada coordTemp = (Coordenada) coord;
if (this.coordenada[0] != coordTemp.coordenada[0])
return false;
if (this.coordenada[1] != coordTemp.coordenada[1])
return false;
return true;
}
#Override
public int compareTo(Coordenada o) {
if (this.coordenada[0] > o.coordenada[0] )
return 1;
if (this.coordenada[1] > o.coordenada[1] )
return 1;
if (this.coordenada[0] < o.coordenada[0])
return -1;
if (this.coordenada[1] < o.coordenada[1])
return -1;
return 0;
}
#Override
public String toString() {
return "(" + coordenada[0] + ", " + coordenada[1] + ")";
}
}
Inserts perfectly Coordenada(12,2) and modifies previous helper_co = Coordenada(10,2)
debugger variables
Thanks for any help!
Look at your compareTo function
(0,1) compareTo (1,0) returns 1
(1,0) compareTo (0,1) returns 1
It's ambiguous.

Suffix array & Binary Search

I have been following a tutorial I found. It is however in C++ and I'm using Java so there might have been a few things lost in translation. I've tried both googling and searching here and while there seem to be plenty of asked questions I still remain stuck. Though it feels like I'm very close.
According to the tutorial, there should be a match for the pattern 'nan' but there simply is no match when I'm running it. What am I missing? Sorry for code that unformated itself when pasted.
package u1;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Arrays;
import java.util.Scanner;
public class SuffixSort {
public Element[] processPattern(String pattern) {
Element[] patternArray = new Element[pattern.length()];
for (int i = 0; i < pattern.length(); i++) {
patternArray[i] = new Element(i, pattern.substring(i, pattern.length()));
}
Arrays.sort(patternArray);
return patternArray;
}
public void binarySearch(String text, String pattern, Element[] array) {
int left = 0, right = text.length() - 1;
int mid = 0, result;
while (left <= right) {
mid = left + (right - left) / 2;
result = pattern.compareTo(array[mid].getSuffix());
if (result == 0) {
System.out.println("Match: " + array[mid].getIndex());
return;
} else if (result < 0) {
right = mid - 1;
} else {
left = mid + 1;
}
}
}
public static void main(String[] args) {
try {
String text = "banana";
String pattern = "nan";
SuffixSort ss = new SuffixSort();
Scanner in = new Scanner(new FileReader("src/resources/100k.txt"));
/*
* while (in.hasNextLine()) { text += in.nextLine(); }
*/
Element[] suffixArray = ss.processPattern(text);
double runtime = System.nanoTime();
ss.binarySearch(text, pattern, suffixArray);
runtime = (System.nanoTime() - runtime) / 1000000;
in.close();
System.out.println(runtime);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
Other class
package u1;
public class Element implements Comparable<Element>{
private int index;
private String suffix;
public Element(int index, String suffix){
this.index = index;
this.suffix = suffix;
}
#Override
public int compareTo(Element o) {
return this.getSuffix().compareTo(o.getSuffix());
}
public int getIndex() {
return index;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}

How to get all possible combinations of substrings?

I have a String of following structure:
A1(N1,N2,N3)P4(O3,O5)Y1.
How to get all combinations? The rule is that options inside parenthesis should not go together. For this example the output should be:
A1N1P4O3Y1,
A1N2P4O3Y1,
A1N3P4O3Y1,
A1N1P4O5Y1,
A1N2P4O5Y1,
A1N3P4O5Y1.
There can be parenthesis, but it can be without it. Another example:
N3P5(L1,L2)Q1, output should be:
N3P5L1Q1,
N3P5L2Q1.
Anyone with elegant solution?
The main idea is to transform a string input into a StringTemplate that holds parts, that can be a single string or a group of strings.
For each part, a iterator is created. While some iterator can go next, update a string array that holds current part values and reset all iterators of parts that come before the part that changed. Feel free to clear repeated code and add nested groups support and syntax verifications if needed.
private static StringTemplate parse(String string) {
List<StringPart> parts = new ArrayList<StringPart>();
boolean insideGroup = false;
StringBuilder currentToken = new StringBuilder();
List<LiteralPart> groupParts = new ArrayList<LiteralPart>();
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
if (ch == '(') {
if (currentToken.length() != 0) {
parts.add(new LiteralPart(currentToken.toString()));
currentToken.delete(0, currentToken.length());
}
insideGroup = true;
} else if (ch == ')') {
if (insideGroup) {
if (currentToken.length() != 0) {
groupParts.add(new LiteralPart(currentToken.toString()));
currentToken.delete(0, currentToken.length());
}
parts.add(new CompositePart(groupParts));
groupParts.clear();
insideGroup = false;
} else {
currentToken.append(ch);
}
} else if (ch == ',') {
if (insideGroup) {
if (currentToken.length() != 0) {
groupParts.add(new LiteralPart(currentToken.toString()));
currentToken.delete(0, currentToken.length());
}
} else {
currentToken.append(ch);
}
} else {
currentToken.append(ch);
}
}
if (currentToken.length() != 0) {
parts.add(new LiteralPart(currentToken.toString()));
currentToken.delete(0, currentToken.length());
}
return new StringTemplate(parts);
}
private static final class StringTemplate {
private final List<StringPart> parts;
public StringTemplate(List<StringPart> parts) {
this.parts = parts;
}
public List<String> getCombinations() {
List<Iterator<String>> iterators = new ArrayList<Iterator<String>>(parts.size());
for (StringPart part : parts) {
iterators.add(part.getStrings().iterator());
}
String[] toJoin = new String[iterators.size()];
List<String> combinations = new ArrayList<String>();
int iteratorThatAdvanced;
int maxIteratorThatAdvanced = Integer.MIN_VALUE;
boolean first = true;
for (;;) {
iteratorThatAdvanced = -1;
for (int i = 0; i < iterators.size(); i++) {
Iterator<String> iterator = iterators.get(i);
if (first || iterator.hasNext()) {
String value = iterator.next();
toJoin[i] = value;
iteratorThatAdvanced = i;
if (!first && i >= maxIteratorThatAdvanced) {
maxIteratorThatAdvanced = i;
break;
}
}
}
if (iteratorThatAdvanced < 0) {
break;
}
if (!first) {
for (int i = 0; i < iteratorThatAdvanced; i++) {
Iterator<String> iterator = parts.get(i).getStrings().iterator();
iterators.set(i, iterator);
toJoin[i] = iterator.next();
}
}
combinations.add(join(toJoin));
first = false;
}
return combinations;
}
}
private static String join(String[] strings) {
StringBuilder builder = new StringBuilder();
for (String string : strings) {
builder.append(string);
}
return builder.toString();
}
private static abstract class StringPart {
abstract List<String> getStrings();
}
private static final class LiteralPart extends StringPart {
private final String literal;
public LiteralPart(String literal) {
this.literal = literal;
}
#Override
List<String> getStrings() {
return Collections.singletonList(literal);
}
}
private static final class CompositePart extends StringPart {
private final List<LiteralPart> parts;
public CompositePart(List<LiteralPart> parts) {
this.parts = new ArrayList<LiteralPart>(parts);
}
#Override
List<String> getStrings() {
List<String> strings = new ArrayList<String>(parts.size());
for (LiteralPart part : parts) {
strings.add(part.literal);
}
return strings;
}
}
Example:
public static void main(String[] args) {
StringTemplate template = parse("A1(N1,N2,N3)P4(O3,O5)Y1");
for (String combination : template.getCombinations()) {
System.out.println(combination);
}
template = parse("N3P5(L1,L2)Q1");
for (String combination : template.getCombinations()) {
System.out.println(combination);
}
}

Reverse a string using a recursive void method

So I'm trying to write a method that reverses a given string but the catch is that it has to be a void method rather than a return method which is making this difficult. My code seems logical to me but it doesn't work so I'm hoping someone can help me figure out where I'm going wrong.
public class Reverser {
public String text, revText;
/**
* #param args
*/
public static void main(String[] args) {
Reverser greeting = new Reverser("Buildings");
greeting.reverse();
System.out.println(greeting.getText());
}
public Reverser(String _text){
text = _text;
}
public void reverse(){
int len = text.length();
if(len >= 1){
String last = text.substring(text.length() - 1, text.length());
revText += last;
text = text.substring(0, text.length() - 1);
Reverser loop = new Reverser(text);
loop.reverse();
}
}
public String getText(){
return revText;
}
}
Here's an idea:
public class Reverser {
private int idx;
private String text, revText;
public static void main(String[] args) {
Reverser greeting = new Reverser("Buildings");
greeting.reverse();
System.out.println(greeting.getText());
}
public void reverse() {
if (idx == text.length())
return;
revText = text.charAt(idx) + revText;
idx++;
reverse();
}
public Reverser(String _text) {
idx = 0;
text = _text;
revText = "";
}
public String getText() {
return revText;
}
}
The fundamental difference with respect to your answer, is that I'm using an index attribute to keep track of where exactly I am in the recursion. In that way, I don't have to modify the original text attribute.
A slighty different version to what Oscar Lopez responded is this
public class Sentence{
private String sntce, rvrse;
private int idx;
public Sentence(String sentence){
sntce = sentence;
rvrse = "";
}
/**
A method to reverse a string recursively.
#return void.
*/
void reverse(){
if (idx == sntce.length()){
sntce = rvrse;
return;
}
rvrse = sntce.charAt(idx) + rvrse;
idx++;
reverse();
}
/**
To test reverse gives the appropriate value.
#return the value of sntce.
*/
public String getText(){
return sntce;
}
}
Here's a version that uses as few instance variables as possible. Unfortunately you need at least one instance variable to hold the final result (result). Otherwise the state is passed into each recursive call.
(PS, is this homework?)
public class RecursiveVoidStringReverser {
public static void main(String[] args) {
final RecursiveVoidStringReverser reverser = new RecursiveVoidStringReverser();
reverser.reverse("Welcome to the jungle!");
System.out.println("reverser.result = " + reverser.result());
}
private String result;
public void reverse(String s) {
if ("".equals(s)) {
result = s;
} else {
reverse(s.toCharArray(), 0);
}
}
private void reverse(char[] chars, int index) {
if (index > chars.length / 2) {
result = new String(chars);
} else {
char t = chars[index];
chars[index] = chars[chars.length - index - 1];
chars[chars.length - index - 1] = t;
reverse(chars, index+1);
}
}
public String result() {
return result;
}
}
Given that a string is immutable, you cannot change it in situ. If the only requirement is that there be no return value, and it's okay simply to print out the final string (or to place it into a class variable), then this would work fine for any strings of at least one character:
public static void main(String args[])
{
reverse("", "original string");
}
public static void reverse(String reversed, String original)
{
if(original.length() <= 1)
{
System.out.println(original.charAt(0) + reversed);
// (or set it into a shared variable)
return;
}
reverse(original.charAt(0) + reversed, original.substring(1));
}
This solution is going for procedural simplicity, not memory efficiency. This produces quite an unpleasant memory footprint, essentially creating two in-memory strings for each character in the original. It is, however, very logically simple.
Of course, if you're just dumping out to console, then you can achieve the same thing using an algorithm that's pretty much the same as one with a return value:
public static void reverse(String original)
{
if(original.length() < 1) return;
System.out.print(original.charAt(original.length() - 1));
reverse(original.substring(0, original.length() - 1));
}

writing a Comparator for a compound object for binary searching

I have a class, and list of instances, that looks something like this (field names changed to protect the innocent/proprietary):
public class Bloat
{
public long timeInMilliseconds;
public long spaceInBytes;
public long costInPennies;
}
public class BloatProducer
{
final private List<Bloat> bloatList = new ArrayList<Bloat>();
final private Random random = new Random();
public void produceMoreBloat()
{
int n = bloatList.size();
Bloat previousBloat = (n == 0) ? new Bloat() : bloatList.get(n-1);
Bloat newBloat = new Bloat();
newBloat.timeInMilliseconds =
previousBloat.timeInMilliseconds + random.nextInt(10) + 1;
newBloat.spaceInBytes =
previousBloat.spaceInBytes + random.nextInt(10) + 1;
newBloat.costInPennies =
previousBloat.costInPennies + random.nextInt(10) + 1;
bloatList.add(newBloat);
}
/* other fields/methods */
public boolean testMonotonicity()
{
Bloat previousBloat = null;
for (Bloat thisBloat : bloatList)
{
if (previousBloat != null)
{
if ((previousBloat.timeInMilliseconds
>= thisBloat.timeInMilliseconds)
|| (previousBloat.spaceInBytes
>= thisBloat.spaceInBytes)
|| (previousBloat.costInPennies
>= thisBloat.costInPennies))
return false;
}
previousBloat = thisBloat;
}
return true;
}
BloatProducer bloatProducer;
The list bloatList is kept internally by BloatProducer and is maintained in such a way that it only appends new Bloat records, does not modify any of the old ones, and each of the fields is monotonically increasing, e.g. bloatProducer.testMonotonicity() would always return true.
I would like to use Collections.binarySearch(list,key,comparator) to search for the Bloat record by either the timeInMilliseconds, spaceInBytes, or costInPennies fields. (and if the number is between two records, I want to find the previous record)
What's the easiest way to write a series of 3 Comparator classes to get this to work? Do I have to use a key that is a Bloat object with dummy fields for the ones I'm not searching for?
You'll need to write a separate comparator for each field you want to compare on:
public class BloatTimeComparator implements Comparator<Bloat> {
public int compare(Bloat bloat1, Bloat bloat2) {
if (bloat1.timeInMilliseconds > bloat2.timeInMilliseconds) {
return 1;
} else if (bloat1.timeInMilliseconds < bloat2.timeInMilliseconds) {
return -1;
} else {
return 0;
}
}
}
And so on for each property in Bloat you want to compare on (you'll need to create a comparator class for each). Then use the Collections helper method:
Collections.binarySearch(bloatList, bloatObjectToFind,
new BloatTimeComparator());
From the Java documentation for the binarySearch method, the return value will be:
the index of the search key, if it is contained in the list; otherwise, (-(insertion point) - 1). The insertion point is defined as the point at which the key would be inserted into the list: the index of the first element greater than the key, or list.size() if all elements in the list are less than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the key is found.
Which is the index you specified that you wanted.
You will need to have 3 separate Comparators if you want to search by each of the 3 properties.
A cleaner option would be to have a generic Comparator which receives a parameter which tells it by which field to compare.
A basic generic comparator should look something like this:
public class BloatComparator implements Comparator<Bloat>
{
CompareByEnum field;
public BloatComparator(CompareByEnum field) {
this.field = field;
}
#Override
public int compare(Bloat arg0, Bloat arg1) {
if (this.field == CompareByEnum.TIME){
// compare by field time
}
else if (this.field == CompareByEnum.SPACE) {
// compare by field space
}
else {
// compare by field cost
}
}
}
Here's a test-driven approach to writing the first comparator:
public class BloatTest extends TestCase{
public class Bloat {
public long timeInMilliseconds;
public long spaceInBytes;
public long costInPennies;
public Bloat(long timeInMilliseconds, long spaceInBytes, long costInPennies) {
this.timeInMilliseconds = timeInMilliseconds;
this.spaceInBytes = spaceInBytes;
this.costInPennies = costInPennies;
}
}
public void testMillisecondComparator() throws Exception {
Bloat a = new Bloat(5, 10, 10);
Bloat b = new Bloat(3, 12, 12);
Bloat c = new Bloat(5, 12, 12);
Comparator<Bloat> comparator = new MillisecondComparator();
assertTrue(comparator.compare(a, b) > 0);
assertTrue(comparator.compare(b, a) < 0);
assertEquals(0, comparator.compare(a, c));
}
private static class MillisecondComparator implements Comparator<Bloat> {
public int compare(Bloat a, Bloat b) {
Long aTime = a.timeInMilliseconds;
return aTime.compareTo(b.timeInMilliseconds);
}
}
}
If you want to leverage the binary search for all three properties, you have to create comparators for them and have additional Lists or TreeSets sorted by the comparators.
test program (MultiBinarySearch.java) to see if these ideas work properly (they appear to):
package com.example.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
class Bloat
{
final public long timeInMilliseconds;
final public long spaceInBytes;
final public long costInPennies;
static final private int N = 100;
public Bloat(long l1, long l2, long l3) {
timeInMilliseconds = l1;
spaceInBytes = l2;
costInPennies = l3;
}
public Bloat() { this(0,0,0); }
public Bloat moreBloat(Random r)
{
return new Bloat(
timeInMilliseconds + r.nextInt(N) + 1,
spaceInBytes + r.nextInt(N) + 1,
costInPennies + r.nextInt(N) + 1
);
}
public String toString() {
return "[bloat: time="+timeInMilliseconds
+", space="+spaceInBytes
+", cost="+costInPennies
+"]";
}
static int compareLong(long l1, long l2)
{
if (l2 > l1)
return -1;
else if (l1 > l2)
return 1;
else
return 0;
}
public static class TimeComparator implements Comparator<Bloat> {
public int compare(Bloat bloat1, Bloat bloat2) {
return compareLong(bloat1.timeInMilliseconds, bloat2.timeInMilliseconds);
}
}
public static class SpaceComparator implements Comparator<Bloat> {
public int compare(Bloat bloat1, Bloat bloat2) {
return compareLong(bloat1.spaceInBytes, bloat2.spaceInBytes);
}
}
public static class CostComparator implements Comparator<Bloat> {
public int compare(Bloat bloat1, Bloat bloat2) {
return compareLong(bloat1.costInPennies, bloat2.costInPennies);
}
}
enum Type {
TIME(new TimeComparator()),
SPACE(new SpaceComparator()),
COST(new CostComparator());
public Comparator<Bloat> comparator;
Type(Comparator<Bloat> c) { this.comparator = c; }
}
}
class BloatProducer
{
final private List<Bloat> bloatList = new ArrayList<Bloat>();
final private Random random = new Random();
public void produceMoreBloat()
{
int n = bloatList.size();
Bloat newBloat =
(n == 0) ? new Bloat() : bloatList.get(n-1).moreBloat(random);
bloatList.add(newBloat);
}
/* other fields/methods */
public boolean testMonotonicity()
{
Bloat previousBloat = null;
for (Bloat thisBloat : bloatList)
{
if (previousBloat != null)
{
if ((previousBloat.timeInMilliseconds
>= thisBloat.timeInMilliseconds)
|| (previousBloat.spaceInBytes
>= thisBloat.spaceInBytes)
|| (previousBloat.costInPennies
>= thisBloat.costInPennies))
return false;
}
previousBloat = thisBloat;
}
return true;
}
public int searchBy(Bloat.Type t, Bloat key)
{
return Collections.binarySearch(bloatList, key, t.comparator);
}
public void showSearch(Bloat.Type t, Bloat key)
{
System.out.println("Search by "+t+": ");
System.out.println(key);
int i = searchBy(t,key);
if (i >= 0)
{
System.out.println("matches");
System.out.println(bloatList.get(i));
}
else
{
System.out.println("is between");
i = -i-1;
Bloat b1 = (i == 0) ? null : bloatList.get(i-1);
System.out.println(b1);
Bloat b2 = (i >= bloatList.size()) ? null : bloatList.get(i);
System.out.println("and");
System.out.println(b2);
}
}
}
public class MultiBinarySearch {
private static int N = 1000;
public static void main(String[] args)
{
BloatProducer bloatProducer = new BloatProducer();
for (int i = 0; i < N; ++i)
{
bloatProducer.produceMoreBloat();
}
System.out.println("testMonotonicity() returns "+
bloatProducer.testMonotonicity());
Bloat key;
key = new Bloat(10*N, 20*N, 30*N);
bloatProducer.showSearch(Bloat.Type.COST, key);
bloatProducer.showSearch(Bloat.Type.SPACE, key);
bloatProducer.showSearch(Bloat.Type.TIME, key);
key = new Bloat(-10000, 0, 1000*N);
bloatProducer.showSearch(Bloat.Type.COST, key);
bloatProducer.showSearch(Bloat.Type.SPACE, key);
bloatProducer.showSearch(Bloat.Type.TIME, key);
}
}

Categories

Resources