Determine phone number prefix with Trie in Java - java

I am trying to create a fast search function to determine the prefix of a phone number.
I am loading prefix data from database into memory as TreeMap, where key is prefix and value is an object containing information about this prefix(country etc).
This is how TreeMap is populated:
private static TreeMap<String, PrefixData> prefixMap = new TreeMap<>();
//EXAMPLE of DB query
try {
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager.getConnection(dbURL, dbUser, dbPass);
connection.setAutoCommit(false);
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM countries_prefixes");
//Looping resultset
while (rs.next()) {
//TODO Review fields that must be stored in memory
String country = rs.getString("name");
//Populating map with data object (keeping nr prefix as key and object as value)
prefixMap.put(rs.getString("country_prefix"), new PrefixData(country));
}
rs.close();
stmt.close();
connection.close();
} catch (ClassNotFoundException | SQLException e) {
System.out.println(e.toString());
}
Lets say I have phone numbers I want to check:
37251845632;
35844021546;
34651478966
etc ...
Some prefixes are 1 digit long, some 2 digits long, some 3 digits long and so on...
So I created a loop, that works:
//TODO Try some other search methods (Tries)
//array for prefix length priority
int[] sequence = {3, 4, 2, 1, 5, 6};
//Performing search from the map
for (int i = 0; i < sequence.length; i++) {
//Extracting prefix from phone nr
String prefix = phoneNr.substring(1, sequence[i] + 1);
if (prefixMap.containsKey(prefix)) {
PrefixData pdata = prefixMap.get(prefix);
System.out.println(String.format("Found matching key [%s] in TreeMap", prefix));
System.out.println(String.format("[NAME: %s] [REGEX: %s] ", pdata.getCountryName(), pdata.getNrRegex()));
//Validate number format with regex
if (pdata.getNrRegex().trim() != null && !pdata.getNrRegex().trim().isEmpty()) {
System.out.println("Regex for number validation is present!");
if (phoneNr.matches(pdata.getNrRegex().replaceAll("^/|/$", ""))) {
System.out.println("NUMBER IS VALID!");
} else {
System.out.println("INVALID NUMBER!");
}
}
return pdata;
}
}
return null;
}
Now the loop works well, but it is slow. I've heard something about Tries, which is faster, but I don't understand how to implement this in my scenario.
Any help is appreciated!

