Implementing my own Collections.binarySearch method - java

Currently I am self learning java and am doing an exercise in my textbook where it asks to me to take 1000 names in a text file with corresponding phone numbers and basically ask the user what they want to search up.
What my code does right now, is uses the Collections.binarySearch to find Phone numbers, or to find names. However, I was wondering how I could implement my own binary search since, this chapter is basically an introduction to searching and sorting, so I figured I would learn more doing it myself.
Here are the important parts of my code
Here I use the comparable interface
public int compareTo(Item otherObject)
{
Item other = (Item) otherObject;
return key.compareTo(other.key);
}
I then Add Phone Numbers and Names into the ArrayLists via
// Read a line containing the name
String name = in.nextLine();
// Read a line containing the number
String number = in.nextLine();
// Store the name and number in the byName array list
byName.add(new Item(name, number));
// Store the number and name in the byNumber array list
byNumber.add(new Item(number, name));
And then call another method which does
int index = Collections.binarySearch(byName, new Item(k,""));
if(index<0) return null;
return byName.get(index).getValue();
I also have another method which can search byPhone
Thus finding everything correctly.
My Question
What I want to know is how I can implement my own method which will do binarySearch. I've done binary search for just arrays and finding a number in an array, but I'm having difficulties really understanding how the method is going to be set up since we are dealing with objects and array lists.
For example I wanted to make a method like this:
int myBinarySearch(ArrayList<Item> thisItem, Object Item)
{
// search logic here
}
However I am not sure whether this is the right approach. Could someone guide my on how exactly I should format my method for binary search, given the fact that I have a bunch of objects in an arraylist which need to be sorted, as opposed to a simple array.
Currently Working code
Here is the full code for my currently working method using Collections.binarySearch
/Item.java:
/**
An item with a key and a value.
*/
public class Item implements Comparable<Item>
{
private String key;
private String value;
/**
Constructs an Item object.
#param k the key string
#param v the value of the item
*/
public Item(String k, String v)
{
key = k;
value = v;
}
/**
Gets the key.
#return the key
*/
public String getKey()
{
return key;
}
/**
Gets the value.
#return the value
*/
public String getValue()
{
return value;
}
public int compareTo(Item otherObject)
{
Item other = (Item) otherObject;
return key.compareTo(other.key);
}
}
//LookupTable.java:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
/**
A table for lookups and reverse lookups
*/
public class LookupTable
{
private ArrayList<Item> byName;
private ArrayList<Item> byNumber;
/**
Constructs a LookupTable object.
*/
public LookupTable()
{
byName = new ArrayList<Item>();
byNumber = new ArrayList<Item>();
}
/**
Reads name and number pairs from the Scanner
and adds them to the byName and byNumber array lists.
#param in the scanner for reading the input
*/
public void read(Scanner in)
{
while (in.hasNextLine())
{
// Read a line containing the name
String name = in.nextLine();
// Read a line containing the number
String number = in.nextLine();
// Store the name and number in the byName array list
byName.add(new Item(name, number));
// Store the number and name in the byNumber array list
byNumber.add(new Item(number, name));
}
// Sort the byName Items so we can binary search
Collections.sort(byName);
// Sort the byNumber Items so we can binary search
Collections.sort(byNumber);
}
/**
Looks up an item in the table.
#param k the key to find
#return the value with the given key, or null if no
such item was found.
*/
public String lookup(String k)
{
// Use the Collections.binarySearch() method to find the
// position of the matching name in the byName array list.
// Return null if position is less than 0 (not found).
// Otherwise, return the number for the found name.
int index = Collections.binarySearch(byName, new Item(k,""));
if(index<0) return null;
return byName.get(index).getValue();
}
/**
Looks up an item in the table.
#param v the value to find
#return the key with the given value, or null if no
such item was found.
*/
public String reverseLookup(String v)
{
// Use the Collections.binarySearch() method to find the
// position of the matching number in the byNumber array list.
// Return null if position is less than 0 (not found).
// Otherwise, return the name for the found number.
int index = Collections.binarySearch(byNumber, new Item(v, ""));
if(index<0) return null;
return byNumber.get(index).getValue();
}
}
//PhoneLookup.java:
import java.io.IOException;
import java.io.FileReader;
import java.util.Scanner;
/* The input file has the format
Abbott, Amy
408-924-1669
Abeyta, Ric
408-924-2185
Abrams, Arthur
408-924-6120
Abriam-Yago, Kathy
408-924-3159
Accardo, Dan
408-924-2236
Acevedo, Elvira
408-924-5200
Acevedo, Gloria
408-924-6556
Achtenhagen, Stephen
408-924-3522
. . .
*/
public class PhoneLookup
{
public static void main(String[] args) throws IOException
{
Scanner in = new Scanner(System.in);
System.out.println("Enter the name of the phonebook file: ");
String fileName = in.nextLine();
LookupTable table = new LookupTable();
FileReader reader = new FileReader(fileName);
table.read(new Scanner(reader));
boolean more = true;
while (more)
{
System.out.println("Lookup N)ame, P)hone number, Q)uit?");
String cmd = in.nextLine();
if (cmd.equalsIgnoreCase("Q"))
more = false;
else if (cmd.equalsIgnoreCase("N"))
{
System.out.println("Enter name:");
String n = in.nextLine();
System.out.println("Phone number: " + table.lookup(n));
}
else if (cmd.equalsIgnoreCase("P"))
{
System.out.println("Enter phone number:");
String n = in.nextLine();
System.out.println("Name: " + table.reverseLookup(n));
}
}
}
}

