Make longitude and latitude as Key of HashMap in Java - java

I have data like this:
23.3445556 72.4535455 0.23434
23.3645556 72.4235455 0.53434
23.3245556 72.4635455 0.21434
23.3645556 72.2535455 0.25434
I want to make HashMap like this:
HashMap<23.34444,72.23455,0.2345566> demo = new HashMap()
Here 23.34444,72.23455 is a key and 0.2345566 is value.
This is because I want to traverse HashMap like this:
if(demo.latitude < 21.45454545 && demo.longitude > 72.3455)
//get the value from hashMap
long lat repn particular pixel on the map,each pixel have same value , i want to get avg value from particular area suppose x y and pixel will be upto 1 million
And i want to know does this is good way since daily it will get millions hit

You could use the Point class to start off.
https://docs.oracle.com/javase/7/docs/api/java/awt/Point.html
int xE6 = x*1e6
int yE6 = y*1e6
new Point(xE6, yE6)
But since this is awt specific and a misuse of the class, you will probably eventually want to create your own.
public final class LatLon {
private double lat;
private double lon;
public LatLon(double lat, double lon) {
this.lat = lat;
this.lon = lon;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LatLon latLon = (LatLon) o;
if (Double.compare(latLon.lat, lat) != 0) return false;
return Double.compare(latLon.lon, lon) == 0;
}
#Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(lat);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(lon);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLon() {
return lon;
}
public void setLon(double lon) {
this.lon = lon;
}
}
(autogenerated using IntelliJ)
This can be used like
public static void main(String[] args) {
HashMap<LatLon, Double> demo = new HashMap<LatLon, Double>();
demo.put(new LatLon(23.3445556,72.4535455), 0.23434);
demo.put(new LatLon(23.3645556,72.4235455), 0.53434);
demo.put(new LatLon(23.3245556,72.4635455), 0.21434);
demo.put(new LatLon(23.3645556,72.2535455), 0.25434);
System.out.println(demo.get(new LatLon(23.3645556,72.2535455))); //0.25434
}
The problem with using this class as is, is that it uses doubles. You want some sort of precision, given by the decimal location.
doubles have strange math, and can give you accuracy errors so I heartily recommend using a library designed for geo-coordinates.
Especially given
if(demo.latitude<21.45454545 && demo.longitude >72.3455)
This sort of check is best served by some sort of purpose built collection for dealing with bounds checks and co-ordinates if you end up hitting performance problems.