As I said, the loop works, but this is not a nice way to achieve my goal.
So I did a little bit of research and came up with solution that is using prefix tree (Trie) implementation.
Little reading what Trie is can be found here.
And now the Trie implementation part. I knew that it would be faster to find a code that is already written and tested, so I found Google implementation here. And Vladimir Kroz's implementation here.
Made some minor modifications and this is the solution. I will provide both solutions:
Prefixmap interface
package tiesImpl;
/**
* Maps string prefixes to values. For example, if you {#code put("foo", 1)},
* {#code get("foobar")} returns {#code 1}. Prohibits null values.
*
* <p>Use instead of iterating over a series of string prefixes calling
* {#code String.startsWith(prefix)}.
*
* #author crazybob#google.com (Bob Lee)
* #param <T>
*/
public interface PrefixMap<T> {
/**
* Maps prefix to value.
*
* #param prefix
* #param value
* #return The previous value stored for this prefix, or null if none.
* #throws IllegalArgumentException if prefix is an empty string.
*/
T put(CharSequence prefix, T value);
/**
* Finds a prefix that matches {#code s} and returns the mapped value.
*
* If multiple prefixes in the map match {#code s}, the longest match wins.
*
* #param s
* #return value for prefix matching {#code s} or {#code null} if none match.
*/
T get(CharSequence s);
}
PrefixTrie class
package tiesImpl;
/*
* Copyright (C) 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Trie implementation supporting CharSequences as prefixes.
*
* Prefixes are sequences of characters, and the set of allowed characters is
* specified as a range of sequential characters. By default, any seven-bit
* character may appear in a prefix, and so the trie is a 128-ary tree.
*
* #author crazybob#google.com (Bob Lee)
* #author mharris#google.com (Matthew Harris)
* #param <T>
*/
public class PrefixTrie<T> implements PrefixMap<T> {
// The set of allowed characters in prefixes is given by a range of
// consecutive characters. rangeOffset denotes the beginning of the range,
// and rangeSize gives the number of characters in the range, which is used as
// the number of children of each node.
private final char rangeOffset;
private final int rangeSize;
private final Node<T> root;
/**
* Constructs a trie for holding strings of seven-bit characters.
*/
public PrefixTrie() {
rangeOffset = '\0';
rangeSize = 128;
root = new Node<>(rangeSize);
}
/**
* Constructs a trie for holding strings of characters.
*
* The set of characters allowed in prefixes is given by the range
* [rangeOffset, lastCharInRange], inclusive.
*
* #param firstCharInRange
* #param lastCharInRange
*/
public PrefixTrie(char firstCharInRange, char lastCharInRange) {
this.rangeOffset = firstCharInRange;
this.rangeSize = lastCharInRange - firstCharInRange + 1;
if (rangeSize <= 0) {
throw new IllegalArgumentException("Char range must include some chars");
}
root = new Node<>(rangeSize);
}
/**
* {#inheritDoc}
*
* #param prefix
* #param value
* #throws IllegalArgumentException if prefix contains a character outside
* the range of legal prefix characters.
*/
#Override
public T put(CharSequence prefix, T value) {
if (value == null) {
throw new NullPointerException();
}
Node<T> current = root;
for (int i = 0; i < prefix.length(); i++) {
int nodeIndex = prefix.charAt(i) - rangeOffset;
try {
Node<T> next = current.next[nodeIndex];
if (next == null) {
next = current.next[nodeIndex] = new Node<>(rangeSize);
}
current = next;
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException(
"'" + prefix.charAt(i) + "' is not a legal prefix character.");
}
}
T oldValue = current.value;
current.value = value;
return oldValue;
}
/**
* {#inheritDoc}
* #param s
*/
#Override
public T get(CharSequence s) {
Node<T> deepestWithValue = root;
Node<T> current = root;
for (int i = 0; i < s.length(); i++) {
int nodeIndex = s.charAt(i) - rangeOffset;
if (nodeIndex < 0 || rangeSize <= nodeIndex) {
return null;
}
current = current.next[nodeIndex];
if (current == null) {
break;
}
if (current.value != null) {
deepestWithValue = current;
}
}
return deepestWithValue.value;
}
/**
* Returns a Map containing the same data as this structure.
*
* This implementation constructs and populates an entirely new map rather
* than providing a map view on the trie, so this is mostly useful for
* debugging.
*
* #return A Map mapping each prefix to its corresponding value.
*/
public Map<String, T> toMap() {
Map<String, T> map = newLinkedHashMap();
addEntries(root, new StringBuilder(), map);
return map;
}
/**
* Adds to the given map all entries at or below the given node.
*
* #param node
* #param builder A StringBuilder containing the prefix for the given node.
* #param map
*/
private void addEntries(Node<T> node,
StringBuilder builder,
Map<String, T> map) {
if (node.value != null) {
map.put(builder.toString(), node.value);
}
for (int i = 0; i < node.next.length; i++) {
Node<T> next = node.next[i];
if (next != null) {
builder.append((char) (i + rangeOffset));
addEntries(next, builder, map);
builder.deleteCharAt(builder.length() - 1);
}
}
}
private static class Node<T> {
T value;
final Node<T>[] next;
#SuppressWarnings("unchecked")
Node(int numChildren) {
next = new Node[numChildren];
}
}
/**
* Creates a {#code LinkedHashMap} instance.
*
* #param <K>
* #param <V>
* #return a newly-created, initially-empty {#code LinkedHashMap}
*/
public static <K, V> LinkedHashMap<K, V> newLinkedHashMap() {
return new LinkedHashMap<>();
}
}
Vladimir Kroz implementation: Trie class
package tiesImpl;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Prefix table based on Trie structure. Allows to perform incremental lookup
* and match based on search key prefixes (classic example - determine phone
* area code for given phone number)
*
* #param <V> a type of value object to be stored along with prefix (e.g when
* key is a country name, the value could be a name of the country)
*
* #author Vladimir Kroz
* https://vkroz.wordpress.com/2012/03/23/prefix-table-trie-implementation-in-java/
*/
public class Trie<V> {
Entry<V> entry;
char key;
Map<Character, Trie<V>> children;
public Trie() {
this.children = new HashMap<>(10);
entry = new Entry<>();
}
/**
* non-public, used by _put()
*/
Trie(char key) {
this.children = new HashMap<>(10);
this.key = key;
entry = new Entry<>();
}
public void put(String key, V value) {
_put(new StringBuffer(key), new StringBuffer(""), value);
}
void _put(StringBuffer remainder, StringBuffer prefix, V value) {
if (remainder.length() > 0) {
char keyElement = remainder.charAt(0);
Trie<V> t = null;
try {
t = children.get(keyElement);
} catch (IndexOutOfBoundsException e) {
}
if (t == null) {
t = new Trie<>(keyElement);
children.put(keyElement, t);
}
prefix.append(remainder.charAt(0));
t._put(remainder.deleteCharAt(0), prefix, value);
} else {
this.entry.value = value;
this.entry.prefix = prefix.toString();
}
}
/**
* Retrieves element from prefix table matching as a prefix to provided
* key. E.g. if key is "37251656565" and prefix table has node "372" then
* this call will return the value of "372"
*
* #param key a string which starts with prefix to be searched in the table
* (e.g. phone number)
* #return an Object associated with matching prefix (i.e if key is a phone
* number it may return a corresponding country name)
*/
public V get(String key) {
return _get(new StringBuffer(key), 0);
}
/**
* Returns true if key has matching prefix in the table
*
* #param key
* #return
*/
public boolean hasPrefix(String key) {
return this.get(key) != null;
}
V _get(StringBuffer key, int level) {
if (key.length() > 0) {
Trie<V> t = children.get(key.charAt(0));
if (t != null) {
//FYI: modified code to return deepest with value instead of returning null if prefix doesn't have corresponding value.
V result = t._get(key.deleteCharAt(0), ++level);
return result == null ? entry.value : result;
} else {
return (level > 0) ? entry.value : null;
}
} else {
return entry.value;
}
}
#Override
//For debugging
public String toString() {
Iterator<Character> it = children.keySet().iterator();
StringBuffer childs = new StringBuffer();
while (it.hasNext()) {
Character _key = it.next();
childs.append(String.format("\n%s\n",
//Adding a tab to the beginning of every line to create a visual tree
String.format("%s: %s", _key, children.get(_key)).replaceAll("(?m)(^)", "\t")));
}
return String.format("Trie [entry=%s, children=%s]", entry, childs);
}
static public class Entry<V> {
String prefix;
V value;
public Entry() {
}
public Entry(String p, V v) {
prefix = p;
value = v;
}
public String prefix() {
return prefix;
}
public V value() {
return value;
}
#Override
public String toString() {
return "Entry [prefix=" + prefix + ", value=" + value + "]";
}
}
}
And finally the Testing part
package tiesImpl;
/**
* Class for testing different implementations of prefix tree (Trie).
*
* #author lkallas
*/
public class TriesTest {
private static final PrefixTrie<String> googleTrie = new PrefixTrie<>();
private static final Trie<String> krozTrie = new Trie<>();
public static void main(String[] args) {
//Inserting prefixes to Google implementation of Trie
googleTrie.put("7", "Russia");
googleTrie.put("77", "Abkhazia");
googleTrie.put("746", "Some unknown country");
//Inserting prefixes to Vladimir Kroz implementation of Trie
krozTrie.put("7", "Russia");
krozTrie.put("77", "Abkhazia");
krozTrie.put("746", "Some unknown country");
System.out.println("Google version of get: " + googleTrie.get("745878787"));
System.out.println("Vladimir Kroz version of get: " + krozTrie.get("745878787"));
}
}
Hope that this answer is somewhat helpful to others also!
Cheers!

