Binary search trouble - java

so i'm trying to do a binary search through an array of DVD objects by movie director and i'm having a little trouble. when i run my binary search it only says that the director isn't in the movie collection when it is. i'm still not the best at searching yet so any suggestions to point me in the right direction would be appreciated.
public int binarySearch(String key) {
int low=0,high=collection.length-1,mid=(low+high)/2;
while (low <= high && collection[mid].getDirector().compareTo(key)!=0) {
if (key.compareTo(collection[mid].getDirector())>0){
low = mid + 1;
}
else {
high = mid - 1;
}
mid=(low+high)/2;
}
if (low>high){
System.out.print("the director is not in your dvd collection");
return -1;
}
else
System.out.print("the movie by director " + collection[mid].getDirector() + " is in index ");
return mid;
}

First of all,
make sure that your array is sorted by director, for example:
Comparator<DVD> comparator = Comparator.comparing(DVD::getDirector);
Arrays.sort(collection, comparator);
Then, use the binary search of the JDK:
int index = Arrays.binarySearch(collection, new DVD() {
#Override
String getDirector() {
return key;
}
}, comparator);
Thanks #Boris for simplifying my clumsy lambda!

Related

Searching for a String in an ArrayList of Objects

I have been trying to figure this out for hours and I have had no luck doing so,
I'm trying to iterate over my Arraylist<Booking> which utilizes my Booking class file and trying to understand how I'm able to search it for the matching, case-insensitive term.
this is my current method:
private void searchBookings() {
if (bookings.size() <= 0) {
JOptionPane.showMessageDialog(null, "There are no bookings.", "Search Bookings", 3);
} else {
String searchTerm = JOptionPane.showInputDialog(null, "Please input search term: ", "Search Bookings", 3);
for (int i = 0; i < bookings.size(); i++) {
while (!bookings.get(i).getStudent().getName().equalsIgnoreCase(searchTerm)) {
i++;
if (bookings.get(i).getStudent().getName().equalsIgnoreCase(searchTerm)) {
String output = String.format("%-30s%-18s%-18b$%-11.2f\n", bookings.get(i).getStudent(), bookings.get(i).getLessons(), bookings.get(i).isPurchaseGuitar(), bookings.get(i).calculateCharge());
this.taDisplay.setText(heading + "\n" + output + "\n");
}
}
}
}
JOptionPane.showMessageDialog(null, "There is no booking with that name.", "Search Bookings", 3);
}
I know it's messy but, just trying to make do.
I am trying to retrieve the name of the booking as I am searching by name as well as provide an error message if that names does not exist, to do that I must
use bookings.getStudent().getName() I have had some luck as I can return the value but now I am not able to provide my error message if I do not find it. Any help is appreciated.
package com.mycompany.mavenproject1;
public class Booking {
private Student student;
private int lessons;
private boolean purchaseGuitar;
// CONSTANTS
final int firstDiscountStep = 6;
final int secondDiscountStep = 10;
final int tenPercentDiscount = 10;
final int twentyPercentDiscount = 5;
final double LESSON_COST = 29.95;
final double GUITAR_COST = 199.00;
double LESSON_CHARGE = 0;
final int MINIUMUM_LESSONS = 1;
public Booking() {
}
public Booking(Student student, int lessons, boolean purchaseGuitar) {
this.student = new Student(student.getName(), student.getPhoneNumber(), student.getStudentID());
this.lessons = lessons;
this.purchaseGuitar = purchaseGuitar;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public int getLessons() {
return lessons;
}
public void setLessons(int lessons) {
this.lessons = lessons;
}
public boolean isPurchaseGuitar() {
return purchaseGuitar;
}
public void setPurchaseGuitar(boolean purchaseGuitar) {
this.purchaseGuitar = purchaseGuitar;
}
public double calculateCharge() {
double tempCharge;
if (lessons < firstDiscountStep) {
LESSON_CHARGE = (lessons * LESSON_COST );
} else if (lessons < secondDiscountStep) {
tempCharge = (lessons * LESSON_COST) / tenPercentDiscount;
LESSON_CHARGE = (lessons * LESSON_COST) - tempCharge;
} else {
tempCharge = (lessons * LESSON_COST) / twentyPercentDiscount;
LESSON_CHARGE = (lessons * LESSON_COST) - tempCharge;
}
if (isPurchaseGuitar()) {
LESSON_CHARGE += GUITAR_COST;
}
return LESSON_CHARGE;
}
#Override
public String toString() {
return student + ","+ lessons + "," + purchaseGuitar +"," + LESSON_COST;
}
}
If I understood you correctly, you are searching for a given student name in your collection of bookings. And if it is present, set a formatted text.
First of all, use a for-each loop, because you don't use the index.
Secondly, return from the for-each loop, when you found your student.
private void searchBookings() {
if (bookings.size() <= 0) {
JOptionPane.showMessageDialog(null, "There are no bookings.", "Search Bookings", 3);
} else {
String searchTerm = JOptionPane.showInputDialog(null, "Please input search term: ", "Search Bookings", 3);
for (final Booking booking : bookings) // for-each
{
if (booking.getStudent().getName().equalsIgnoreCase(searchTerm))
{
String output = booking.getFormattedOutput();
this.taDisplay.setText(heading + "\n" + output + "\n");
return; // break out of the loop and method and don't display dialog message
}
}
}
JOptionPane.showMessageDialog(null, "There is no booking with that name.", "Search Bookings", 3);
}
Then there are multiple other things, which you could improve.
Don't get all the data from a booking just to format it externally. Let the Booking class handle the formatting and return you the string you desire. (move the formatting in a function inside the Booking class)
Instead of recreating a Student you receive in your Booking constructor, make the Student class immutable, and then you can just reuse the object provided.
Try also making the Booking class immutable. You provided some setters, but do you really want to change the student in a booking? Or would you rather create a new booking for the other student?
The calculteCharge method could be stateless. Just get the LESSON_CHARGE value and hold it in a local variable. Your method would also get threading-proof.
Make your constants final and better yet make them members of the class (by adding the static modifier) instead of every member.
Lastly, representing a money amount with a floating (double is better but not good either) number, you will run into funny situations. Try this calculation: 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 for example.
One way would be to create a Money class which holds the value in cents as an integer. And when you want to display the amount you can divide it by 100 and format it accordingly. That way, you can also restrict it become negative.
PS: Sometimes we desperately try to find a solution that we don't give ourselves some rest. After a little break, you might recognize the problem. Oh and try debugging with breakpoints. Or this, if you use IntelliJ IDEA (which I would highly recommend, the community edition is free).
You're re-incrementing your counter variable, which is really not going to help. Try the following:
private void searchBookings() {
if (bookings.size() <= 0) {
JOptionPane.showMessageDialog(null, "There are no bookings.", "Search Bookings", 3);
} else {
String searchTerm = JOptionPane.showInputDialog(null, "Please input search term: ", "Search Bookings", 3);
boolean studentFound = false;
for (int i = 0; i < bookings.size(); i++) {
if (bookings.get(i).getStudent().getName().equalsIgnoreCase(searchTerm)) {
String output = String.format("%-30s%-18s%-18b$%-11.2f\n", bookings.get(i).getStudent(),
bookings.get(i).getLessons(), bookings.get(i).isPurchaseGuitar(),
bookings.get(i).calculateCharge());
this.taDisplay.setText(heading + "\n" + output + "\n");
studentFound = true;
break;
}
}
}
if (!studentFound) {
JOptionPane.showMessageDialog(null, "There is no booking with that name.", "Search Bookings", 3);
}
}

Staircase problem: How to print the combinations?

Question:
In this problem, the scenario we are evaluating is the following: You're standing at the base of a staircase and are heading to the top. A small stride will move up one stair, and a large stride advances two. You want to count the number of ways to climb the entire staircase based on different combinations of large and small strides. For example, a staircase of three steps can be climbed in three different ways: three small strides, one small stride followed by one large stride, or one large followed by one small.
The call of waysToClimb(3) should produce the following output:
1 1 1,
1 2,
2 1
My code:
public static void waysToClimb(int n){
if(n == 0)
System.out.print("");
else if(n == 1)
System.out.print("1");
else {
System.out.print("1 ");
waysToClimb(n - 1);
System.out.print(",");
System.out.print("2 ");
waysToClimb(n - 2);
}
}
My output:
1 1 1,
2,
2 1
My recursion doesn't seem to remember the path it took any idea how to fix it?
Edit:
Thank you guys for the responses. Sorry for the late reply
I figured it out
public static void waysToClimb(int n){
String s ="[";
int p=0;
com(s,p,n);
}
public static void com(String s,int p,int n){
if(n==0 && p==2)
System.out.print(s.substring(0,s.length()-2)+"]");
else if(n==0 && p !=0)
System.out.print(s+"");
else if(n==0 && p==0)
System.out.print("");
else if(n==1)
System.out.print(s+"1]");
else {
com(s+"1, ",1,n-1);
System.out.println();
com(s+"2, ",2,n-2);
}
}
If you explicity want to print all paths (different than counting them or finding a specific one), you need to store them all the way down to 0.
public static void waysToClimb(int n, List<Integer> path)
{
if (n == 0)
{
// print whole path
for (Integer i: path)
{
System.out.print(i + " ");
}
System.out.println();
}
else if (n == 1)
{
List<Integer> newPath = new ArrayList<Integer>(path);
newPath.add(1);
waysToClimb(n-1, newPath);
}
else if (n > 1)
{
List<Integer> newPath1 = new ArrayList<Integer>(path);
newPath1.add(1);
waysToClimb(n-1, newPath1);
List<Integer> newPath2 = new ArrayList<Integer>(path);
newPath2.add(2);
waysToClimb(n-2, newPath2);
}
}
initial call: waysToClimb(5, new ArrayList<Integer>());
Below mentioned solution will work similar to Depth First Search, it will explore one path. Once a path is completed, it will backtrace and explore other paths:
public class Demo {
private static LinkedList<Integer> ll = new LinkedList<Integer>(){{ add(1);add(2);}};
public static void main(String args[]) {
waysToClimb(4, "");
}
public static void waysToClimb(int n, String res) {
if (ll.peek() > n)
System.out.println(res);
else {
for (Integer elem : ll) {
if(n-elem >= 0)
waysToClimb(n - elem, res + String.valueOf(elem) + " ");
}
}
}
}
public class Test2 {
public int climbStairs(int n) {
// List of lists to store all the combinations
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// initially, sending in an empty list that will store the first combination
csHelper(n, new ArrayList<Integer>(), ans);
// a helper method to print list of lists
print2dList(ans);
return ans.size();
}
private void csHelper(int n, List<Integer> l, List<List<Integer>> ans) {
// if there are no more stairs to climb, add the current combination to ans list
if(n == 0) {
ans.add(new ArrayList<Integer>(l));
}
// a necessary check that prevent user at (n-1)th stair to climb using 2 stairs
if(n < 0) {
return;
}
int currStep = 0;
// i varies from 1 to 2 as we have 2 choices i.e. to either climb using 1 or 2 steps
for(int i = 1; i <= 2; i++) {
// climbing using step 1 when i = 1 and using 2 when i = 2
currStep += 1;
// adding current step to the arraylist(check parameter of this method)
l.add(currStep);
// make a recursive call with less number of stairs left to climb
csHelper(n - currStep, l, ans);
l.remove(l.size() - 1);
}
}
private void print2dList(List<List<Integer>> ans) {
for (int i = 0; i < ans.size(); i++) {
for (int j = 0; j < ans.get(i).size(); j++) {
System.out.print(ans.get(i).get(j) + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
Test2 t = new Test2();
t.climbStairs(3);
}
}
Please note this solution will timeout for larger inputs as this isn't a memoized recursive solution and can throw MLE(as I create a new list when a combination is found).
Hope this helps.
if anyone looking for a python solution, for this problem.
def way_to_climb(n, path=None, val=None):
path = [] if path is None else path
val = [] if val is None else val
if n==0:
val.append(path)
elif n==1:
new_path = path.copy()
new_path.append(1)
way_to_climb(n-1, new_path, val)
elif n>1:
new_path1 = path.copy()
new_path1.append(1)
way_to_climb(n-1, new_path1, val)
new_path2 = path.copy()
new_path2.append(2)
way_to_climb(n-2, new_path2, val)
return val
Note: it is based on the #unlut solution, here OP has used a top-down recursive approach. This solution is for all people who looking for all combination of staircase problem in python, no python question for this so i have added a python solution here
if we use a bottom-up approach and use memorization, then we can solve the problem faster.
Even though you did find the correct answer to the problem with your code, you can still improve upon it by using just one if to check if the steps left is 0. I used a switch to check the amount of steps taken because there are only 3 options, 0, 1, or 2. I also renamed the variables that were used to make the code more understandable to anyone seeing it for the first time, as it is quite confusing if you are just using one letter variable names. Even with all these changes the codes run the same, I just thought it might be better to add some of these things for others who might view this question in the future.
public static void climbStairsHelper(String pathStr, int stepsTaken, int stepsLeft)
{
if(stepsLeft == 0)
{
switch(stepsTaken)
{
case 2:
System.out.print(pathStr.substring(0, pathStr.length() - 2) + "]");
break;
case 1:
System.out.print(pathStr + "");
break;
case 0:
System.out.print("");
break;
}
}
else if(stepsLeft == 1)
{
System.out.print(pathStr + "1]");
}
else
{
climbStairsHelper(pathStr + "1, ", 1, stepsLeft - 1);
System.out.println();
climbStairsHelper(pathStr + "2, ", 2, stepsLeft - 2);
}
}`
`

Search 2D ArrayList by user input & display row in Java

I'm having a little trouble with a fairly simple assignment, but couldn't find an answer here that seemed to work.
I need to have a 2D ArrayList that contains staff data, and then be able to run a separate function that allows the user to input a staff member's name, searches the ArrayList for that name, and if it exists, display the full row of data.
Here's what I've got so far:
The ArrayList
List<List<String>> staffArrayString = new ArrayList<>();
staffArrayString.add(Arrays.asList("Steven George", "12 York Road", "07123456678", "Permanent", "York", "27000/yr"));
staffArrayString.add(Arrays.asList("Rina Veyer", "20 Leeds Road", "08987987765", "Part Time", "Leeds", "10/hr"));
staffArrayString.add(Arrays.asList("Brian Lym", "13 Bradford Road", "07123234345", "Permanent", "Bradford", "27000/yr"));
The search function
public void staffNameSearch() {
System.out.println("Enter name of staff member:");
String staffName = in.next();
boolean found = false;
int row = 0;
for (int i = 0; i < staffArrayString.size(); i++) {
for (int j = 0; j < staffArrayString.get(i).size(); j++) {
if (staffArrayString.get(i).get(j).equals(staffName)) {
row = staffArrayString.get(i).size();
found = true;
}
}
}
if (found = true) {
System.out.print(staffArrayString.get(row) + " ");
}
}
I'm currently getting an output of 'Exception in thread "main" java.lang.IndexOutOfBoundsException' at the print line on the end there, but I can't for the life of me work out why. I'd appreciate any advice on this (especially if it's some obvious and stupid mistake on my part!).
The error is occuring because you are setting row to something unrelated to the row counter. When you discover the row (variable i) which has the name in the jth element, set row=i.
Be careful about if (found = true) - it is incorrect; prefer:
a) if (found)
b) if (found == true)
For efficiency, include && !found in the for loops so they exit as soon as you find something.
You can use for each loop for simpler.
System.out.println("Enter name of staff member:");
String staffName = in.next();
boolean found = false;
String[] foundArray;
for(String[] staffArray: staffArrayString){
for(String str : staffArray){
if(str.equals(staffName)){
foundArray = staffArray;
found = true;
break;
}
}
}
if (found == true) {
System.out.print(foundArray + " ");
}
You might be able to simplify the code a little bit by using a Map and a Staff class.
For example, the Staff class
public class Staff{
String name;
String address;
String id; // ?
boolean permanent; // Or a enum if there are more than 2 values
String city; // ?
int payrate;
boolean hourly;
#Override
public String toString(){ // Easily convert the class to a String
return String.format("%s %s %s %s %s $%d/%s",
name,
address,
id,
permanent ? "Permanent" : "Part-time",
city,
payrate,
hourly ? "hr" : "yr");
}
}
And for the code to read it
private Map<String, Staff> staffDirectory; // String is the name in lowercase
public void staffNameSearch() {
// Code
if(staffDirectory.containsKey(staffName)){ // Make sure staffName is lowercase
System.out.print(staffDirectory.get(staffName) + " ");
} else {
System.out.println("Name not found");
}
}
This way you can avoid using loops and get O(1) efficiency.

Finding information stored in array by binary search

This is a small library with two books for the sake of the question, it allows the user to type in a random number, and if that number matches up with a book the title of the book is outputted. I've created a class called 'Book' which houses all the titles.
String book1, book2;
class Book {
Book (int _input, String book_1, String book_2) {
book1 = book_1 = "Read This Book";
book2 = book_2 = "How to Read a Book";
I apologize if my code is all one big mess that makes no sense...
}
}
ArrayList <Book> titles = new ArrayList <Book>(50);
public static Boolean binarySearch(String [] A, int left, int right, String V) { //binary search
int middle;
Boolean found = false;
while (found == false && left <= right) {
//If middle item == 0, returns true
middle = (left + right)/2;
int compare = A[middle].compareTo(V);
if (compare == 0) {
found = true;
} else {
if (compare >0) {
right = middle -1;
} else {
left = middle + 1;
}
}
}
if (left > right) {
return false;
} else {
return true;
}
}
Then the problem...I'm not sure how to use the binary search to actually output any information after pressing the "find" button, any ideas on what I should below to make this work?
private void findButtonActionPerformed(java.awt.event.ActionEvent evt) {
//Take inputted values which will match with book title
int input = Integer.parseInt(enterNumberField.getText());
//Store values in array
Book c = new Book (input, book1, book2);
titles.add(c);
String temp;
//calls out information in array
for (int j=0; j<=input; j++) {
for (int x=0; x<=input; x++) {
temp = titles.get(x) + "\n";
}
binarySearchField.setText("" + j); //should output book title
}
You want your binary search to return not just a true or false. You want it to return Book, the item it found, or null if it found no book matching this query. To be consistent you probably want to change the name from binarySearch, to getBook, or some other better suited name. In your case you don't want to know if an element is there, you want to get the element for use later (printing).
This is how collections are expected to behave when you query them. Just check out the get methods from any of the Java collections and you will see they do the same, returning the item if it's there, or null.
Here is some example code. This is just example code! So modify as you like, and also be careful about bugs, I used your search which I'm going to assume is correct to start with. Also know that there are better many good ways of storing a key to a value, Map for example, that I'm not going to use here.
public class Book{
public String title;
public int sameTitle(String bookTitle) {
return this.title.compareTo(bookTitle);
}
}
public static Book getBook(Book [] A, int left, int right, String bookTitle) { //binary search
int middle;
while (left <= right) {
//If middle item == 0, returns true
middle = (left + right)/2;
int compare = A[middle].sameTitle(bookTitle);
if (compare == 0) {
return A[middle];
} else {
if (compare >0) {
right = middle -1;
} else {
left = middle + 1;
}
}
}
return null;
}
// example use of getting and using the book
Book b = getBook(...);
if (b != null){
System.out.println("Success! you found the book " + b);
}
Try to change this line:
int compare = A[middle].compareTo(V);
if (compare == 0) {
found = true;
To:
int compare = A[middle].compareTo(V);
if (compare == 0) {
return A[middle];
And be sure to get the result in your findButtonActionPerformed method.
Also, it appears to be a mistake in your code... Should not A be a book array instead of a string array?

Find Range of a number from an array

I'm just rephrasing the question I asked a little while ago.
I have a sorted array {2.0,7.8,9.0,10.5,12.3}
If I given an input 9.5
What is the fastest way to find 9.0 and 10.5 to indicate that 9.5 is in between 9.0 and 10.5 (9.5 >=9.0 and <10.5) ?
Is binary search an option?But since the input need not be in the array.I'm not sure how I should do this.
Also If there is any other data structure that is suitable please comment.
A binary search would certainly be the "standard" approach - http://en.wikipedia.org/wiki/Binary_search_algorithm. Speed is O(log(N)) as opposed to linear.
In certain specialised cases you can do better than O(log(N)). But unless you are dealing with truly gigantic array sizes and satisfy these special cases then your binary search is really the fastest approach.
You could use Arrays.binarySearch to quickly locate 9.0 and 10.0, indeed.
Here's a binary search algorithm I just wrote for you that does the trick:
import java.util.Random;
public class RangeFinder {
private void find(double query, double[] data) {
if (data == null || data.length == 0) {
throw new IllegalArgumentException("No data");
}
System.out.print("query " + query + ", data " + data.length + " : ");
Result result = new Result();
int max = data.length;
int min = 0;
while (result.lo == null && result.hi == null) {
int pos = (max - min) / 2 + min;
if (pos == 0 && query < data[pos]) {
result.hi = pos;
} else if (pos == (data.length - 1) && query >= data[pos]) {
result.lo = pos;
} else if (data[pos] <= query && query < data[pos + 1]) {
result.lo = pos;
result.hi = pos + 1;
} else if (data[pos] > query) {
max = pos;
} else {
min = pos;
}
result.iterations++;
}
result.print(data);
}
private class Result {
Integer lo;
Integer hi;
int iterations;
long start = System.nanoTime();
void print(double[] data) {
System.out.println(
(lo == null ? "" : data[lo] + " <= ") +
"query" +
(hi == null ? "" : " < " + data[hi]) +
" (" + iterations + " iterations in " +
((System.nanoTime() - start) / 1000000.0) + " ms. )");
}
}
public static void main(String[] args) {
RangeFinder rangeFinder = new RangeFinder();
// test validation
try {
rangeFinder.find(12.4, new double[] {});
throw new RuntimeException("Validation failed");
} catch (IllegalArgumentException e) {
System.out.println("Validation succeeded");
}
try {
rangeFinder.find(12.4, null);
throw new RuntimeException("Validation failed");
} catch (IllegalArgumentException e) {
System.out.println("Validation succeeded");
}
// test edge cases with small data set
double[] smallDataSet = new double[] { 2.0, 7.8, 9.0, 10.5, 12.3 };
rangeFinder.find(0, smallDataSet);
rangeFinder.find(2.0, smallDataSet);
rangeFinder.find(7.9, smallDataSet);
rangeFinder.find(10.5, smallDataSet);
rangeFinder.find(12.3, smallDataSet);
rangeFinder.find(10000, smallDataSet);
// test performance with large data set
System.out.print("Preparing large data set...");
Random r = new Random();
double[] largeDataSet = new double[20000000];
largeDataSet[0] = r.nextDouble();
for (int n = 1; n < largeDataSet.length; n++) {
largeDataSet[n] = largeDataSet[n - 1] + r.nextDouble();
}
System.out.println("done");
rangeFinder.find(0, largeDataSet);
rangeFinder.find(5000000.42, largeDataSet);
rangeFinder.find(20000000, largeDataSet);
}
}
I would do it like that
double valuebefore = 0;
double valueafter = 0;
double comparevalue = 9;
foreach (var item in a)
{
valueafter = item;
if (item > comparevalue)
{
break;
}
valuebefore = item;
}
System.Console.WriteLine("Befor = {0} After = {1}", valuebefore, valueafter);
If the input numbers are in an array then binary search will be handy. Every time the search fails, indicating the number is not present in the array, the array elements at index low and high will give you the range.
The most efficient (space and time-wise) is to implement this as a modified binary search.
A simple (but less efficient) solution is to replace the array with a NavigableMap<Double, Double> and use floorKey and ceilingKey to find the bounding values. Assuming that you use a TreeMap, this has the same complexity as binary search.
For small numbers of bins a sorted linked list will be most elegant. You scan over it and when you find a number bigger you have the range.
For very large numbers it is worth putting them in a BTree or similar Tree structure in order to get O(log(N)) performance.
In Java you can use a TreeSet for this.
lowerBound = boundaries.headSet(yourNumber).last();
upperBound = boundaries.tailSet(yourNumber).first();
or similar will be O(logN) for large numbers.

Categories