I'm having problem with optimizing a function AltOper. In set {a, b, c}, there is a given multiplication (or binomial operation, whatsoever) which does not follow associative law. AltOper gets string consists of a, b, c such as "abbac", and calculates any possible answers for the operation, such as ((ab)b)(ac) = c, (a(b(ba)))c = a. AltOper counts every operation (without duplication) which ends with a, b, c, and return it as a triple tuple.
Though this code runs well for small inputs, it takes too much time for bit bulky ones. I tried memoization for some small ones, but apparently it's not enough. Struggling some hours, I finally figured out that its time complexity is basically too large. But I couldn't find any better algorithm for calculating this. Can anyone suggest idea for enhancing (significantly) or rebuilding the code? No need to be specific, but just vague idea would also be helpful.
public long[] AltOper(String str){
long[] triTuple = new long[3]; // result: {number-of-a, number-of-b, number-of-c}
if (str.length() == 1){ // Ending recursion condition
if (str.equals("a")) triTuple[0]++;
else if (str.equals("b")) triTuple[1]++;
else triTuple[2]++;
return triTuple;
}
String left = "";
String right = str;
while (right.length() > 1){
// splitting string into two, by one character each
left = left + right.substring(0, 1);
right = right.substring(1, right.length());
long[] ltemp = AltOper(left);
long[] rtemp = AltOper(right);
// calculating possible answers from left/right split strings
triTuple[0] += ((ltemp[0] + ltemp[1]) * rtemp[2] + ltemp[2] * rtemp[0]);
triTuple[1] += (ltemp[0] * rtemp[0] + (ltemp[0] + ltemp[1]) * rtemp[1]);
triTuple[2] += (ltemp[1] * rtemp[0] + ltemp[2] * (rtemp[1] + rtemp[2]));
}
return triTuple;
}
One comment ahead: I would modify the signature to allow for a binary string operation, so you can easiely modify your "input operation".
java public long[] AltOper(BiFunction<long[], long[], long[]> op, String str) {
I recommend using some sort of lookup table for subportions you have already answered. You hinted that you tried this already:
I tried memoization for some small ones, but apparently it's not enough
I wonder what went wrong, since this is a good idea, especially since your input is strings, which are both quickly hashable and comparable, so putting them in a map is cheap. You just need to ensure, that the map does not block the entire memory by ensuring, that old, unused entries are dropped. Cache-like maps can do this. I leave it to you to find one that suites your personal preferences.
From there, I would run any recursions through the cache check, to find precalculated results in the map. Small substrings that would otherwise be calculated insanely often are then looked up quickly, which cheapens your algorithm drastically.
I rewrote your code a bit, to allow for various inputs (including different operations):
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
public class StringOpExplorer {
#Test
public void test() {
BiFunction<long[], long[], long[]> op = (left, right) -> {
long[] r = new long[3];
r[0] += ((left[0] + left[1]) * right[2] + left[2] * right[0]);
r[1] += (left[0] * right[0] + (left[0] + left[1]) * right[1]);
r[2] += (left[1] * right[0] + left[2] * (right[1] + right[2]));
return r;
};
long[] result = new StringOpExplorer().opExplore(op, "abcacbabc");
System.out.println(Arrays.toString(result));
}
#SuppressWarnings("serial")
final LinkedHashMap<String, long[]> cache = new LinkedHashMap<String, long[]>() {
#Override
protected boolean removeEldestEntry(final Map.Entry<String, long[]> eldest) {
return size() > 1_000_000;
}
};
public long[] opExplore(BiFunction<long[], long[], long[]> op, String input) {
// if the input is length 1, we return.
int length = input.length();
if (length == 1) {
long[] result = new long[3];
if (input.equals("a")) {
++result[0];
} else if (input.equals("b")) {
++result[1];
} else if (input.equals("c")) {
++result[2];
}
return result;
}
// This will check, if the result is already known.
long[] result = cache.get(input);
if (result == null) {
// This will calculate the result, if it is not yet known.
result = applyOp(op, input);
cache.put(input, result);
}
return result;
}
public long[] applyOp(BiFunction<long[], long[], long[]> op, String input) {
long[] result = new long[3];
int length = input.length();
for (int i = 1; i < length; ++i) {
// This might be easier to read...
String left = input.substring(0, i);
String right = input.substring(i, length);
// Subcalculation.
long[] leftResult = opExplore(op, left);
long[] rightResult = opExplore(op, right);
// apply operation and add result.
long[] operationResult = op.apply(leftResult, rightResult);
for (int d = 0; d < 3; ++d) {
result[d] += operationResult[d];
}
}
return result;
}
}
The idea of the rewrite was to introduce caching and to isolate the operation from the exploration. After all, your algorithm is in itself an operation, but not the 'operation under test'. So now you colud (theoretically) test any operation, by changing the BiFunction parameter.
This result is extremely fast, though I really wonder about the applicability...
Related
I implemented an experimental OOP language and now benchmark garbage collection using a Storage benchmark. Now I want to check/print the following benchmark for small depths (n=2, 3, 4,..).
The tree (forest with 4 subnode) is generated by the buildTreeDepth method. The code is as follows:
import java.util.Arrays;
public final class StorageSimple {
private int count;
private int seed = 74755;
public int randomNext() {
seed = ((seed * 1309) + 13849) & 65535;
return seed;
}
private Object buildTreeDepth(final int depth) {
count++;
if (depth == 1) {
return new Object[randomNext() % 10 + 1];
} else {
Object[] arr = new Object[4];
Arrays.setAll(arr, v -> buildTreeDepth(depth - 1));
return arr;
}
}
public Object benchmark() {
count = 0;
buildTreeDepth(7);
return count;
}
public boolean verifyResult(final Object result) {
return 5461 == (int) result;
}
public static void main(String[] args) {
StorageSimple store = new StorageSimple();
System.out.println("Result: " + store.verifyResult(store.benchmark()));
}
}
Is there a somewhat simple/straight forward way to print the tree generated by buildTreeDepth? Just the short trees of n=3, 4, 5.
As other has already suggested, you may choose some lib to do so. But if you just want a simple algo to test in command line, you may do the following, which I always use when printing tree in command line (write by handle, may have some bug. Believe you can get what this BFS algo works):
queue.add(root);
queue.add(empty);
int count = 1;
while (queue.size() != 1) {
Node poll = queue.poll();
if (poll == empty) {
count = 1;
queue.add(empty);
}
for (Node n : poll.getChildNodes()) {
n.setNodeName(poll.getNodeName(), count++);
queue.add(n);
}
System.out.println(poll.getNodeName());
}
Sample output:
1
1-1 1-2 1-3 1-4
1-1-1 1-1-2 1-1-3 1-2-1 1-2-2 1-3-1 1-3-2 1-4-1
...
And in your case you use array, which seems even easier to print.
Instead of using object arrays, use a List implementation like ArrayList. For an improved better result subclass ArrayList to also hold a 'level' value and add indentation to the toString() method.
I have a array list contains thousands of data.
For Example:
List<String> custNames = new ArrayList<String>();
custNames.add("John");
custNames.add("Tom");
custNames.add("Bart");
custNames.add("Tim");
custNames.add("Broad");
Now I want to get count of names only starting with 'T'. I used looping mechanism for my solution.
List<String> filterNames = new ArrayList<String>();
String nameStarts="T";
for(int i=0;i<custNames.size();i++)
{
if(custNames.get(i).toLowerCase().startsWith(nameStarts.toLowerCase()))
{
filterNames.add(custNames.get(i));
}
}
System.out.println(filterNames.size());
But I have very large collection of data in this custNames list.
Is there any different solution without using loop?
Thanks.
There is very good solution from Java 8 for your problem.
Try this,
long filterNameCount = custNames
.stream()
.parallel()
.filter((s) -> s.startsWith(nameStarts.toLowerCase()))
.count();
System.out.println(filterNameCount);
If you are open to using a third-party library, there are a few interesting options you could use with Eclipse Collections.
If you use the ArrayList as you have it above, you can use the LazyIterate utility as follows:
int count = LazyIterate.collect(custNames, String::toLowerCase)
.countWith(String::startsWith, nameStarts.toLowerCase());
Assert.assertEquals(2, count);
If you use the Eclipse Collections replacement for ArrayList, you can use the rich functional protocols available directly on MutableList:
MutableList<String> custNames =
Lists.mutable.with("John", "Tom", "Bart", "Tim", "Broad");
String nameStarts= "T";
int count = custNames.asLazy()
.collect(String::toLowerCase)
.countWith(String::startsWith, nameStarts.toLowerCase());
System.out.println(count);
Assert.assertEquals(2, count);
The serial API in Eclipse Collections is eager-by-default, which is why I called asLazy() first. The collect method would otherwise create another MutableList.
If you benchmark your code with your full set of data, the following parallel version of the code may be more performant:
MutableList<String> custNames =
Lists.mutable.with("John", "Tom", "Bart", "Tim", "Broad");
String nameStarts= "T";
int processors = Runtime.getRuntime().availableProcessors();
int batchSize = Math.max(1, custNames.size() / processors);
ExecutorService executor = Executors.newFixedThreadPool(processors);
int count = custNames.asParallel(executor, batchSize)
.collect(String::toLowerCase)
.countWith(String::startsWith, nameStarts.toLowerCase());
executor.shutdown();
Assert.assertEquals(2, count);
The asParallel() API in Eclipse Collections is lazy-by-default. The API forces you to pass in a an ExecutorService and an int batchSize. This gives you complete control over the parallelism.
You can also use the Stream API with all MutableCollections in Eclipse Collections because they extend java.util.Collection.
Note: I am a committer for Eclipse Collections.
You could also use a tree storage : it would very efficient for this kind of search. If you are stucked with a list the previous answered is a way to do.
remove all the items which dont start with "T" like this:
custNames.removeIf(p->!p.startsWith("T"));
you can make a copy out of your list and remove items not starting with "T".
First, you can shorten your initialization with Arrays.asList(T); Second, I would use a simple loop to build a table of counts once and then use that to determine the subsequent queries. Something like,
List<String> custNames = new ArrayList<String>(Arrays.asList("John", "Tom",
"Bart", "Tim", "Broad"));
int[] counts = new int[26];
for (String name : custNames) {
char ch = Character.toLowerCase(name.charAt(0));
counts[ch - 'a']++;
}
for (int i = 0; i < counts.length; i++) {
if (counts[i] > 0) {
System.out.printf("There are %d words that start with %c%n",
counts[i], (char) ('a' + i));
}
}
Which outputs
There are 2 words that start with b
There are 1 words that start with j
There are 2 words that start with t
Or, in the specific case - counts['t' - 'a'] is the count of words starting with t.
If you have more or less static list and perform search operation often you can sort your list or use TreeMap.
Also you don't need to create new list and get its size then. You can simply create a counter variable and increment it.
You can create your own sorting and finding implementation.
Consider the following:
public class ContainingArrayList<E> extends ArrayList<E> {
private Comparator<E> comparator;
public ContainingArrayList(Comparator<E> comparator) {
this.setComparator(comparator);
}
#Override
public boolean add(E e) {
// If the collection is empty or the new element is bigger than the last one, append it to the end of the collection
if(size() == 0 || comparator.compare(e, get(size()-1)) >= 0)
return super.add(e);
else {
for (int i = 0; i < size(); i++) {
int result = comparator.compare(e, get(i));
// If the new element is bigger than the current element, continue with the next element
if (result > 0) continue;
// If the new element is equal to the current element, no need to insert (you might insert of course)
if (result == 0) return false;
// Otherwise the new element is smaller than the current element, so insert it between the previous and the current element
super.add(i, e);
return true;
}
return super.add(e);
}
}
public E get(E containingElement) {
int start = 0;
int end = size()-1;
// If the element is the first one, return the first element
if(comparator.compare(containingElement, super.get(start)) == 0)
return super.get(start);
// If the element is the last one, return the last element
if(comparator.compare(containingElement, super.get(end)) == 0)
return super.get(end);
// Otherwise do a binary search
while(start != end) {
// Get the element between start and end positions
E mid = super.get(start + (end/2));
// Compare the two elements
int result = comparator.compare(containingElement, mid);
// If the middle element compared to the containing element is equal, return the middle element
if(result == 0) {
return mid;
}
// If the containing element is smaller than the middle, halve the end position
else if(result < 0) {
end = start + (end/2);
}
// If the containing element is bigger than the middle, set the start position to the middle position
else if(result > 0) {
start = start + (end/2);
}
}
return null;
}
public Comparator<E> getComparator() {
return comparator;
}
public void setComparator(Comparator<E> comparator) {
this.comparator = comparator;
}
}
The custom comparator is used to sort the elements and to find the element that starts with a specific character. This means that you can change the comparator implementation for your needs at any time or you can create a more dynamic finding solution.
Test:
public class SortFindTest {
public SortFindTest() {
ContainingArrayList<String> t = new ContainingArrayList<String>(new MyComparator());
t.add("John");
t.add("Tom");
t.add("Bart");
t.add("Tim");
t.add("Broad");
System.out.println(t.get("T"));
}
class MyComparator implements Comparator<String> {
#Override
public int compare(String o1, String o2) {
int o1c = o1.charAt(0);
int o2c = o2.charAt(0);
if(o1c == o2c)
return 0;
if(o1c > o2c)
return 1;
return -1;
}
}
public static void main(String[] args) {
new SortFindTest();
}
}
I'm not sure if this would be faster than Java 8 Stream API but it worth a try.
If the order in which the items are stored does not matter, you could store the names in a HashMap, where the first character of each name is the key, and an ArrayList of names with that first character are the values. And then all you need to do, assuming the HashMap is named customerList, is customerList.get("T").size().
Initializing HashList and Adding Customers
HashMap<Character, ArrayList<String>> customerList = new HashMap<Character, ArrayList<String>>();
int NUM_ALPHABETS = 26;
int ascii_char = 97;
for(int i = 0; i < NUM_ALPHABETS; i++){
char c = (char) ascii_char;
customerList.add(c, new ArrayList<String>());
ascii_char++;
}
customerList.get("t").add("Tony");
customerList.get("a").add("Alice");
customerList.get("b").add("Ben");
Getting Number of Customers Starting with "t"
int num_t = customerList.get("t").size();
I'm currently trying to match 2 objects based on their values. Except, it's not a.a = a.a, but a.a = a.b and a.b = b.a. This means that overriding equals is an option but it's certainly not the right option.
While sorting these objects will make the matching time quicker, the population will be small so it is unnecessary. Also, compareTo isn't exactly right either for the same reason given for equals.
Do I simply make my own method in case? There will be 4 fields to match which is why I am not using an if statement up front.
public boolean isOpposite(Object other) {
return (this.a == other.b) ? true : false;
}
There is also the possibility that the object will implement/extend a base object to take on more fields and implement its own way of matching.
I'm considering using a LinkedList because I know it to be quicker for use than ArrayList, however I've also been considering Maps.
Edit: better explanation of objects
public class Obj {
public String a;
public String b;
public String c;
public double d;
}
The relationships are as follows:
Obj obj1, obj2;
obj1.a == obj2.b //.equals for String of course
obj1.b == obj2.a
obj1.c == obj2.c
obj1.d == obj2.d * -1
Overriding the equals or compareTo is not the right way to go, as you've mentioned. Because there is an assumption that both methods should be transitive, i.e. A eq B and B eq C => A eq C but it doesn't hold for the "opposite" objects. It's good to know, because you can't define a equivalence class and partition it into subsets, but you need to find all the pairs (depending on your use case).
Not sure, what is your goal. If you have some containers with such objects and you need to find all pairs that suffice the condition, then I am afraid you'd need to do n^2 comparisons.
I'll probably create two hash sets, one with the originals and second with the opposites and ask if the second hash set contains the opposite of each member of original hash set.
I've done some testing and determined that the cleanest way I knew how to implement this was with using ArrayList<Obj>.
This was my implementation:
public static List<ObjGroup> getNewSampleGroup(int size) {
List<ObjGroup> sampleGroup = new ArrayList<ObjGroup>();
sampleGroup.add(new ObjGroup((generateNumbers(size, 1)))); //Positives
sampleGroup.add(new ObjGroup((generateNumbers(size, -1)))); //Negatives
return sampleGroup;
}
private static List<Obj> generateNumbers(int size, int x) {
List<Obj> sampleGroup = new ArrayList<Obj>();
for (int i = 0; i < size; i ++) {
Random rand = new Random();
String randC;
String randA;
String randB;
double randD;
if (x == 1) {
randD = rand.nextInt((maxP - minP + 1) + minP);
randA = "aval";// + String.valueOf(rand.nextInt((max - min + 1) + min));
randB = "bval";// + String.valueOf(rand.nextInt((max - min + 1) + min));
randC = "cval";// + String.valueOf(rand.nextInt((max - min + 1) + min));
} else {
randD = rand.nextInt((maxP - minP + 1) + minP) * -1;
randA = "bval";// + String.valueOf(rand.nextInt((max - min + 1) + min));
randB = "aval";// + String.valueOf(rand.nextInt((max - min + 1) + min));
randC = "cval";// + String.valueOf(rand.nextInt((max - min + 1) + min));
}
sampleGroup.add(new Obj(randA, randB, randC, randD));
}
return sampleGroup;
}
public List<ObjGroup> findMatches(List<ObjGroup> unmatchedList) {
List<Obj> pivotPos = unmatchedList.get(0).getObjs(); //First grouping are positives
List<Obj> pivotNeg = unmatchedList.get(1).getObjs(); //Second grouping are negatives
List<ObjGroup> matchedList = new ArrayList<ObjGroup>();
long iterations = 0;
Collections.sort(pivotPos);
Collections.sort(pivotNeg, Collections.reverseOrder());
for (Iterator<Obj> iteratorPos = pivotPos.iterator(); iteratorPos.hasNext();) {
final Obj focus = iteratorPos.next();
iteratorPos.remove(); //Remove this once pulled as you won't match it again.
for (Iterator<Obj> iteratorNeg = pivotNeg.iterator(); iteratorNeg.hasNext();) {
final Obj candidate = iteratorNeg.next();
if (compare(focus, candidate)) {
matchedList.add(new ObjGroup(new ArrayList<Obj>() {
{
add(focus);
add(candidate);
}
}));
iteratorNeg.remove(); //Remove this once matched as you won't match it again.
break;
}
iterations ++;
}
iterations ++;
}
return matchedList;
}
I ran this against a sample size of 4,000,000 psuedo random Obj objects. This was my output:
Starting matching test.
18481512007 iterations.
3979042 matched objects.
10479 unmatched objects.
Processing time: 44 minutes.
There were 1989521 number of matches found.
Closing matching test.
Background: Floating point numbers have rounding issues, so they should never be compared with "==".
Question: In Java, how do I test whether a list of Double contains a particular value. I am aware of various workarounds, but I am looking for the most elegant solution, presumably the ones that make use of Java or 3rd party library features.
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
// should be 1.38, but end up with 1.3800000000000001
Double d1 = new Double(1.37 + 0.01);
System.out.println("d1=" + d1);
// won't be able to test the element for containment
List<Double> list = new ArrayList<Double>();
list.add(d1);
System.out.println(list.contains(1.38));
}
}
Output is:
d1=1.3800000000000001
false
Thank you.
The general solution would be to write a utility method that loops through the list and checks if each element is within a certain a threshold of the target value. We can do slightly better in Java 8, though, using Stream#anyMatch():
list.stream().anyMatch(d -> (Math.abs(d/d1 - 1) < threshold))
Note that I am using the equality test suggested here.
If you're not using Java 8, I would write a simple utility method along the lines of this:
public static boolean contains(Collection<Double> collection, double key) {
for (double d : collection) {
if (Math.abs(d/key - 1) < threshold)
return true;
}
return false;
}
Note that you might need to add a special case to both of these approaches to check if the list contains 0 (or use the abs(x - y) < eps approach). That would just consist of adding || (abs(x) < eps && abs(y) < eps) to the end of the equality conditions.
Comparing bits wasn't a good idea. Similar to another post, but deals with NaN and Infinities.
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
// should be 1.38, but end up with 1.3800000000000001
Double d1 = 1.37d + 0.01d;
System.out.println("d1=" + d1);
// won't be able to test the element for containment
List<Double> list = new ArrayList<>();
list.add(d1);
System.out.println(list.contains(1.38));
System.out.println(contains(list, 1.38d, 0.00000001d));
}
public static boolean contains(List<Double> list, double value, double precision) {
for (int i = 0, sz = list.size(); i < sz; i++) {
double d = list.get(i);
if (d == value || Math.abs(d - value) < precision) {
return true;
}
}
return false;
}
}
You could wrap the Double in another class that provides a 'close enough' aspect to its equals method.
package com.michaelt.so.doub;
import java.util.HashSet;
import java.util.Set;
public class CloseEnough {
private Double d;
protected long masked;
protected Set<Long> similar;
public CloseEnough(Double d) {
this.d = d;
long bits = Double.doubleToLongBits(d);
similar = new HashSet<Long>();
masked = bits & 0xFFFFFFFFFFFFFFF8L; // 111...1000
similar.add(bits);
similar.add(bits + 1);
similar.add(bits - 1);
}
Double getD() {
return d;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CloseEnough)) {
return false;
}
CloseEnough that = (CloseEnough) o;
for(Long bits : this.similar) {
if(that.similar.contains(bits)) { return true; }
}
return false;
}
#Override
public int hashCode() {
return (int) (masked ^ (masked >>> 32));
}
}
And then some code to demonstrate it:
package com.michaelt.so.doub;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<CloseEnough> foo = new ArrayList<CloseEnough>();
foo.add(new CloseEnough(1.38));
foo.add(new CloseEnough(0.02));
foo.add(new CloseEnough(1.40));
foo.add(new CloseEnough(0.20));
System.out.println(foo.contains(new CloseEnough(0.0)));
System.out.println(foo.contains(new CloseEnough(1.37 + 0.01)));
System.out.println(foo.contains(new CloseEnough(0.01 + 0.01)));
System.out.println(foo.contains(new CloseEnough(1.39 + 0.01)));
System.out.println(foo.contains(new CloseEnough(0.19 + 0.01)));
}
}
The output of this code is:
false
true
true
true
true
(that first false is the compare with 0, just to show that it isn't finding things that aren't there)
CloseEnough is just a simple wrapper around the double that masks the lowest three bits for the hash code (enough that and also stores the valid set of similar numbers in a set. When doing an equals comparison, it uses the sets. Two numbers are equal if they contain a common element in their sets.
That said, I am fairly certain that there are some values that would be problematic with a.equals(b) being true and a.hashCode() == b.hashCode() being false that may still occur at edge conditions for the proper bit patterns - this would make some things (like HashSet and HashMap) 'unhappy' (and would likely make a good question somewhere.
Probably a better approach to this would instead be to extend ArrayList so that the indexOf method handles the similarity between the numbers:
package com.michaelt.so.doub;
import java.util.ArrayList;
public class SimilarList extends ArrayList<Double> {
#Override
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < this.size(); i++) {
if (get(i) == null) {
return i;
}
}
} else {
for (int i = 0; i < this.size(); i++) {
if (almostEquals((Double)o, this.get(i))) {
return i;
}
}
}
return -1;
}
private boolean almostEquals(Double a, Double b) {
long abits = Double.doubleToLongBits(a);
long bbits = Double.doubleToLongBits(b);
// Handle +0 == -0
if((abits >> 63) != (bbits >> 63)) {
return a.equals(b);
}
long diff = Math.abs(abits - bbits);
if(diff <= 1) {
return true;
}
return false;
}
}
Working with this code becomes a bit easier (pun not intended):
package com.michaelt.so.doub;
import java.util.ArrayList;
public class ListTest {
public static void main(String[] args) {
ArrayList foo = new SimilarList();
foo.add(1.38);
foo.add(1.40);
foo.add(0.02);
foo.add(0.20);
System.out.println(foo.contains(0.0));
System.out.println(foo.contains(1.37 + 0.01));
System.out.println(foo.contains(1.39 + 0.01));
System.out.println(foo.contains(0.19 + 0.01));
System.out.println(foo.contains(0.01 + 0.01));
}
}
The output of this code is:
false
true
true
true
true
In this case, the bit fiddling is done in the SimilarList based on the code HasMinimalDifference. Again, the numbers are converted into bits, but this time the math is done in the comparison rather than trying to work with the equality and hash code of a wrapper object.
Background: Floating point numbers have rounding issues, so they should never be compared with "==".
This is false. You do need to be awake when writing floating-point code, but reasoning about the errors that are possible in your program is in many cases straightforward. If you cannot do this, it behooves you to at least get an empirical estimate of how wrong your computed values are and think about whether the errors you're seeing are acceptably small.
What this means is that you cannot avoid getting your hands dirty and thinking about what your program is doing. If you're going to work with approximate comparisons, you need to have some idea of what differences mean that two values really are different and what differences mean that two quantities might be the same.
// should be 1.38, but end up with 1.3800000000000001
This is also false. Notice that the closest double to 1.37 is 0x1.5eb851eb851ecp+0 and the closest double to 0.01 is 0x1.47ae147ae147bp-7. When you add these, you get 0x1.6147ae147ae14f6p+0, which rounds to 0x1.6147ae147ae15p+0. The closest double to 1.38 is 0x1.6147ae147ae14p+0.
There are several reasons why two slightly different doubles do not compare ==. Here are two:
If they did so, that would break transitivity. (There would be a, b, and c such that a == b and b == c but !(a == c).
If they did so, carefully-written numerical code would stop working.
The real issue with trying to find a double in a list is that NaN does not compare == to itself. You may try using a loop that checks needle == haystack[i] || needle != needle && haystack[i] != haystack[i].
Right now I have my array sorting (which is better than getting an error) except it is sorting in the reverse than what I want it to sort in.
public static void sortDatabase(int numRecords, String[] sDeptArr,
int[] iCourseNumArr, int[] iEnrollmentArr)
{
System.out.println("\nSort the database. \n");
String sTemp = null;
int iTemp = 0;
int eTemp = 0;
String a, b = null;
for(int i=0; i<numRecords; i++)
{
int iPosMin = i+1;
for(int j=iPosMin; j<numRecords; j++)
{
a = sDeptArr[i];
b = sDeptArr[iPosMin];
if(a.compareTo(b) > 0)
{
sTemp= sDeptArr[j];
sDeptArr[j] = sDeptArr[iPosMin];
sDeptArr[iPosMin] = sTemp;
iTemp = iCourseNumArr[j];
iCourseNumArr[j] = iCourseNumArr[iPosMin];
iCourseNumArr[iPosMin] = iTemp;
eTemp = iEnrollmentArr[j];
iEnrollmentArr[j] = iEnrollmentArr[iPosMin];
iEnrollmentArr[iPosMin] = eTemp;
}
else if(sDeptArr[j].equals(sDeptArr[iPosMin]) && !(iCourseNumArr[j] < iCourseNumArr[iPosMin]))
{
sTemp= sDeptArr[i];
sDeptArr[i] = sDeptArr[iPosMin];
sDeptArr[iPosMin] = sTemp;
iTemp = iCourseNumArr[i];
iCourseNumArr[i] = iCourseNumArr[iPosMin];
iCourseNumArr[iPosMin] = iTemp;
eTemp = iEnrollmentArr[i];
iEnrollmentArr[i] = iEnrollmentArr[iPosMin];
iEnrollmentArr[iPosMin] = eTemp;
}
else continue;
}
}
}
Again, no array lists or array.sorts. I need just to reverse how this is sorting but I have no idea how.
just do a.compareTo(b) < 0 instead of the > 0
EDIT: I've figured out the problem. But since this is homework (thanks for being honest), I won't post my solution, but here are a few tips:
You are doing selection sort. The algorithm isn't as complicated as you made it. You only have to swap if the two elements you are checking are in the wrong order. I see you have 3 branches there, no need.
Take a look at when you are assigning a and b. Through the inner loop, where j is changing, a and b never change, because i and iPosMin stay the same. I hope that helps.
It's always good to break your algorithm down to discreet parts that you know works by extracting methods. You repeat the same swap code twice, but with different arguments for indices. Take that out and just make a:
-
// swaps the object at position i with position j in all arrays
private static void swap(String[] sDeptArr, int[] iCourseNumArr, int[] iEnrollmentArr, int i, int j)
Then you'll see you're code get a lot cleaner.
First I'd say you need to build a data structure to encapsulate the information in your program. So let's call it Course.
public class Course {
public String department;
public Integer courseNumber;
public Integer enrollment;
}
Why not use the built in sort capabilities of Java?
List<Course> someArray = new ArrayList<Course>();
...
Collections.sort( someArray, new Comparator<Course>() {
public int compare( Course c1, Course c2 ) {
int r = c1.compareTo( c2 );
if( r == 0 ) { /* the strings are the same sort by something else */
/* using Integer instead of int allows us
* to compare the two numbers as objects since Integer implement Comparable
*/
r = c1.courseNumber.compareTo( c2.courseNumber );
}
return r;
}
});
Hope that gets you an A on your homework. Oh and ditch the static Jr. Maybe one day your prof can go over why statics are poor form.
Hmm... I wonder what would happen of you altered the line that reads if(a.compareTo(b) > 0)?