Related

Java presenting problem to fill the HashMap

The code below is presenting a strange behavior when filling a HashMap. As can be seen there is a for that goes through a list of Faces and adds one IntegrationCellVertex into the map called values of each Face. However, in the first loop it adds this object into more than one Face of the list (see the image, highlighted in blue). Would anyone have any idea what may be going on? Unfortunately, the code is too big, so I just sharing the section where is happening the problem.
Thanks a lot!
int vertexCounter = 0;
for (Face face : this.getDomainDataStructure().getFacesList()) {
IPolygon polygon = face.faceToPolygon();
IPoint3d centroid = polygon.getCentroid();
IntegrationCellVertex integrationCellVertex = new IntegrationCellVertex();
integrationCellVertex.setPoint(centroid);
vertexCounter += 1;
integrationCellVertex.setLabel(String.valueOf(vertexCounter));
integrationCellVertex.setContainingFace(face);
face.setValue(MeshfreeModel.INTEGRATION_CELL_VERTEX, integrationCellVertex);
this.getModel().addIntegrationCellsVertex(integrationCellVertex);
}
Face class:
public class Face implements Serializable {
private static final long serialVersionUID = 1L;
private LinkedList<Loop> loopList = new LinkedList<Loop>();
private HashMap<String, Object> values;
private ArrayList<String> keys;
private Solid solid;
private String id;
private LinkedList<Vertex> internalVertices = new LinkedList<Vertex>();
private ArrayList<Face> facesVizinhas = new ArrayList<Face>();
private ArrayList<Edge> oldEdgesVizinhas = new ArrayList<Edge>();
private ArrayList<Edge> newEdgesVizinhas = new ArrayList<Edge>();
private ArrayList<Edge> oldOldEdgesVizinhas = new ArrayList<Edge>();
/**
* The constructor of this class.
*/
public Face() {
this.values = new HashMap<String, Object>();
this.keys = new ArrayList<String>();
}
/**
* The constructor of this class.
*
* #param id The identification of this face.
* #param loop The loop.
*/
public Face(String id, Loop loop) {
this.id = id;
this.loopList.add(loop);
loop.setFace(this);
this.values = new HashMap<String, Object>();
this.keys = new ArrayList<String>();
}
/**
* getPlanarSubdivision.
* #return Returns the planarSubdivision.
*/
public Solid getSolid() {
return solid;
}
/**
* setPlanarSubdivision.
* #param planarSubdivision The planarSubdivision to set.
*/
public void setSolid(Solid planarSubdivision) {
this.solid = planarSubdivision;
}
/**
* getId.
* #return Returns the index.
*/
public String getId() {
return id;
}
/**
* setIndex.
* #param id The index to set.
*/
public void setId(String id) {
this.id = id;
}
/**
* getLoopList.
* #return Returns the loopList.
*/
public LinkedList<Loop> getLoopList() {
return loopList;
}
/**
* setLoopList.
* #param loopList The loopList to set.
*/
public void setLoopList(LinkedList<Loop> loopList) {
this.loopList = loopList;
}
/**
* Returns a value of this face.
*
* #param key The key of the value.
* #return The face value.
*/
public Object getValue(String key) {
return this.values.get(key);
}
/**
* The method set a value at a key of the face values.
*
* #param key The key of the value.
* #param value The value.
*/
public void setValue(String key, Object value) {
if (!this.keys.contains(key)) {
this.keys.add(key);
}
this.values.put(key, value);
}
/**
* Returns true if the node has the point values for the key.
*
* #param key The key of the point values.
* #return True if the node has the point values for the key.
*/
public boolean valuesContainsKey(String key) {
return this.values.containsKey(key);
}
/**
* The method return the values.
*
* #return Returns The values.
*/
public HashMap<String, Object> getValues() {
return this.values;
}
/**
* Returns the keys of this face.
*
* #return The keys of this face.
*/
public ArrayList<String> getKeys() {
return keys;
}
/**
* setKeys.
* #param keys The keys to set.
*/
public void setKeys(ArrayList<String> keys) {
this.keys = keys;
}
/**
* The method sets the HashMap.
*
* #param values the values to set
*/
public void setValues(HashMap<String, Object> values) {
this.values = values;
}
/**
* Returns true if the face contains the vertex.
*
* #param vertex The vertex
* #return If the face contains the vertex
*/
public boolean contains(Vertex vertex) {
boolean contains = false;
for (int i = 0; i < this.loopList.size(); i++) {
for (int j = 0; j < this.loopList.get(i).getHalfEdgeList().size(); j++) {
if (vertex.equals(this.loopList.get(i).getHalfEdgeList().get(j).getVertex())) {
contains = true;
break;
}
}
}
return contains;
}
/**
* Returns the internalVertices.
* #return the internalVertices
*/
public LinkedList<Vertex> getInternalVertices() {
return internalVertices;
}
public ArrayList<Face> getFacesVizinhas() {
return facesVizinhas;
}
public void setFacesVizinhas(ArrayList<Face> facesVizinhas) {
this.facesVizinhas = facesVizinhas;
}
public ArrayList<Edge> getOldEdgesVizinhas() {
return oldEdgesVizinhas;
}
public void setOldEdgesVizinhas(ArrayList<Edge> oldEdgesVizinhas) {
this.oldEdgesVizinhas = oldEdgesVizinhas;
}
public ArrayList<Edge> getNewEdgesVizinhas() {
return newEdgesVizinhas;
}
public void setNewEdgesVizinhas(ArrayList<Edge> newEdgesVizinhas) {
this.newEdgesVizinhas = newEdgesVizinhas;
}
public ArrayList<Edge> getOldOldEdgesVizinhas() {
return oldOldEdgesVizinhas;
}
public void setOldOldEdgesVizinhas(ArrayList<Edge> oldOldEdgesVizinhas) {
this.oldOldEdgesVizinhas = oldOldEdgesVizinhas;
}
/**
* Returns the two edges of this face that are connected to the informed vertex.
* #param vertex a vertex
* #return the two edges connected to this vertex
*/
public Edge[] getEdges(Vertex vertex) {
// Initialization of the edges array
Edge[] edges = new Edge[2];
// Auxiliary variables
Edge edge;
HalfEdge halfEdge;
int counter = 0;
// Iteration over the loops of this face
ListIterator<Loop> loops = this.getLoopList().listIterator();
while (loops.hasNext()) {
// This loop
Loop loop = loops.next();
// Iteration over the half-edges of this loop
ListIterator<HalfEdge> halfEdges = loop.getHalfEdgeList().listIterator();
while (halfEdges.hasNext()) {
// This half-edge
halfEdge = halfEdges.next();
// The edge of this half-edge
edge = halfEdge.getEdge();
// Check if this edge contains the vertex
if (edge.getRightHalfEdge().getVertex().isTheSame(vertex) || edge.getLeftHalfEdge().getVertex().isTheSame(vertex)) {
edges[counter] = edge;
counter++;
}
}
}
return edges;
}
/**
* Returns the vertices associated to this face.
* #return an {#link ArrayList} of instances of the class {#link Vertex}
*/
public ArrayList<Vertex> getVertices() {
ArrayList<Vertex> vertices = new ArrayList<>();
ListIterator<Loop> loops = this.loopList.listIterator();
while (loops.hasNext()) {
Loop loop = loops.next();
ListIterator<HalfEdge> halfEdges = loop.getHalfEdgeList().listIterator();
while (halfEdges.hasNext()) {
HalfEdge halfEdge = halfEdges.next();
Vertex vertex = halfEdge.getVertex();
vertices.add(vertex);
}
}
return vertices;
}
/**
* Returns an instance of the class {#link IPolygon} constructed using the vertices of this face.
* #return an instance of the class {#link IPolygon}
*/
public IPolygon faceToPolygon() {
ArrayList<IPoint3d> points = new ArrayList<>();
ListIterator<Vertex> vertices = this.getVertices().listIterator();
while (vertices.hasNext()) {
Vertex vertex = vertices.next();
points.add(vertex.getCoords());
}
return new IPolygon(points);
}
}
Well, look at your code, which is in clear disagreement with your description.
You say:
As can be seen there is a for that goes through a list of Faces and adds one IntegrationCellVertex into the map called values of each Face.
and yet the one and only place that adds any integration cells vertex to anything is
this.getModel().addIntegrationCellsVertex(integrationCellVertex);
which is clearly adding it to this.getModel(), not face.
Your code does not include any put calls into any maps, and your picture is showing off how the same value is in the values map of multiple faces. Basically, the code you pasted is irrelevant to your question, perhaps paste more, or make a self contained test case. There may well be more problems with your code than just the disconnect between what it does and how you understand it.
If you can't make a self contained test case, face.setValue, and model.addIntegrationCellsVertex all look like required source code that needs to be added to your question in order for anybody to shed some light on your issue.
Second answer now that you've updated your code:
Your code cannot result in the debug state you've shown, unless someone did something stupid, like, say:
Face a = this.getDomainDataStructure().getFacesList().get(0);
Face b = this.getDomainDataStructure().getFacesList().get(1);
a.setValues(b.getValues());
at which point a's values map and b's values map are just pointers to the same map. modify one, and the other one is also modified (if you a street address written on a piece of paper, and I have another entirely different piece of paper with the exact same address written on it, and you follow your paper and toss a brick through the window, and later I drive over there, I'll find a vandalized house. Same situation).
Take your Face class. Delete all the setters. Make all fields final.
Now, all errors? Those are the actual places that are buggy or at least sufficiently silly that other code (such as the one you pasted) will then fail. Fix THOSE. The problem is there, not in what you pasted.

Algorithm on an ordered collection of strings

I have an assessment to do and I'm not good at programming. I can search for the algorithms and see how they are done, but in my case I have ordered collection of strings and somehow I have to use the get method.
I have these two classes that must not be changed:
public class SearchTest {
/**
* Test program for the Search class.
* Put whatever tests you like in the body of the method.
* #param args the command line arguments
* #throws java.io.IOException of error reading the input
*/
public static void main(String[] args) throws IOException {
// Don't change this line
final Search search = new Search();
// You can set this to any of the text files in the data folder
final FileStrings strings = new FileStrings("data/small.txt");
// add your tests here
System.out.println(search.longestWord(strings));
}
}
and
public class FileStrings implements StringList {
/** Underlying list of elements */
private final ArrayList<String> elements;
/** Number of calls to get() since the last call to resetCount() */
private int count;
/**
* Create a list containing the lines of a text file.
* #param fileName name of a text file of strings, in order
* #throws java.io.IOException on input error
*/
public FileStrings(String fileName) throws IOException {
elements = new ArrayList<>();
try (BufferedReader input = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = input.readLine()) != null) {
elements.add(line);
}
}
count = 0;
}
/**
* Returns the number of elements in this list.
* This method takes constant time.
* #return the number of elements in this list
*/
#Override
public int size() {
return elements.size();
}
/**
* Returns the element at the specified position in this list.
* This method takes constant time.
* #param i position in the list, between 0 and size()-1
* #return the element at the position i
*/
#Override
public String get(int i) {
count++;
return elements.get(i);
}
/**
* Reset the count field.
*/
public void resetCount() {
count = 0;
}
/**
* Getter for count.
* #return number of calls to get() since the last resetCount()
*/
public int getCount() {
return count;
}
}
And my first task is to find the longest word from the given file list.
This is my attempt(I know it's wrong, but can't follow examples, because the solutions I see use the array directly):
public class Search {
/**
* Returns the index of the longest string in the list.
* If there are several string of this length, the
* indexed returned is the that of the first.
* #param a list of strings, in ascending order
* #return position of an entry with the longest string in the list
*/
public int longestWord(StringList a) {
int i=0;
int longestWord=0;
String nextWord=a.get(i+1);
String previousWord=a.get(i);
while (i < a.size() ) {
if (nextWord.length()>previousWord.length()){
longestWord = i;
}
i = i + 1;
}
return longestWord;
}
The result should be "14", the world "because" is the 15th word and is the longest. I hope you can help me with this!
list of words
public class Search {
/**
* Returns the index of the longest string in the list.
* If there are several string of this length, the
* indexed returned is the that of the first.
* #param a list of strings, in ascending order
* #return position of an entry with the longest string in the list
*/
public int longestWord(StringList a) {
int length=a.get(0).length();
int i=0;
int longestWord=0;
while (i<a.size()){
if (a.get(i).length()>length){
length=a.get(i).length();
longestWord=i;
}
i = i + 1;
}
return longestWord;
}
managed to do it :P

How would I convert this code to Generics?

I am in desperate need of help here. I am to migrate this existing code to generics and I've really hit a wall. Any help would be greatly appreciated.
The existing code is an algorithm library and is accompanied by some classes with constructors for vehicles (i.e Bike.Java).
I've tried alot of different things but I just can't seem to figure it out. I'd love some insight.
public class Algo
{
/**
* Copies all objects from src to tgt, for which the predicate pred holds.
*
* #param src source list
* #param tgt target list
* #param pred unary predicate
*/
static public
void copyIf(List src, List tgt, UnaryPredicate pred)
{
for (Object obj : src)
{
if (pred.test(obj)) tgt.add(obj);
}
}
/**
* Copies all objects from src to tgt that are greater than yardstick.
*
* #param src source
* #param tgt target
* #param yardstick determines if objects in src should be copied to tgt.
*/
static public
void copyIfGreaterThan(List src, List tgt, final Comparable yardstick)
{
copyIf(src, tgt, new UnaryPredicate() {
public boolean test(Object o)
{
return yardstick.compareTo(o) < 0;
}
});
}
/**
* Finds a maximum object in lst.
*
* #param lst a list containing non-null references
* #return a maximum object in lst
*/
static public
Comparable findMax(List lst)
{
assert lst != null;
Comparable max = null;
Iterator iter = lst.iterator();
// handle first element
if (iter.hasNext())
max = (Comparable) iter.next();
// handle remaining elements
while (iter.hasNext())
{
assert max != null;
Comparable cand = (Comparable) iter.next();
if (max.compareTo(cand) < 0)
max = cand;
}
return max;
}
/**
* Adds the smaller of lhs and rhs to dst.
*
* #param lhs left hand side object
* #param rhs right hand side object
* #param dst destination list
*/
static public
void storeMin(Comparable lhs, Comparable rhs, List dst)
{
Comparable min = lhs;
if (min.compareTo(rhs) > 0) min = rhs;
dst.add(min);
}
/**
* swaps the elements at a and b in lst.
*
* #param lst a list
* #param a first location in lst
* #param b second location in lst
*/
static private
void swap(ArrayList objs, int a, int b)
{
Object tmp = objs.get(a);
objs.set(a, objs.get(b));
objs.set(b, tmp);
}
/**
* Sorts the elements in lst.
*
* #param lst an array list containing non-null references
*/
static public
void selectionSort(ArrayList lst)
{
for (int i = 0; i < lst.size(); ++i)
{
int min = i;
Comparable minobj = (Comparable) lst.get(min);
for (int j = i+1; j < lst.size(); ++j)
{
if (minobj.compareTo(lst.get(j)) > 0)
{
min = j;
minobj = (Comparable) lst.get(min);
}
}
swap(lst, min, i);
}
}
}
Since Java 8 you could do it like that:
public static <T> List<T> copyIf(List<T> src, Predicate<T> predicate){
return src.stream().filter(predicate).collect(Collectors.toList());
}
public static <T> List<T> copyIfGreaterThan(List<T> src, Comparable<T> yardstick) {
return copyIf(src, t -> (yardstick.compareTo(t) < 0));
}
For more infos about generics see:
https://docs.oracle.com/javase/tutorial/java/generics/types.html
For more infos on streams see e.g.
https://www.tutorialspoint.com/java8/java8_streams.htm

HashMap Difficulties

This is a school assignment. Overall this should read a text file, add words from it to a hash table. I've coded it out, but I'm testing it and it's giving me some problems. When I try to find the index of the object, it always returns -1, meaning it's saying the words aren't in the array even if they are. There are a few other problems as well. It's giving me a headache.
import java.util.Iterator;
import java.util.LinkedList;
public class MyMap<K,V> implements Iterable<MyMap.MyEntry<K,V>> {
int collision; // maintain the current count of collisions here.
int slots = 0;
int key = 0;
MyEntry<K,V> tempPair;
LinkedList<MyEntry<K,V>> [] bucketArray;
int keyMod;
/**
* Create a MyMap instance with the specified number of
* buckets.
*
* #param buckets the number of buckets to make in this map
*/
public MyMap(int buckets) {
slots = buckets;
bucketArray = (LinkedList<MyEntry<K,V>> [])new LinkedList[buckets];
}
/**
* Puts an entry into the map. If the key already exists,
* it's value is updated with the new value and the previous
* value is returned.
*
* #param key the object used as a key to retrieve the value
* #param value the object stored in association with the key
*
* #return the previously stored value or null if the key is new
*/
public V put(K key, V value) {
// don't forget hashcodes can be any integer value. You'll
// need to compress them to ensure they give you a valid bucket.
MyEntry<K,V> tempPair = new MyEntry<K,V>(key, value);
Word newWord = new Word((String)key);
keyMod = newWord.hashCode((String)key) % slots;
if ((bucketArray[keyMod]) == null){
LinkedList<MyEntry<K,V>> firstList = new LinkedList<MyEntry<K,V>>();
firstList.add(tempPair);
bucketArray[keyMod] = firstList;
return null;
}
else {
int indexNode = bucketArray[keyMod].indexOf(tempPair);
if (indexNode == -1) {
bucketArray[keyMod].add(tempPair);
collision += 1;
//System.out.println(indexNode );
return null;
}
else {
MyEntry<K,V> oldNode = bucketArray[keyMod].get(indexNode);
V oldValue = oldNode.value;
oldNode.value = tempPair.value;
//System.out.println(indexNode );
return oldValue;
}
}
}
/**
* Retrieves the value associated with the specified key. If
* it exists, the value stored with the key is returned, if no
* value has been associated with the key, null is returned.
*
* #param key the key object whose value we wish to retrieve
* #return the associated value, or null
*/
public V get(K key) {
//MyEntry<K,V> tempPair = new MyEntry<K,V>(key,value);
Word newWord = new Word((String)key);
int keyMod = newWord.hashCode((String)key) % slots;
if (bucketArray[keyMod] == null) {
return null;
}
else {
int temp = bucketArray[keyMod].indexOf(key);
if (temp == -1) {
return null;
}
else {
MyEntry<K,V> tempNode = bucketArray[keyMod].get(temp);
return tempNode.value;
}
}
}
/**
*
* I've implemented this method, however, you must correctly
* maintain the collisions member variable.
*
* #return the current count of collisions thus far.
*/
public int currentCollisions(K key) {
return collision;
}
/**
* Looks through the entire bucket where the specified key
* would be found and counts the number of keys in this bucket
* that are not equal to the current key, yet still have the
* same hash code.
*
* #param key
* #return a count of collisions
*/
public int countCollisions(K key) {
Word newKey = new Word((String) key);
int keyMod = newKey.hashCode((String) key) % slots;
if (bucketArray[keyMod].indexOf(key) == -1){
return bucketArray[keyMod].size();
}
return (bucketArray[keyMod].size()-1);
}
/**
* Removes the value associated with the specifed key, if it exists.
* #param key the key used to find the value to remove.
* #return the value if the key was found, or null otherwise.
*/
public V remove(K key) {
Word newWord = new Word((String)key);
//int keyMod = newWord.hashCode((String)key) % slots;
int tempNodeIndex = bucketArray[newWord.hashCode((String)key)].indexOf(key);
if (tempNodeIndex == -1) {
return null;
}
else{
tempPair = bucketArray[key.hashCode()].get(tempNodeIndex);
V returnValue = tempPair.value;
tempPair.value = null;
return returnValue;}
}
/**
* Returns the number of entries in this map
* #return the number of entries.
*/
public int size() {
int size = 0;
for (int i =0; i< bucketArray.length; i++){
size = bucketArray[i].size() + size;
}
return size;
}
/**
* Creates and returns a new Iterator object that
* iterates over the keys currently in the map. The iterator
* should fail fast, and does not need to implement the remove
* method.
*
* #return a new Iterator object
*/
public Iterator<MyEntry<K,V>> iterator() {
return null;
}
public static class MyEntry<K,V> {
K key;
V value;
public MyEntry(K k, V v) {
key = k;
value = v;
}
}
}
Here is the Word Class
/* The reason you can't extend String Class is because String is a final class and you can not have
* a subclass that might alter components of a final class. Since the word class would extend the
* String class, it would have the capability to change variables within the String Final Class.
*/
public class Word {
String word;
/**
* Creates a Word object representing the specified String
*
* #param w the String version of this word.
*/
public Word(String w) {
word = w;
}
/**
* Returns a hashcode for this Word -- an integer whose value is based on the
* word's instance data. Words that are .equals() *must* have the same hashcode.
* However, the converse need not hold -- that is, it *is* acceptable for
* two words that are not .equals() to have the same hashcode.
*/
public int hashCode(String word) {
int code = 0;
for ( int i =0; i<word.length(); i++){
code = word.charAt(i) + code;
}
return code; //word.hashCode();
//int hashCode = 0;
//for ( int i = 0; i<word.length(); i++) {
//hashCode = Math.abs(hashCode*13 + word.charAt(i));
//}
//System.out.println(hashCode);
//return hashCode;
}
/**
* Returns true if and only if this Word object represents the same
* sequence of characters as the specified object. Here, you can assume
* that the object being passed in will be a Word.
*/
public boolean equals(Object o) {
String passedIn = o.toString();
boolean returnValue = word.equals(passedIn);
return returnValue;
}
/**
* This method returns the string representation of the object.
* A correct implementation will return the String representation of the
* word that is actually being stored. ie., if you had a word object representing
* 'hi', it should return 'hi'
*/
public String toString() {
String thisString = word;
return thisString;
}
}
Here is the beginnings of my tester:
import java.io.*;
import java.util.*;
public class Tester<K,V> {
public static void main(String [] args) throws FileNotFoundException {
MyMap<String, Integer> pain = new MyMap<String, Integer>(3000);
Scanner s = new Scanner (new File("pg4.txt"));
while (s.hasNext()) {
String word = s.next();
Integer value = (Integer) pain.get(word);
if (value == null) {
pain.put(word, 1);
}
else {
value +=1;
pain.put(word, value);
}
}
s.close();
pain.put("the",1);
pain.put("the",5);
pain.get("the");
System.out.println("'the' gives this many collisions: " + pain.get("the") );
pain.remove("the");
System.out.println("'the' gives this many collisions: " + pain.get("the") );
}
}
indexOf uses equals for comparisons, so your calls to indexOf do not work. You need to implement equals for MyEntry.
public static class MyEntry<K,V> {
K key;
V value;
public MyEntry(K k, V v) {
key = k;
value = v;
}
#Override
public int hashCode() {
// (overriding hashCode
// just because we are overriding equals)
return ( key == null ? 0 : key.hashCode() );
}
#Override
public boolean equals(Object o) {
if(!(o instanceof MyEntry<?, ?>))
return false;
MyEntry<?, ?> that = (MyEntry<?, ?>)o;
return( this.key == null ?
that.key == null : this.key.equals(that.key)
);
}
}
If you don't do this, then you need to create your own indexOf method where you loop through the LinkedList yourself.
Your remove method does not actually do a removal, just set the value to null.
tempPair = bucketArray[key.hashCode()].get(tempNodeIndex);
V returnValue = tempPair.value;
tempPair.value = null;
More correctly it would be:
tempPair = bucketArray[key.hashCode()].remove(tempNodeIndex);
return tempPair.value;
As far as I can tell, you do not need the Word class at all. Your casting to String makes assumptions about what the type of K is, which is dubious for a generic class. (What if I have a MyMap<Long, Double>?)
You are only using it to get a hashCode which your K will already have (because hashCode is declared on java.lang.Object).
You could use hashCode from a temp MyEntry like I defined above or call it directly:
int keyMod = ( key == null ? 0 : key.hashCode() ) % slots;
To get your Word class working, you need to override hashCode correctly:
// now you can call hashCode() on a Word
// when a Word is passed in to MyMap as a key
#Override
public int hashCode() {
int code = 0;
// 'word' now refers to the instance variable
for ( int i =0; i<word.length(); i++){
code = word.charAt(i) + code;
}
return code;
}
// also implementing equals correctly, but your
// implementation in the question probably did
// not cause an error
#Override
public boolean equals(Object o) {
if(!(o instanceof Word))
return false;
String passedIn = ((Word)o).word;
boolean returnValue = word.equals(passedIn);
return returnValue;
}
Then you will be able to use a MyMap<Word, Integer>.
I found a few problems in my MyMap class where I wasn't constructing correctly. I found the error and fixed it. My tests were also causing errors. I corrected them. There were no problems in my Word Class. Once I corrected these the map constructed correctly, all methods worked.

Java PhoneBook using ArrayList

Im trying to make a PhoneBook using array list but I'm not getting the right output here is my code, thank you for any help with this, the output I'm getting now is just a zero when i ask for the size, not seeming to add anyone, that is probably where the problem lies
import java.util.ArrayList;
public class Phonebook implements Directory
{
private ArrayList<Person> book;
public Phonebook ()
{
book = new ArrayList<Person>();
}
/**
* will return the number of entries currently entered in
* the <code>Directory</code>.
* #return - the number of valid entries in the <code>Directory</code>.
**/
public int size()
{
return book.size();
}
/**
* will display the entries currently entered in the <code>Directory</code>.
**/
public void listAll()
{
for(int i = 0; i < book.size(); i++)
{
System.out.println(book.get(i));
}
}
/**
* will add a new record to the <code>Directory</code> in alphabetical order
* if the name is not a duplicate entry. Otherwise no changes will be made.
* #param name - name of individual to be added to the <code>Directory</code>.
* #param number - phone number of the individual to be added.
* #return - true if the entry was added successfully, otherwise false.
**/
public boolean addPerson(String name, String number)
{
Person x = new Person (name, number);
if (checkPerson(name) == -1)
return false;
int index = 0;
while(index < book.size())
{
if((x.getName().compareTo((book.get(index)).getName())) < 0)
{
book.add(x);
return true;
}
index++;
}
return false;
}
public int checkPerson(String name)
{
int lo = 0;
int hi = book.size() - 1;
while(lo <= hi)
{
int half = (lo + hi) / 2;
if(name.equals(book.get(half).getName()))
return half;
if(name.compareTo(book.get(half).getName()) < 0){
hi = half - 1;}
else lo = half + 1;
}
return -1;
}
/**
* will remove an entry from the <code>Directory</code> if the name parameter
* is currently in the <code>Directory</code>. Otherwise no changes
* will be made.
* #param name - individual to be removed from the <code>Directory</code>.
* #return - true if the entry was successfully removed, otherwise false.
**/
public boolean removePerson(String name)
{
if (checkPerson(name) == -1)
return false;
book.remove(checkPerson(name));
return true;
}
/**
* will search the <code>Directory</code> to find out if the name passed in
* is currently in the <code>Directory</code>. If so, it will return the
* phone number associated with this person. Otherwise it will return null.
* #param name - name of individual to look up in the <code>Directory</code>.
* #return - the phone number if the name was found, otherwise null.
**/
public String lookUp(String name)
{
Person n = new Person (name, "999-9999");
int local = checkPerson(n.getName());
if(local == -1)
return null;
return book.get(local).getNumber();
}
/**
* will search the <code>Directory</code> to find out if the phone number
* is currently in the <code>Directory</code>. If so, it will return the
* name associated with this number. Otherwise it will return null.
* #param number - name of individual to look up in the <code>Directory</code>.
* #return - the name of the person if the number was found, otherwise null.
**/
public String lookUpNum(String number)
{
for(int i = 0; i <book.size(); i++)
{
if(number.equals(book.get(i).getNumber()))
return book.get(i).getName();
}
return null;
}
}
/**
* The Person class is a container class to hold the
* name and phone number of an individual. There are methods
* to access the name and number, and modify the name and number.
* Each name is stored in "Last, First" form to facilitate searching
* and sorting of persons. A private helper method is used to be
* sure that names entered in "First Last" form are converted to
* the proper format.
*/
public class Person implements Comparable<Person>
{
private String first;
private String last;
private String name; // Last, First
private String number;
/**
* explicit constructor, will store the first and last
* names, as well as the entire name in Last, First order
*
* #param na is the name of the individual
* #param nu is the phone number of the individual
*/
public Person(String na, String nu)
{
convert(na);
number = nu;
}
/**
* copy constructor, will make an exact copy of the parameter
*
* #param per is the <B>Person</B> to be duplicated
*/
public Person(Person per)
{
first = per.first;
last = per.last;
name = per.name;
number = per.number;
}
/**
* accessor method to return the name of <B>this Person</B>
*
* #return the name of the individual in Last, First order
*/
public String getName()
{
return name;
}
/**
* accessor method to return the phone number of <B>this Person</B>
*
* #return the phone number of the individual
*/
public String getNumber()
{
return number;
}
/**
* modifier method to set a new name for <B>this Person</B>
* The helper method convert() is called to handle the details
*
* #param the new name for the individual
*/
public void setName(String na)
{
convert(na);
}
/**
* modifier method to set a new phone number <B>this Person</B>
* just in case somebody needs to enter witness protection
*
* #param the new phone number for the individual
*/
public void setNumber(String num)
{
number = num;
}
/**
* accessor method that implements the <B>Comparable interface</B>
* based on the name field for <B>this Person</B>
* will return a positive number if <B>this</B> is greater than oth
* zero if <B>this</B> is equal to oth
* and a negative number if <B>this</B> is less than oth
*
* #return negative, zero, or positive int as per Comparable interface
*/
public int compareTo(Person oth)
{
return name.toUpperCase().compareTo(oth.name.toUpperCase());
}
/**
* accessor method to test if the instance data for <B>this Person</B>
* is equal to the instance data for oth
*
* #return true if names and numbers match, false otherwise
*/
public boolean equals(Person oth)
{
return name.toUpperCase().equals(oth.name.toUpperCase()) && number.equals(oth.number);
}
private void convert(String na)
{
if(na.indexOf(" ") == -1)
{
last = na;
first = null;
name = na;
}
else if(na.indexOf(",") != -1)
{
name = na;
first = na.substring(na.indexOf(",") + 2);
last = na.substring(na.indexOf(","));
}
else
{
first = na.substring(0, na.indexOf(" "));
last = na.substring(na.indexOf(" ") + 1);
name = last + ", " + first;
}
}
/**
* accessor method to return the instance data of <B>this Person</B>
* in a formatted String (24 character name field, followed by the number)
*
* #return name in Last, First order followed by the phone number
*/
public String toString()
{
String local = name;
if(name.length() < 8)
local += "\t";
if(name.length() < 16)
local += "\t";
local += "\t" + number;
return local;
}
}
public class client
{
public static void main(String[] args)
{
Phonebook nickBook = new Phonebook();
nickBook.addPerson("name lastname", "321-3256");
System.out.println();
nickBook.listAll();
System.out.println(nickBook.size());
}
}
Your addPerson method won't add the Person if the list is empty, since while (0 < 0) will be false, and the loop won't be entered :
int index = 0;
while(index < book.size())
{
if((x.getName().compareTo((book.get(index)).getName())) < 0)
{
book.add(x);
return true;
}
index++;
}
Beside that problem, book.add(x); will always add the new Person to the end of the List, which is not what you want. You should use book.add(index,x), assuming index is the location in which you wish to add the new Person.
Finally, if the new Person wasn't added inside the while loop, that means this Person should be the last Person on the List, so you have to add it to the end of the List after the loop.
A possible implementation :
public boolean addPerson(String name, String number)
{
Person x = new Person (name, number);
if (checkPerson(name) == -1)
return false;
int index = 0;
while(index < book.size())
{
if((x.getName().compareTo((book.get(index)).getName())) < 0)
{
book.add(index,x);
return true;
}
index++;
}
book.add(x); // this handles both the case of an empty List and the
// case in which the new Person should be the last Person
// on the list
return true;
}
Your function checkPerson is wrong. book.size() is 0 in the beginning and the hi results to -1 which means that it does not enter the loop. Besides that think about your half variable. It is possible that this results in another number than an integer which is not allowed if you are using this variable as an index for a query of the list.
public int checkPerson(String name)
{
int lo = 0;
int hi = book.size() -1;
while(lo <= hi)
{
int half = (lo + hi) / 2;
if(name.equals(book.get(half).getName()))
return half;
if(name.compareTo(book.get(half).getName()) < 0){
hi = half - 1;}
else lo = half + 1;
}
return -1;
}

Categories