I think you are approaching the problem the wrong way. Using a HashMap will not work correctly with greater than or lesser than comparisons. What would happen if you had 2 latlong keys that matched your comparison? What value do you choose?
I would probably solve your problem like this:
First, create a class that will contain both your "key" values and your "value" value
public class GeoValue {
double lat;
double lon;
double value;
}
Then, add a comparison method to the class
public boolean lessThanLatGreaterThanLon(double lat, double lon) {
return lat < this.lat && lon > this.lon;
}
Add all of those created objects to a Set type collection. If you use a HashSet, make sure that you also override .equals() and .hashCode methods for your GeoValue class.
To find the values you want you can use a filter method (if you're in Java8 or example)
final double lat = 3.5D;
final double lon = 4.5D;
Set<GeoValue> matchingValues = geoValues.stream()
.filter(geo -> geo.lessThanLatGreaterThanLon(lat, lon))
.collect(Collectors.toSet());
And you're ready to go.

If it's a demo you're creating I would suggest creating an enum class with each coordinate you want to showcase as a separate enum object or as a key to the HashMap.
If that doesn't work for you I would create a "Coordinates" class and store the keys there. You would have to override the hashcode and equals method though or it might not behave like you want it to.
Example
public class Coordinates {
double latitude, longitude;
}
...
HashMap<Coordinates, Double> demo = new HashMap<>(); /* Note: An object of Coordinates is the key. So, you first have to make an object of Coordinates class, put the latitude and longitude values and then put in the HashMap as key.*/

HashMap won't be of use for your needs, because it's not meant for range queries, i.e. give me the entry whose key is closest to 12.0, or give me all entries between keys 10.0 and 20.0.
There are special-purpose structures that deal with geo points efficiently, i.e. R-tree or R* tree.
These kind of trees require you to index your data based on a geo-point like structure, usually a latitude/longitude pair, though they also allow to index data based on geo-shapes.
Creating a lat/lon pair object to be used as the key (as suggested in other answers) is only useful if you use a specialized structure that stores and indexes spatial data. Otherwise, having such pair will be pointless, because you won't be able to search points that are near a given location, or points that lie within a given rectangle, etc.
Now, if you don't want to go the R-tree's way and you can live with quite limited spatial queries, you might want to consider using the following structure:
TreeMap<Double, TreeMap<Double, Double>> demo = new TreeMap<>();
This is a TreeMap of TreeMaps, and the idea is to have the latitude as the key of the outer map and the longitude as the key of the inner maps. So you will always have to search first by latitude, then by longitude.
If this is OK for you, you can take advantage of some very useful methods of TreeMap, such as headMap, tailMap and subMap, to name the most relevant ones.
For example, if you want to find all the points within the rectangle determined by its upper left corner [-10.0, -10.0] and its lower right corner [10.0, 10.0], you could do it as follows:
// Get all points with latitude between -10.0 and 10.0
SortedMap<Double, TreeMap<Double, Double>> byLat = demo.subMap(-10.0, 10.0);
// Now print points from byLat submap with longitude between -10.0 and 10.0
byLat.entrySet().stream()
.map(e -> e.getValue().subMap(-10.0, 10.0))
.forEach(System.out::println);
Even for 1 million points, performance will be reasonable, though not the best one, because TreeMap is a general purpose Map implementation based on a Red/Black tree, which has O(log n) time complexity.
On the other hand, if you are willing to install some software, I recommend you use Elasticsearch with Geolocation. It has geo-point and geo-shaped specialized datatypes that will make your life easier. This search engine has excellent performance and scales horizontally up to thousands of nodes, so memory, lookup times, etc won't be a problem.

You can generate a hashcode based on the longitude,latitude and then use that hashcode as key for save your values. That way it will be simpler instead of using them directly or converting them into a point as there is no use of the point at later point of time.

You can also make use of Point2D class available as part of java.awt. You will need to extend it and make a concrete class but it will give you equals/hashcode, etc. all built in. For integer coordinates you could just use the Point class from same library (no need to extend as well)

Related

Match two java collections by property avoiding ConcurrentModificationException (example of Google Roads api)

I haven't found any similar questions, where one collection is accessed while looping through another. Most of the questions about two collections and loops refer to simultaneous looping through both collections.
I'm using Google Road API in my application.
(documentation: https://developers.google.com/maps/documentation/roads/snap)
Simplified pre-history:
There is a polyline of car route. It should be corrected according to real roads coordinates and then colored according to speed at each point.
So, I have my own RoutePoint object:
public class RoutePoint{
private double latitude;
private double longitude;
private double speed;
}
Out of List<RoutePoint> routePoints I take latitudes and longitudes, construct request to Google Roads Api with interpolate = true and receive my fixed points with real roads coordinates, all good.
Each received snappedPoint (as Google calls them) has originalIndex property which is the index of each inital point if the request. Since I use interpolate= true, size of snappedPoints will usually be bigger than originalPoints and each interpolated point will have originalIndex = null.
public class SnappedPoint{
private SnappedLocation location;
private Integer originalIndex;
private String placeId;
}
Once I received my snappedPoints, my task is to draw colored polylines, depending on speed in each point. In order to do that I need to match original points with snapped points by originalIndex. Then, assign speed to each of the snappedPoints. So, I add speed to SnappedPoint class:
public class SnappedPoint{
private SnappedLocation location;
private Integer originalIndex;
private String placeId;
private double speed;
}
In order to match original points with snapped points I decided to loop through snappedPoints and when originalIndex matches routePoint index, copy the speed to snappedPoint:
int counter = 0;
for (int i = 0; snappedPoints.size() > i; i++) {
SnappedPoint snappedPoint = snappedPoints.get(i);
RoutePoint routePoint = routePoints.get(counter); //ConcurrentModificationException at this line
//do my match speed logic, but I can never come to this point
if (snappedPoint.getOriginalIndex() == null) {
snappedPoint.setSpeed(routePoint.getSpeed());
} else {
if (snappedPoint.getOriginalIndex() <= counter) {
snappedPoint.setSpeed(routePoint.getSpeed());
} else {
counter++;
}
}
}
Now, we've come to my problem. I get ConcurrentModificationException when trying to access routePoints.get(). As far as I understand it's because I'm accessing one collection while looping through another.
How to avoid this exception?
Or how to match original points with snapped points, avoiding this situation?

How do I separate the choices in a list

The purpose of the program is to calculate the volumes of different geometrical figures (Like a cylinder or a pyramid). I've started out by adding a list where the user can choose between the different figures.
The problem is that I don't know how to make the program know which formula to use. I need to be able to separate the choices instead of just making an int out of the answer.
private void btnAktiveraActionPerformed(java.awt.event.ActionEvent evt) {
String form = listForm.getSelectedValue().toString();
int fo = Integer.valueOf( form );
String höjd = txfHöjd.getText().toString();
int hö = Integer.valueOf( höjd );
String bredd = txfBredd.getText().toString();
int br = Integer.valueOf( bredd );
String radie = txfRadie.getText();
int ra = Integer.valueOf(radie);
String djup = txfDjup.getText();
int dj = Integer.valueOf(djup);
double ACyl = 3.14*ra*ra*hö;
double APyr = (br*dj*hö)/2;
double AKub = br*dj*hö;
double ARät = br*dj*hö;
txfHöjd.setEnabled(false);
txfBredd.setEnabled(false);
txfDjup.setEnabled(false);
txfRadie.setEnabled(false);
listForm.setEnabled(false);
}
private void btnBeräknaActionPerformed(java.awt.event.ActionEvent evt) {
// I know this code won't work, its just a reminder.
if (answer == Cyinder){
System.out.print("volymen är: "+ACyl+" cm^3");
}
}
I don't understand your question very clearly. I would suggest to make a plan to solve your problems.
make a list of figures that program will calculate
make a list of methods to count volumes of those figures
create individual classes, variables etc...
create methods
create main method with user input
You mentioned you don't know which formula to use. I assume there won't be many formulas in your program. I would create an individual method for each individual figure i.e. piramidFormula(), cilinderFormula()...
There is no point to refer to polimorphism when I think your level of programming is very basic at this stage.
I hope that will help you a little bit.
You need a list to hold the things, you seem to understand this just fine.
You need a way to select things. Selection is typically not exactly the same thing as the list, you need a class to be responsible for the "selection" behaviour.
Each thing has a routine that can calculate the volume. That means it will need input parameters. This is where it starts to get tricky, because if you want all of your things to be in the same list, you need to decide how to manage the different input parameters for the different types in the list.
public List<VolumeCalculations> volumeCalculations ...
public interface VolumeCalculation {
public double getVolume();
}
public class CubleCalcuation implements VolumeCalculation {
private double side = 0;
public void setSide(double value) {
this.side = value;
}
#Override
public double getVolume() {
return side*side*side;
}
}
the other volume calculations are left as an exercise to you.
Then you need to put them all in the list
volumeCalculations.add(new CubeVolumeCalculation());
...
But when you select the calculation, you will need "something" to ask for the right input.
public interface CalculationInputGather {
public void setCalcualtion(VolumeCalcuation value);
public void askForInputs();
}
which the one for the CubleCalcuation might look like
public CubeInputGather implements CalculationInputGatherer {
#Override
public void setCalculation(VolumeCalcualtion value) {
if (value instanceof CubeCalcuation) {
this.volume = value;
}
throw new IllegalArgumentException("value must be a CubeCalculation");
}
public void askForInputs() {
System.out.println("enter the side value:");
// read the value
volume.setSide(value);
}
}
then when you know the selected item in the list, you can use a Map of Calcuations to their input gatherers to lookup the right input gatherer for the selected calcuation.
If you already have the list for the user to choose from, maybe consider a map instead. You can have all your shapes as the keys of the map and then the formulas for volume as the values of the map. The list of shapes can be provided to the user via the keySet and their response can be matched back against the map to find the formula.
EDIT: You have your formulas for each shape inside an action event. You'll need to move those into a separate class
public static class Formulas() {
// list all formulas here
private String cylinder = "3.14*r*r*h";
}
Then when you hit the action you can either create a new instance of the Formulas class and use any convenience methods you might write in there.

How can I avoid using a lot of variables?

I would like to create a simple program that would output the atomic mass of any element entered. I am taking a Java course that I recently started so I don't know how to avoid using over 100 variables each with the elements atomic mass.
Also how could I get a if statement to use the name input from the user (which I know how to store in a string) and match it with one of the elements, in order to output the element's mass (corresponding to method used to store the multiple elements).
How can I condense this example code:
int carbon = 12;
int oxygen = 16;
int hydrogen = 1;
int sulfur = 32;
etc....
Sounds like your first step is to learn about the Map data structure. You can use it to associate the string names to integer values and then look them back up later.
Map<String, Integer> elements = new HashMap<String, Integer>();
elements.put("CARBON", 12);
elements.put("OXYGEN", 16);
//etc
Then if you have some input you can look up the number.
String userInput = scanner.next(); // or however you're getting input
Integer atomicWeight = elements.get(userInput.toUpper());
if (atomicWeight == null) //print element not found etc
Then once you have the program down and working you can learn about whatever technology is appropriate for loading the reference data from outside of the source code, whether that's a file or a database or a webservice or whatever.
I'd likely define an enum if confronted with this problem.
public enum Elements
{
HYDROGEN(1),
...
UNOBTANIUM(666);
public final int atomicWeight;
Elements(int atomicWeight)
{
this.atomicWeight = atomicWeight;
}
}
then to get the right element it's
String name = ...// from user input
Elements e = Elements.valueOf(name.toUpperCase());
I'd recommend using an enum as some have suggested, though i'd do it a little different. Maps have lots of overhead, and since your data is not dynamic it's not a great fit. Atomic mass should be a decimal value (double or BigDecimal depending on what you're using it for), not an int
public enum AtomicElement {
HYDROGEN(1.00794),
HELIUM(4.002602),
...;
private double atomicMass;
private AtomicElement (double atomicMass) {
this.atomicMass = atomicMass;
}
public int getAtomicNumber() {
return ordinal();
}
public double getAtomicMass() {
return atomicMass;
}
public static AtomicElement forAtomicNumber(int atomicNumber) {
return AtomicElement.values()[atomicNumber];
}
public static AtomicElement forElementName(String elementName) {
return AtomicElement.valueOf(elementName);
}
}
Then you can search by atomic number or element name
AtomicElement.forAtomicNumber(2);
AtomicElement.forElementName("CARBON");
This does however assume you're going to represent the entire periodic table with no gaps in the data, since it's using the ordinal() value as the atomic number. If you want gaps, you'll have to have an int field for the atomic number and your "forAtomicNumber" function will have to cycle through the "values()" to find the one with the given number.
You could even extend this if you wanted to include known isotopes, etc... if your requirements dictate that.
Because the atomic mass of the elements is not going to change at any point in your app, you should define them as final:
public class AtomicMass {
public static final int CARBON = 12;
public static final int OXYGEN = 16;
...
}
...or, you could use an enum:
public static enum Element {
carbon(12),
oxygen(16),
hydrogen(1),
sulfur(32);
private int atomicMass;
private Element( int mass ) {
this.atomicMass = mass;
}
}
If you order your elements sequentially (and add an UNKNOWN for 0) you wouldn't even need to explicitly provide the mass.
I like to group related data into arrays or arrayLists.
String[] elements = new String[# of elements in table];
Based on the position of the element you can have the atomic number.
Then I would loop through them to find any element or fill the array.
You can look into Java Scanner class to get input from user.
Create a class called Element that contains attributes like name, atomic number, etc. Each element will correspond to an instance of Element. You can then put all the Elementss in several maps, keyed by name, atomic number, etc. Use a factory class to instantiate and initialize the maps, and provide lookup methods.
If I understand you correctly you just want to only have 1 variable to store all the elements and their masses in which case I would recommend a HashMap. It will not really save on code lines but will let you do number 2 pretty easily. HashMaps store a set of key-value pairs and you can get the value if you have the key so this would create the list:
//Declare a new hashmap and initialize it
HashMap<String, Integer> elements = new HashMap<>();
//Add element information
elements.put("CARBON", 12);
elements.put("OXYGEN", 16);
elements.put("HYDROGEN", 1);
elements.put("SULFUR", 32);
Then for example to get user input from a dialog box and print the result to command line you do something like this:
//Collect user input and convert it to all upper case (in real life you would validate this)
String input = JOptionPane.showInputDialog(null, "Please enter an element name").toUpperCase();
//If element name exists in hashmap print its atomic weight
if(elements.containsKey(input.toUpperCase())){
System.out.println("Atomic Weight: " + elements.get(input));
}
Store your data in a file
Element, Weight
oxygen = 16
carbon, 12
.
.
.
Pseudocode:
//Read data file into a `Map<String, int>`
//Get user input
//Access map
//Output

Java MultiMap Not Recognizing Key

I'm trying to store multiple values for a key in a data structure so I'm using Guava (Google Collection)'s MultiMap.
Multimap<double[], double[]> destinations = HashMultimap.create();
destinations = ArrayListMultimap.create();
double[] startingPoint = new double[] {1.0, 2.0};
double[] end = new double[] {3.0, 4.0};
destinations.put(startingPoint, end);
System.out.println(destinations.containsKey(startingPoint));
and it returns false.
Note: Key-values are being stored in the multimap as the destinations.size() increases when I put something there.It also does not happen when keys are String instead of double[].
Any idea what the problem is?
Edit: Many thanks to Jon Skeet I now implemented the class:
class Point {
double lat;
double lng;
public boolean equals(Point p) {
if (lat == p.lat && lng == p.lng)
return true;
else
return false;
}
#Override
public int hashCode() {
int hash = 29;
hash = hash*41 + (int)(lat * 100000);
hash = hash*41 + (int)(lng * 100000);
return hash;
}
public Point(double newlat, double newlng) {
lat = newlat;
lng = newlng;
}
}
And now I have a new problem. This is how I'm using it:
Multimap<Point, Point> destinations = HashMultimap.create();
destinations = ArrayListMultimap.create();
Point startingPoint = new Point(1.0, 2.0);
Point end = new Point(3.0, 4.0);
destinations.put(startingPoint, end);
System.out.println( destinations.containsKey(startingPoint) );
System.out.println( destinations.containsKey(new Point(1.0, 2.0)) );
The first one returns true, the second one returns false. It gives me an error if I put #Override before the equals method.Any Idea what the problem is now?
Thanks :)
Edit2: It now behaves exactly as expected when I changed equals to this:
#Override
public boolean equals(Object p) {
if (this == p)
return true;
else if ( !(p instanceof Point) )
return false;
else {
Point that = (Point) p;
return (that.lat == lat) && (that.lng == lng);
}
}
Thanks everyone.
You're using arrays as the hash keys. That's not going to work - Java doesn't override hashCode and equals for arrays. (The Arrays class provides methods to do this, but it's not going to help you here.) Admittedly I'd expect it to work in this specific case, where you're using the exact same reference for both put and containsKey... When I test your code, it prints true. Are you sure you can reproduce it with exactly your code?
For example, while I'd expect it to work for the code you've given, I wouldn't expect this to work:
// Logically equal array, but distinct objects
double[] key = (double[]) startingPoint.clone();
System.out.println(destinations.containsKey(key));
It sounds like you shouldn't really be using double[] here - you should create a Point class which has two double variables, and overrides equals and hashCode.
Additionally, using double values in hash keys is usually a bad idea anyway, due to the nature of binary floating point arithmetic. That's going to be a problem even using the Point idea above... it should be okay if you don't need to actually do any arithmetic (if you're just copying values around) but take great care...
The problem is that you cannot hash "equal" arrays and get the same result each time. For example:
public static void main(String[] args) {
System.out.println(new double[]{1.0, 2.0}.hashCode());
System.out.println(new double[]{1.0, 2.0}.hashCode());
}
will result something like
306344348
1211154977

Is there a Java data structure that is effectively an ArrayList with double indicies and built-in interpolation?

I am looking for a pre-built Java data structure with the following characteristics:
It should look something like an ArrayList but should allow indexing via double-precision rather than integers. Note that this means that it's likely that you'll see indicies that don't line up with the original data points (i.e., asking for the value that corresponds to key "1.5"). EDIT: For clarity, based on the comments, I'm not looking to change the ArrayList implementation. I'm looking for a similar interface and developer experience.
As a consequence, the value returned will likely be interpolated. For example, if the key is 1.5, the value returned could be the average of the value at key 1.0 and the value at key 2.0.
The keys will be sorted but the values are not ensured to be monotonically increasing. In fact, there's no assurance that the first derivative of the values will be continuous (making it a poor fit for certain types of splines).
Freely available code only, please.
For clarity, I know how to write such a thing. In fact, we already have an implementation of this and some related data structures in legacy code that I want to replace due to some performance and coding issues.
What I'm trying to avoid is spending a lot of time rolling my own solution when there might already be such a thing in the JDK, Apache Commons or another standard library. Frankly, that's exactly the approach that got this legacy code into the situation that it's in right now....
Is there such a thing out there in a freely available library?
Allowing double values as indices is a pretty large change from what ArrayList does.
The reason for this is that an array or list with double as indices would almost by definition be a sparse array, which means it has no value (or depending on your definition: a fixed, known value) for almost all possible indices and only a finite number of indices have an explicit value set.
There is no prebuilt class in Java SE that supports all that.
Personally I'd implement such a data structure as a skip-list (or similar fast-searching data structure) of (index, value) tuples with appropriate interpolation.
Edit: Actually there's a pretty good match for the back-end storage (i.e. everything except for the interpolation): Simply use a NavigableMap such as a TreeMap to store the mapping from index to value.
With that you can easily use ceilingEntry() and (if necessary) higherEntry() to get the closest value(s) to the index you need and then interpolate from those.
If your current implementation has complexity O(log N) for interpolating a value, the implementation I just made up may be for you:
package so2675929;
import java.util.Arrays;
public abstract class AbstractInterpolator {
private double[] keys;
private double[] values;
private int size;
public AbstractInterpolator(int initialCapacity) {
keys = new double[initialCapacity];
values = new double[initialCapacity];
}
public final void put(double key, double value) {
int index = indexOf(key);
if (index >= 0) {
values[index] = value;
} else {
if (size == keys.length) {
keys = Arrays.copyOf(keys, size + 32);
values = Arrays.copyOf(values, size + 32);
}
int insertionPoint = insertionPointFromIndex(index);
System.arraycopy(keys, insertionPoint, keys, insertionPoint + 1, size - insertionPoint);
System.arraycopy(values, insertionPoint, values, insertionPoint + 1, size - insertionPoint);
keys[insertionPoint] = key;
values[insertionPoint] = value;
size++;
}
}
public final boolean containsKey(double key) {
int index = indexOf(key);
return index >= 0;
}
protected final int indexOf(double key) {
return Arrays.binarySearch(keys, 0, size, key);
}
public final int size() {
return size;
}
protected void ensureValidIndex(int index) {
if (!(0 <= index && index < size))
throw new IndexOutOfBoundsException("index=" + index + ", size=" + size);
}
protected final double getKeyAt(int index) {
ensureValidIndex(index);
return keys[index];
}
protected final double getValueAt(int index) {
ensureValidIndex(index);
return values[index];
}
public abstract double get(double key);
protected static int insertionPointFromIndex(int index) {
return -(1 + index);
}
}
The concrete interpolators will only have to implement the get(double) function.
For example:
package so2675929;
public class LinearInterpolator extends AbstractInterpolator {
public LinearInterpolator(int initialCapacity) {
super(initialCapacity);
}
#Override
public double get(double key) {
final double minKey = getKeyAt(0);
final double maxKey = getKeyAt(size() - 1);
if (!(minKey <= key && key <= maxKey))
throw new IndexOutOfBoundsException("key=" + key + ", min=" + minKey + ", max=" + maxKey);
int index = indexOf(key);
if (index >= 0)
return getValueAt(index);
index = insertionPointFromIndex(index);
double lowerKey = getKeyAt(index - 1);
double lowerValue = getValueAt(index - 1);
double higherKey = getKeyAt(index);
double higherValue = getValueAt(index);
double rate = (higherValue - lowerValue) / (higherKey - lowerKey);
return lowerValue + (key - lowerKey) * rate;
}
}
And, finally, a unit test:
package so2675929;
import static org.junit.Assert.*;
import org.junit.Test;
public class LinearInterpolatorTest {
#Test
public void simple() {
LinearInterpolator interp = new LinearInterpolator(2);
interp.put(0.0, 0.0);
interp.put(1.0, 1.0);
assertEquals(0.0, interp.getValueAt(0), 0.0);
assertEquals(1.0, interp.getValueAt(1), 0.0);
assertEquals(0.0, interp.get(0.0), 0.0);
assertEquals(0.1, interp.get(0.1), 0.0);
assertEquals(0.5, interp.get(0.5), 0.0);
assertEquals(0.9, interp.get(0.9), 0.0);
assertEquals(1.0, interp.get(1.0), 0.0);
interp.put(0.5, 0.0);
assertEquals(0.0, interp.getValueAt(0), 0.0);
assertEquals(0.0, interp.getValueAt(1), 0.0);
assertEquals(1.0, interp.getValueAt(2), 0.0);
assertEquals(0.0, interp.get(0.0), 0.0);
assertEquals(0.0, interp.get(0.1), 0.0);
assertEquals(0.0, interp.get(0.5), 0.0);
assertEquals(0.75, interp.get(0.875), 0.0);
assertEquals(1.0, interp.get(1.0), 0.0);
}
#Test
public void largeKeys() {
LinearInterpolator interp = new LinearInterpolator(10);
interp.put(100.0, 30.0);
interp.put(200.0, 40.0);
assertEquals(30.0, interp.get(100.0), 0.0);
assertEquals(35.0, interp.get(150.0), 0.0);
assertEquals(40.0, interp.get(200.0), 0.0);
try {
interp.get(99.0);
fail();
} catch (IndexOutOfBoundsException e) {
assertEquals("key=99.0, min=100.0, max=200.0", e.getMessage());
}
try {
interp.get(201.0);
fail();
} catch (IndexOutOfBoundsException e) {
assertEquals("key=201.0, min=100.0, max=200.0", e.getMessage());
}
}
private static final int N = 10 * 1000 * 1000;
private double measure(int size) {
LinearInterpolator interp = new LinearInterpolator(size);
for (int i = 0; i < size; i++)
interp.put(i, i);
double max = interp.size() - 1;
double sum = 0.0;
for (int i = 0; i < N; i++)
sum += interp.get(max * i / N);
return sum;
}
#Test
public void speed10() {
assertTrue(measure(10) > 0.0);
}
#Test
public void speed10000() {
assertTrue(measure(10000) > 0.0);
}
#Test
public void speed1000000() {
assertTrue(measure(1000000) > 0.0);
}
}
So the functionality seems to work. I only measured speed in some simple cases, and these suggest that scaling will be better than linear.
Update (2010-10-17T23:45+0200): I made some stupid mistakes in checking the key argument in the LinearInterpolator, and my unit tests didn't catch them. Now I extended the tests and fixed the code accordingly.
In the Apache commons-math library, if you implement the UnivariateRealInterpolator and the return value of its interpolate method which is typed UnivariateRealFunction you'll be most of the way there.
The interpolator interface takes two arrays, x[] and y[]. The returned function has a method, value() that takes an x' and returns the interpolated y'.
Where it fails to provide an ArrayList-like experience is in the ability to add more values to the range and domain as if the List is growing.
Additionally, they look to be in need of some additional interpolation functions. There are only 4 implementations in the library for the stable release. As a commenter pointed out, it seems to be missing 'linear' or something even simpler like nearest neighbor. Maybe that's not really interpolation...
That's a huge change from ArrayList.
Same as Joachim's response above, but I'd probably implement this as a binary tree, and when I didn't find something I was looking for, average the value of the next smallest and largest values, which should be quick to traverse to.
Your description that it should be "like an ArrayList" is misleading, since what you've described is a one dimensional interpolator and has essentially nothing in common with an ArrayList. This is why you're getting suggestions for other data structures which IMO are sending you down the wrong path.
I don't know of any available in Java (and couldn't easily find one one google), but I think you should have a look at GSL - GNU Scientific Library which includes a spline interpolator. It may be a bit heavy for what you're looking for since it's a two dimensional interpolator, but it seems like you should be looking for something like this rather than something like an ArrayList.
If you'd like it to "look like an ArrayList" you can always wrap it in a Java class which has access methods similar to the List interface. You won't be able to actually implement the interface though, since the methods are declared to take integer indices.

Categories