Need assistance reducing duplicate code - java

I'm a beginner and the lecturer told me to reduce the duplicate code in these two functions. This is a library. I guess I need one more method for checking through the array. All I need is some advice/principle I need to use. I'll write the method myself. Thanks!
getBooks() returns the ArrayList where Books are stored.
public List<Book> getAvailableBooks() {
List<Book> result = new ArrayList<Book>();
for (int i = 0; i < this.getBooks().size(); i++) {
if (this.getBooks().get(i).getPerson() == null) {
result.add(this.getBooks().get(i));
}
}
return result;
}
public List<Book> getUnavailableBooks() {
List<Book> result = new ArrayList<Book>();
for (int i = 0; i < this.getBooks().size(); i++) {
if (this.getBooks().get(i).getPerson() != null) {
result.add(this.getBooks().get(i));
}
}
return result;
}

You already have two methods. You can't reduce by adding one more.
But you can have one method instead of two. e.g.
public List<Book> getBooks(boolean available) {
Now you have one method, and you can tell it whether you want available or unavailable books.

Pass in a boolean parameter that should indicate what
(this.getBooks().get(i).getPerson() == null)
Should evaluate to, and add a condition to check that. I.e. should this expression return true or false.

Here're some advice:
Use enhanced for loop instead of indexed one. This will avoid using this.getBooks().get(i) twice in each method.
Unavailable books + Available books = Total books. Use this equation to avoid writing all the codes in both the methods. You might want to use a Set<Book> instead of List<Book> to make this easier to work with. [HINT: Set Difference].
Also, rather than doing the null check in those methods, I'll add a method isAvailable() inside the Book class only, which will return true if it is available, else false.

In the general case, if you see a very common pattern repeated in your code, there is often an opportunity to reduce duplication. The first step is to look at your code and identify the actual differences. You have:
public List<Book> getAvailableBooks() {
List<Book> result = new ArrayList<Book>();
for (int i = 0; i < this.getBooks().size(); i++) {
if (this.getBooks().get(i).getPerson() == null) {
result.add(this.getBooks().get(i));
}
}
return result;
}
public List<Book> getUnavailableBooks() {
List<Book> result = new ArrayList<Book>();
for (int i = 0; i < this.getBooks().size(); i++) {
if (this.getBooks().get(i).getPerson() != null) {
result.add(this.getBooks().get(i));
}
}
return result;
}
Ignoring the method names, let's do a line-by-line comparison. Doing this, we can spot the difference:
if (this.getBooks().get(i).getPerson() == null) {
// vs:
if (this.getBooks().get(i).getPerson() != null) {
The only difference there is == vs. !=; the condition is inverted. After identifying the differences, the next step is to see if you can parameterize the behavior, so that the logic itself is exactly the same and depends only on the value of a few outside variables. In this case, we can see a transformation like this:
a == x => (a == x) == true
a != x => (a == x) == false
both: => (a == x) == <variable>
So we can make those two lines equivalent:
boolean available = true;
if ((this.getBooks().get(i).getPerson() == null) == available) {
// vs:
boolean available = false;
if ((this.getBooks().get(i).getPerson() == null) == available) {
Now the logic is equivalent, and we can select between the two by simply changing available. Make that a method parameter and, voila!
public List<Book> getBooksByAvailability (bool available) {
List<Book> result = new ArrayList<Book>();
for (int i = 0; i < this.getBooks().size(); i++) {
if ((this.getBooks().get(i).getPerson() == null) == available) {
result.add(this.getBooks().get(i));
}
}
return result;
}
Note that another way to approach this problem is to make "availability" itself be a property of a book. For example, if you move the availability test into a new method Book.isAvailable(), the solution becomes a bit more obvious:
public List<Book> getBooksByAvailability (bool available) {
List<Book> result = new ArrayList<Book>();
for (int i = 0; i < this.getBooks().size(); i++) {
if (this.getBooks().get(i).isAvailable() == available) {
result.add(this.getBooks().get(i));
}
}
return result;
}
And that has the added bonus of letting you change the internal definition of "availabilty" without modifying code anywhere else (e.g. if, in the future, you decide that getPerson() == null is not a sufficient indication of "availability", you only need to change it in Book.isAvailable()).
As for clarity, you could, as Rohit Jain mentioned in this answer, switch to enhanced for loops to improve readability a bit, e.g.:
public List<Book> getBooksByAvailability (bool available) {
List<Book> result = new ArrayList<Book>();
for (Book book : this.getBooks()) {
if (book.isAvailable() == available) {
result.add(book);
}
}
return result;
}
To keep your two existing functions, if that's necessary, you can use something like the above as a private utility method, called by the two public ones.

If you don't want to change the method signature, I think you cannot do much better than you already have. You can just rewrite the loops to foreach and possibly do the substractions of the lists. Dirty solution, but the lecturer may take it.
public List<Book> getAvailableBooks() {
Set<Book> result = new HashSet<>(getBooks());
result.removeAll(getUnavailableBooks());
return new ArrayList<Book>(result);
}
public List<Book> getUnavailableBooks() {
List<Book> result = new ArrayList<Book>();
for (Book b: getBooks()) {
if (b.getPerson() != null) {
result.add(b);
}
}
return result;
}
Probably not the fastest solution, but quite short

Related

Compare by multiple methods in java compareTo? [duplicate]

This question already has answers here:
How to compare objects by multiple fields
(23 answers)
Closed 3 years ago.
I don't think that's the best way to word that title but I can't think of a better way to word it. Here's my problem: I have to write a method that compares in several different ways. If the last names are the same, I then need to compare by first name. If the first names are the same, then I need to sort by section. What would be the most effective way to sort a data structure in this hierarchy? Here's what I've currently got and I think I understand why it doesn't work but I can't come up with a different way to write this function:
//Student class structure, each field has a public get/set method
public class Student implements Comparable<Student>
{
private String fname;
private String lname;
private int section;
}
//My current compareTo method
#Override
public int compareTo(Student s)
{
/*
-compare by last name
-if the same, compare by first name
-if the same, compare by section
*/
String slast = s.getLastName();
if(lname.compareTo(slast) == 0)
{
String sfirst = s.getFirstName();
if(fname.compareTo(sfirst) == 0)
{
int sclass = s.getSection();
return Integer.compare(section, sclass);
}
else
{
return fname.compareTo(sfirst);
}
}
else
{
return lname.compareTo(slast);
}
}
You can create a Comparator for your Student class this way:
Comparator<Student> comparator = Comparator
.comparing(Student::getLastName)
.thenComparing(Student::getFirstName)
.thenComparing(Student::getSection);
And then use this comparator (instead of implementing Comparable interface) to sort a list with Student objects, or to create a TreeMap with these objects:
Collections.sort(listOfStudents, comparator);
TreeMap<Student> mapOfStudents = new TreeMap<>(comparator);
You don't have to use getters or setters if you're overriding compareTo. You can also forgo the else/return statements since they're terminal return statements, and just use return.
#Override
public int compareTo(Student s) {
if (lname.compareTo(s.lname) == 0) {
if (fname.compareTo(s.fname) == 0) {
return section.compareTo(s.section);
}
return fname.compareTo(s.fname);
}
return lname.compareTo(s.lname);
}
Your code looks correct to me.
What would be the most effective way to sort a data structure in this
hierarchy?
Well, it's worth mentioning that you are potentially doing the first two comparisons (first name and last name) multiple times
if(lname.compareTo(slast) == 0)
{
//...
}
else
{
return lname.compareTo(slast);
}
It should be fairly obvious that you are doing lname.compareTo(slast) twice. You can store the result in a variable instead.
int lastNameComparison = lname.compareTo(slast);
if(lastNameComparison == 0)
{
//...
}
else
{
return lastNameComparison;
}
It is a matter of style, but I would not bother to store the result of getters into variables. Just call them when you need them.
Combining both of the above points, you get:
int lastNameComparison = lname.compareTo(s.getLastName();
if (lastNameComparison == 0)
{
int firstNameComparison = fname.compareTo(s.getFirstName());
if (firstNameComparison == 0)
{
return Integer.compare(section, s.getSection());
}
else
{
return firstNameComparison;
}
}
else
{
return lastNameComparison;
}
The nesting is quite ugly and if we need to add another criteria, it would get even worse.
We can solve that by inverting the conditions and using multiple return statements.
int lastNameComparison = lname.compareTo(s.getLastName());
if (lastNameComparison != 0) return lastNameComparison;
// Last names must be equal
int firstNameComparison = fname.compareTo(s.getFirstName());
if (firstNameComparison != 0) return firstNameComparison;
// First names must be equal
return Integer.compare(section, s.getSection());
I would personally use the declarative style of writing this, but if this code is for an assignment, that is likely not what they are expecting.

Adding items into Inventory confusion

The Inventory consists of two arrays, one an array of objects[10] and one an array of ints[10]. The array of objects is to identify an item, and the array of ints is supposed to keep track of how many you have. For some reason the code is producing all kinds of errors. Not really sure what to do!
public void additem(Object newItem) {
if (itemsInInventory == 0) {
invent[0] = newItem;
inventItemAmount[0]++;
itemsInInventory++;
}else if (itemsInInventory > 0) {
for (int i = 0; i < itemsInInventory; i++) {
if (invent[i].getItemNum() == newItem.getItemNum()) {
inventItemAmount[i]++;
} else {
invent[itemsInInventory] = newItem;
inventItemAmount[itemsInInventory]++;
itemsInInventory++;
}
}
}
}
Complete code can be found here: https://github.com/YungSheep/HitsujiStories
I see in your GitHub code that your inventory is limited to 10 ; and your if-else condition doesn't hold any case for itemsInInventory > 10, that will first give you an idea of where your NPE comes from. It might be better for you to change your current else if condition to something like "a > 0 && a <= max" then add a case when it's higher than your max capacity.
EDIT : Also, I'm pretty sure I know why you get messed up amounts for each type of item : if you imagine the if-else statement inside a loop, the item slot [0] would only be accessible once, when the player has an empty inventory. That means I can't add up any further in the slot [0] if I picked an item and set itemsInInventory to another number than 0 ! You might have to rebuild your if-else contents.
SECOND EDIT : In case you find it messy to code, I suggest you to make an InventorySlot class :
public class InventorySlot {
private Object object;
private int amount;
// CONSTRUCTOR (assuming you don't instanciate filled slots)
public InventorySlot() {
this.setObject(null);
this.setAmount(0);
}
// GETTERS AND SETTERS
public Object getObject() {
return this.object;
}
public int getAmount() {
return this.amount;
}
public void setObject(final Object object) {
this.object = object;
}
public void setAmount(final int amount) {
this.amount = amount;
}
// METHOD THAT ADDS NEW ITEM IF MATCHES. RETURNS BOOLEAN TO TELL IF SUCCEEDED
public bool addIfMatches(final Object object) {
if (this.getObject.getItemNum() == object.getItemNum()) {
this.setAmount(this.getAmount++);
return true;
} else {
return false;
}
}
// AND OTHER USEFUL METHODS...
}
Hope this helps you, happy coding !

How to check all members of an array

I am writing a text-based survival simulator that uses an array of Entitys. The Entity class contains data about each entity, such as energy, hydration, and morale. I'm starting to wrap up the project but I have a problem. In order to write a checkStatus() method, I need to have an if statement that checks for Entity[].isAlive on all entities, even if I don't know how long the array is. In short, how can I use an if statement to check for the value of all members of an array? I know I will probably have to use a for loop to iteratively check the members, with the array.getLength. So far I can only check variables in single classes. I have seen similar questions but they don't quite get what I'm looking for.
P.S. I'm using basic Java, with no frameworks or libraries.
Pseudo-code that demonstrates what I'm looking for
if Entity[ALL-MEMBERS].isAlive {
gameOver = true;
}
Java 6, 7, and 8:
public boolean areAllAlive(Entity[] entities) {
if(entities == null || entities.length == 0) {
return false; //?
}
for(Entity e : entities) {
if(!e.isAlive()) {
return false;
}
}
return true;
}
Java 8, using streams/functions:
public boolean areAllAlive(Entity[] entities) {
if(entities == null || entities.length == 0) {
return false; //?
}
return Arrays.stream(entities).allMatch(e -> e.isAlive());
}
First, since you probably don't know the number of Entities you are going to use before hand an ArrayList is probably a better choice. Then yes, you should use an enhanced for loop:
List<Entity> list = new ArrayList<>();
public void addEntities(){
//add Entities here
}
public boolean ifAlive(){
for (Entity e: list){
if (!e.isAlive()){
return false;
}
}
return true;
}
or something like that.
Assuming array is your entity's array, try this.:
for (int i = 0; i < array.length; i++) {
Entity entity = array[i];
if (entity.isAlive) {
gameOver = true;
//your code here
}
}

Use method to compare lists

I'm trying to use a method to compare t2o different lists. Basically I want to pass two different lists to a method which will return true or false if the elements of one array list are contained in the other using .contains. Right now it only returns true - and I'm not sure why. I'd like it to return false. If someone could help me figure this out, that would be great.
public class ArrayListTest {
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>();
List<String> list2 = new ArrayList<String>();
list1.add("cat");
list1.add("dog");
list1.add("zebra");
list1.add("lion");
list1.add("mouse");
//Test Values
//list2.add("cat");
list2.add("lizard");
boolean doesitcontain = contains(list1, list2);
System.out.println(doesitcontain);
}
public static boolean contains (List<String>list1, List<String>list2){
boolean yesitcontains;
for(int i = 0; i < list1.size(); i++){
if(list2.contains(list1.get(i))){
System.out.println("Duplicate: "+list1.get(i));
yesitcontains = true;
System.out.println(yesitcontains);
}else{
yesitcontains = false;
System.out.println(yesitcontains);
}
}
if (yesitcontains = true){
return true;
}else
return false;
}
}
You have inadvertently used the assignment operator where you intended the equality operator. In your specific case you should rewrite all this:
if (yesitcontains = true){
return true;
}else
return false;
}
to just
return yesitcontains;
and avoid any chance of confusion.
Furthermore, your algorithm will not work because you should return true immediately when you see a duplicate. Instead you go on with the loop and "forget" your finding. You can expect this to always return false except if the very last elements coincide.
In a wider context, I should also give you the following general advice:
Avoid indexed iteration over lists. Not all lists are ArrayLists and may show O(n) complexity for get(i). Instead use the enhanced for loop, which is safer, more concise, and more obvious;
Know the library: if you're just after confirming there are no duplicates, just Collections.disjoint(list1, list2) would give you what you need;
Be aware of algorithmic complexity: checking for duplicates in two lists is O(n2), but if you turn one of them into a HashSet, you'll get O(n).
Taking everything said above into account, the following would be an appropriate implementation:
static boolean disjoint(Collection<?> c1, Collection<?> c2) {
for(Object o : c1)
if (c2.contains(o))
return true;
return false;
}
If you look at Collections.disjoint, you'll find this exact same loop, preceded by a piece of code which optimizes the usage of sets for reasons described above.
Seems to me your method should be rewritten to:
public static boolean contains(List<String>list1, List<String>list2) {
return list2.containsAll(list1);
}
The code you currently have actually only checks if the last element of list1 is also in list2.
If you're actually looking for a contains any, this simple solution will do:
public static boolean contains(List<String>list1, List<String>list2) {
for (String str : list1) {
if (list2.contains(str)) {
return true;
}
}
return false;
}
if (yesitcontains = true){
should be
if (yesitcontains == true){
== is for comparison and = is for assignment.
if (yesitcontains = true){
will always evaluate to if(true) which causing return true;
EDIT:
(OR)
simply return yesitcontains; as commented.
if (yesitcontains == true) { } // use `==` here
or just
if (yesitcontains) { }
The below code assigns true to yesitcontains , and the expression will always be true.
if (yesitcontains = true) { }
There is no point of if() in your code , you can simple return yesitcontains;

How to avoid null pointer error

I trying to find whether the elements of 2 arrayLists are match or not.
But this code give me error Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException since some of the elements are null.
How can I solved this problem?
String level []={"High","High","High","High","High","High"};
ArrayList<Object> n = new ArrayList<Object>(Arrays.asList(level));
String choice []={null,"High","Low","High",null,"Medium"};
ArrayList<Object> m = new ArrayList<Object>(Arrays.asList(choice));
//Check if the two arrayList are identical
for(int i=0; i<m.size(); i++){
if(!(m.get(i).equals(n.get(i)))){
result= true;
break;
}
}
return result;
}
Just use Arrays.equals, like so:
String level []={"High","High","High","High","High","High"};
String choice []={null,"High","Low","High",null,"Medium"};
return Arrays.equals(level, choice);
The problem is that you are calling the equals method on some elements without first checking for null.
Change to:
for(int i=0; i<m.size(); i++){
if(m.get(i) != null && !(m.get(i).equals(n.get(i)))){
result = true;
break;
}
}
Or if you want to allow two null values to compare equal:
for(int i=0; i<m.size(); i++){
if (m.get(i) == null) {
if (n.get(i) != null) {
result = true;
}
} else if(!(m.get(i).equals(n.get(i)))){
result = true;
}
if (result) {
break;
}
}
One thing I don't get - why are you setting result to true when you find a mismatch? Don't you want to return true if both lists match and false otherwise?
The root of this problem could be you are using null as an actual value.
Just looking at your code you could use enum and instead of null use an EMPTY value. Then you can actually compare with in a list without nullpointerexceptions.
Check this out:
http://java.sun.com/docs/books/tutorial/java/javaOO/enum.html
Also try to avoid using arrays. Just use List but use the proper type. Don't use List<Object> that is almost never valid.
null should indicate an error or testing only. It should never be used in valid code as you will create null pointer exception bugs during runtime.
if you know the first list never contains nulls switch the call around
if(!(n.get(i).equals(m.get(i)))){
also specifying ArrayList<Object> is bad practice, use List<String> if it is actually String objects.
Check if the objects are the same object (or both null) first. Check for null before you do the equals() test.
boolean result = true;
String level[] = { "High", "High", "High", "High", "High", "High" };
ArrayList<String> n = new ArrayList<String>(Arrays.asList(level));
String choice[] = { null, "High", "Low", "High", null, "Medium" };
ArrayList<String> m = new ArrayList<String>(Arrays.asList(choice));
// Check if the two arrayList are identical
for (int i = 0; i < m.size(); i++) {
String mElement = m.get(i);
String nElement = n.get(i);
if (mElement == nElement) {
result = true;
} else if ((mElement == null) || (nElement == null)) {
result = false;
break;
} else if (!(m.get(i).equals(n.get(i)))) {
result = false;
break;
}
}
return result;
}
Rewrite your if like this in order to check for both double-nullity and single-nullity:
if((m.get(i) == null && n.get(i) == null) || (m.get(i) != null && !(m.get(i).equals(n.get(i)))))
Rather than solving this specific problem, give yourself a tool you can use over and again, e.g.:
public static final boolean areEqual(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
...in some handy utility class, then use that in your loop.
But of course, for this specific requirement, derivation has the right answer (use java.util.Arrays.equals(Object[],Object[])).
Remove NULLs
You can remove NULL values from your List objects before processing.
myList.removeAll( Collections.singleton( null ) );
The Collections class is a bunch of convenient utility methods. Not to be confused with Collection (singular), the interface that parents List and is implemented by ArrayList.
See this posting, Removing all nulls from a List in Java, for more discussion.

Categories