You can find how JDK does it from JDK source code. In java.util.Collections:
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key)
Very same as the one you're working.

A binary search is a very simple algorithm. In pseudo code, it is:
find(list, item):
if list is empty
return not found
get middle item from list
if item matches middle
return middle
else if item is before middle
return find(list before middle, item)
else
return find(list after middle, item)
The list must be sorted to allow this to work. You can use List.subList to avoid having to do any copying or passing indices around.
In your case you want to be able to search by multiple criteria. It would make the most sense if you passed a argument into your method that defines what you are searching for.

Related

Checking if there is a certain string in an object contained in an ArrayList, then adding the object to another ArrayList

I have Arraylist of objects ArrayList<Product> productDatabase. The object contains a String and a double and then these objects will be added to the productDatabase by addProductToDatabase(); as follows:
public void addProductToDatabase(String productName, double dimensions); {
Product newProduct = new Product(ProductName, dimensions);
productDatabase.add(newProduct);
}
I also want to make an Arraylist<ProductCount> productInventory which counts how many Product are accounted for. Before it can add to ArrayList<ProductCount> productInventory however, it should first check if the object details exist in the productDatabase while running addProductToInventory()
public Product getProduct(String name) {
for(i = 0; i < productDatabase.size(); i++)
if(productDatabase.get(i).contains(name) //Error: cannot find symbol- method contains.(java.lang.String)
return productDatabase.get(i)
}
public void addProductToInventory(String productName, double quantity)
{
Product p = getProduct(name);
productCount.add(new ProductCount(o, quantity));
}
Assume that you always have different objects (so nothing will have the same name), but you're always unsure of the dimensions (so when you input the same producttName + dimensions you edit the dimensions in it).
At the end of the day, you have to put all the items in it a large box and report what you've inventoried, so you also have a getProductQuantityTotal() and you have to getProductDimensionTotal()-- as the name suggests, get the total of number of objects you've counted, and the sum of the dimensions.
What do I have to add/change/remove about this code? Don't consider syntax first (because BlueJ checks for common syntax errors and I just typed this by hand). I'm sure that I'm missing a for statement somewhere, and I'm probably misusing contains() because it won't recognise it (I have import java.util.*; and import java.util.ArrayList;)
To answer the question in your post title: How to find a string in an object, for a list of those objects, here is some sample code that does this:
First, I created a trivial object that has a string field:
class ObjectWithStringField {
private final String s;
public ObjectWithStringField(String s) {
this.s = s;
}
public String getString() {
return s;
}
}
And then a code that populates a list of it, and then searches each for the string. There's no magic here, it just iterates through the list until a match is found.
import java.util.List;
import java.util.Arrays;
/**
<P>{#code java StringInObjectInList}</P>
**/
public class StringInObjectInList {
public static final void main(String[] ignored) {
ObjectWithStringField[] owStrArr = new ObjectWithStringField[] {
new ObjectWithStringField("abc"),
new ObjectWithStringField("def"),
new ObjectWithStringField("ghi")};
//Yes this is a List instead of an ArrayList, but you can easily
//change this to work with an ArrayList. I'll leave that to you :)
List<ObjectWithStringField> objWStrList = Arrays.asList(owStrArr);
System.out.println("abc? " + doesStringInObjExistInList("abc", objWStrList));
System.out.println("abcd? " + doesStringInObjExistInList("abcd", objWStrList));
}
private static final boolean doesStringInObjExistInList(String str_toFind, List<ObjectWithStringField> owStrList_toSearch) {
for(ObjectWithStringField owStr : owStrList_toSearch) {
if(owStr.getString().equals(str_toFind)) {
return true;
}
}
return false;
}
}
Output:
[C:\java_code\]java StringInObjectInList
abc? true
abcd? false
In the real world, instead of a List, I'd use a Map<String,ObjectWithStringField>, where the key is that field. Then it'd be as simple as themap.containsKey("abc");. But here it is implemented as you require. You'll still have quite a bit of work to do, to get this working as specifically required by your assignment, but it should get you off to a good start. Good luck!

Map error with function

this is what i have so far and what im trying to do i dont know how to access both parts of the split this code is really wrong but i dont know how to do what i want(yes it is for school)
public class Relatives
{
private Map<String,Set<String>> map;
/**
* Constructs a relatives object with an empty map
*/
public Relatives()
{
map = new TreeMap<String,Set<String>>();
}
/**
* adds a relationship to the map by either adding a relative to the
* set of an existing key, or creating a new key within the map
* #param line a string containing the key person and their relative
*/
public void setPersonRelative(String line)
{
String[] personRelative = line.split(" ");
if(map.containsKey(personRelative[0]))
{
map.put(personRelative[0],map.get(personRelative[1])+personRelative[1]);
}
else
{
map.put(personRelative[0],personRelative[1]);
}
}
im trying to access the person and add to there current relatives and if the dont exist create a new person with that relative
how would i format it so it returns like this
Dot is related to Chuck Fred Jason Tom
Elton is related to Linh
i have this but get error
public String getRelatives(String person)
{
return map.keySet();
}
You cannot add an item to a Set using the += operator; you must use the add method.
Also, you have to create the set the first time you are going to use it.
The fixed code could look like:
String[] personRelative = line.split(" ");
String person = personRelative[0];
String relative = personRelative[1];
if(map.containsKey(person))
{
map.get(person).add(relative);
}
else
{
Set<String> relatives = new HashSet<String>();
relatives.add(relative);
map.put(person,relatives);
}

Java - binarySearch(). How do I set up a binarySearch for a Spell Check

I am doing a Spell Check project. I have a words list and then the Gettysburg address with some words misspelled. my job is to identify which words are misspelled and then print out astericks or something under the misspelled word when i print out the Address. my issue is in the binarySearch part. Im not sure of the syntax and the javadoc looks like its in chinese. here is my souce code (binarySearch is towards the bottom)
/*
* Assignment 1: Spell Check
* Professor Subrina Thompson
* CS102
*/
package spellcheck;
import java.util.*;
import java.io.*;
public class SpellCheck {
//48,219 words in the words.txt
//Declare Variables
static FileReader reader;
static Scanner input;
static ArrayList <String> wordList = new ArrayList<String>();
static FileReader reader2;
static Scanner input2;
static String testWord;
static String index;
//Main Method
public static void main(String[] args) throws FileNotFoundException {
fileSort();
}
//Open file to be read from
public static void openFile() throws FileNotFoundException {
reader = new FileReader("words.txt");
input = new Scanner(reader);
}
//sort the file
public static void fileSort() throws FileNotFoundException{
openFile();
//read the word list into an ArrayList
while (input.hasNext()){
wordList.add(input.next());
}
//Sort the array
Collections.sort(wordList);
}
//read the gettysburg address
public static void gAddress()throws FileNotFoundException{
reader2 = new FileReader("gettysburg.txt");
input2 = new Scanner(reader2);
//create loop to place word from file into a var then test to see if it is in the dictionary
for(int i = 0; i < wordList.size(); i++){
//place the word into a variable
testWord = input2.next();
//test if the word is in the dictionary
index = Collections.binarySearch(wordList,testWord);
}
}
//compare the address and array through binary search
//print out if spelling is correct
}
PS. I know its not complete and with a lot of loose ends, its still a work-in-progress.
EDIT:
i tried making a new search function based off how i understand binarySearch works. this is the code for that function. the "string w" would be the dictionary word to test against the testWord from the Address:
public static int binarySearch(String w){
int start = 0;
int stop = wordList.size() - 1;
while (start != stop){
int half = ((stop - start)/2) + start;
int res = wordList.get(half).compareToIgnoreCase(w);
if( res == 0 ){
return half;
}
else if( stop - start <= 1 ){
return -1;
}
else if( res > 0 ){
start = half;
}
else if( res < 0 ){
stop = half;
}
}
return -1;
}
This is all you need:
if(index < 0) {
System.out.println(testWord + " not in dictionary");
}
In addition by examining the absolute value of index you can easily find words in dictionary that were alphabetically close to your mistyped word.
The javadoc looks like chinese cause the lists are Generic.
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
Should be read with T as any generic type, T is the type of the key.
The first parameter, the list, must be a list of a type that implements the Comparable interface for a type that T derives from.
In your case, T, the key type, is String. And it's a list of Strings. String implements Comparable, and String is a super class of String. So that is valid.
If you fill in String, the method signature turns into something more normal:
public static int binarySearch(List<String> list, String key)
So therefore, given
int index;
List<String> list;
String key;
an invocation looks like
index = Collections.binarySearch(list, key);
after which index will contain the index of the search key in the list, or a negative number if the key was not found. More precisely:
index of the search key, if it is contained in the list; otherwise,
(-(insertion point) - 1). The insertion point is defined as the point
at which the key would be inserted into the list: the index of the
first element greater than the key, or list.size(), if all elements in
the list are less than the specified key. Note that this guarantees
that the return value will be >= 0 if and only if the key is found.
create loop to place word from file into a var then test to see if it is in the dictionary
But that is not what you are doing. You are creating a loop to go through all the words in the dictionary and checking if the next word in the address is in dictionary and doing nothing about it, whether it is found or not.
If the dictionary has more words then address you will probably get exception, if the address has more words then you will not check all of them.

Java Inheritance - Getting a Parameter from Parent Class

I'm trying to take one parameter from the parent class of Car and add it to my array (carsParked), how can i do this?
Parent Class
public class Car
{
protected String regNo; //Car registration number
protected String owner; //Name of the owner
protected String carColor;
/** Creates a Car object
* #param rNo - registration number
* #param own - name of the owner
**/
public Car (String rNo, String own, String carColour)
{
regNo = rNo;
owner = own;
carColor = carColour;
}
/** #return The car registration number
**/
public String getRegNo()
{
return regNo;
}
/** #return A String representation of the car details
**/
public String getAsString()
{
return "Car: " + regNo + "\nColor: " + carColor;
}
public String getColor()
{
return carColor;
}
}
Child Class
public class Carpark extends Car
{
private String location; // Location of the Car Park
private int capacity; // Capacity of the Car Park - how many cars it can hold
private int carsIn; // Number of cars currently in the Car Park
private String[] carsParked;
/** Constructor for Carparks
* #param loc - the Location of the Carpark
* #param cap - the Capacity of the Carpark
*/
public Carpark (String locations, int room)
{
location = locations;
capacity = room;
}
/** Records entry of a car into the car park */
public void driveIn()
{
carsIn = carsIn + 1;
}
/** Records the departure of a car from the car park */
public void driveOut()
{
carsIn = carsIn - 1;
}
/** Returns a String representation of information about the carpark */
public String getAsString()
{
return location + "\nCapacity: " + capacity +
" Currently parked: " + carsIn +
"\n*************************\n";
}
}
Last Question Method
public String getCarsByColor (String carColour)
{
for (int num = 0; num < carsParked.length; num++)
{
if ( carColour.equals(carsParked[num]) )
{
System.out.print (carsParked[num]);
}
}
return carColour;
}
I have this so far so that if "red" is put in the parameters, it would list all the cars with the color red and it's corresponding information but does not seem to work ~_~.
You seem to have the wrong relationship here: a car park is not a car. I would recommend against using inheritance in either direction between these classes. And Carpark should probably just have an array or collection of cars.
Also note that the parameter carsIn isn't necessary - just get the length of the array of cars (or size() if it's a Collection).
Edit: Okay, ignoring the inheritance part, it seems like it makes sense to add cars when driveIn is called, and remove them when driveOut is called.
driveIn should probably take a Car as an argument, so the method can access the parameter you want to store (personally I would just store Car references, but fine). Since we're going to be adding and removing these parameters, it'll be much easier to use a List that can resize itself instead of an array, like ArrayList. For example:
private final List<String> carsRegNosParked = new ArrayList<String>();
public void driveIn(Car car) {
carsRegNosParked.add(car.getRegNo());
}
It's less clear what driveOut should do. It could take a specific registration number to remove:
public void driveOut(String regNo) {
carsRegNosParked.remove(regNo);
}
Or it could just indiscriminately remove a car, say the first car added:
public void driveOut() {
if (!carsRegNosParked.isEmpty()) {
carsRegNosParked.remove(0);
}
}
Note the difference between remove(Object) and remove(int).
First change carsParked to a list. So:
private String[] carsParked;
becomes
private List<String> carsParked;
Then in you constructor initialize it to an empty list by doing:
carsParked = new ArrayList();
Then in your drive in method, make it take a car parameter and pull the param you want:
public void driveIn(Car car) {
carsParked.add(car.getRegNo());
}
Also you do not need to keep track of the number of cars this way. Since you could always do carsParked.size() to find out.
Now I would probably change that list to be List<Car> instead of string and just dump the whole car in there. Sure you may only need one item right now, but who knows down the road, maybe you will need something else.
EDIT:
Sure you could do it with an simple array. The issue with that is sizing. Say you initially create an array of size 5, when you go to add the 6 item you will need to create a new larger array, copy the original data, then add the new item. Just more work. Now if the idea is you have a carpark, and it can have X number of spots then you initilize your array to that size from the begining.
public Carpark (String locations, int room){
location = locations;
capacity = room;
//this creates an array with the max number of spots
carsParked = new String[capacity];
//also good idea to init
carsIn = 0; //initial number of cars parked
}
then in your driveIn() method:
public void driveIn(Car car) {
carsParked[carsIn] =car.getRegNo();
carsIn=carsIn+1;
}
now driveOut()
public void driveOut(Car car) {
//loop through the array until we find the car
for (int i=0; i < carsParked.length; i=i+1){
if (car.getRegNo().equals(carsParked[i])){
//we found the car, so set the space null
carsParked[i] = null;
carsIn=carsIn-1;
//stop looping now
break;
}
}
}
Looks nice doesn't it. Well no it is not. Now the driveIn will not work, since we have null spots scattered all over the place. How do we fix it:
public void driveIn(Car car) {
//loop through the array until we find a null spot,
//then park the car
for (int i=0; i < carsParked.length; i=i+1){
if (carsParked[i] == null){
//we found the car, so set the space null
carsParked[i] = car.getRegNo();
carsIn=carsIn+1;
//stop looping now
break;
}
}
}
It could still be improved further. I would probably still change String[] carsParked to Car[] carsParked as to not throw away information.
I would also change the driveIn and driveOut methods to return booleans to indicate if the successfully parked or un-parked a car.
Final Edit:
Okay, if you want to keep track of what cars are parked in the car park and which spot they are in you need to know enough about each car to make it unique. In your case you may only need regNo. So when you call driveIn or driveOut you have to pass that information so we can store it at the appropriate index (parking spot) in the array. Otherwise all you will know is a car was parked somewhere, or that a car left. Not which spots are open.
So in short the parameter Car car in those two methods contain the information needed to uniquely identify each car that is being parked, or is leaving. Without it the car park instance would have no clue who is currently parked, or where they are parked.

Sorting an ArrayList in Java

So I'm having trouble figuring out how to update a TextArea with information that I submit from an generics arraylist. As of now the program creates a new Order:
Order d1 = new Order();
Then the user selects some data and pushes an add button, and the order is added to a TextArea. The problem I have is that I have to add the order to the correct spot in the list and update it each time. I"m only sorting it by one item. I'm not really sure how to do that using the CompareTo method.
public void actionPerformed(ActionEvent event)
{
ArrayList<Drink> DrinkArray = new ArrayList<Drink>();
if (event.getSource() == addcoffeeButton)
{
String coffeesize = (String) sizecoffeelist.getSelectedItem();
double coffeeprice = Double.parseDouble(pricecoffeeTextfield.getText());
String coffeetype = (String) cuptypecoffeelist.getSelectedItem();
String coffeecaffeine = (String) caffeineList.getSelectedItem();
String coffeeroom = (String) roomforcreamList.getSelectedItem();
String coffeeadditional = additionalflavorList.getText();
if ((coffeeadditional.isEmpty()))
coffeeadditional = "No Additional Flavor";
Drink d1 = new Coffee(coffeesize, coffeeprice, coffeetype, coffeecaffeine, coffeeroom, coffeeadditional);
DrinkArray.add(d1);
orderTextArea.append(d1);
So I would have to add the drink to the correct spot before adding it to the array and printing to the text area, but I'm not quite sure how to do that.
I'll assume that Drink implements Comparable. Look at the javadocs if you don't know what that means.
If that's true, you can do this:
List<Drink> drinks = new ArrayList<Drink>();
// add Drinks
Collections.sort(drinks); // now they're sorted according to your Comparable.
You can also instantiate a Comparator and pass it to the sorts method.
Something like this (make the getValue() function whatever you want):
public class DrinkComparator implements Comparator<Drink> {
public int compare(Drink d1, Drink d2) {
if (d1.getValue() < d2.getValue()) {
return -1;
} else if (d1.getValue() > d2.getValue()) {
return 1;
} else {
return 0;
}
}
public boolean equals(Object obj) {
return this.compare(this, (Drink)obj) == 0;
}
}
You basically need to pre-determine the insertion point where the "object" would be inserted...
Take a look at Collections.binarySearch(List<T>, T)
From the Java Docs
Returns:
the index of the search key, if it is contained in the list;
otherwise, (-(insertion point) - 1). The insertion point is defined as
the point at which the key would be inserted into the list: the index
of the first element greater than the key, or list.size() if all
elements in the list are less than the specified key. Note that this
guarantees that the return value will be >= 0 if and only if the key
is found.

Categories

Resources