I have an ArrayList that contains a number of songs objects.
ArrayList<Song> aSong = new ArrayList<Song>();
The Song class looks like this:
public class Song {
private String sName;
private String sLength;
public Song(String name, String length){
sName = name;
sLength= length;
}
public String getName(){
return sName;
}
public String getLength(){
return sLength;
}
public void edit(String name, String length){
sName = name;
sLength= length;
}
}
I'm trying to see if a song exists with the ArrayList method "contains"
// example
aSong.contains("Song 1")
but this returns false even though the song does really exist in the ArrayList.
Any help would be highly appreciated and thank you in advance.
No, "Song 1" definitely doesn't exist in the ArrayList - because that's a string, not a Song.
Three options:
Check for each song in the list to see whether it has that name. This is reasonably easy to do, but if you need that functionality in multiple places you'd definitely want to extract it into a separate method. Something like:
public static Song findSongByName(Iterable<? extends Song> songList,
String name)
{
// TODO: Nullity checks
for (Song song : songList)
{
if (song.getName().equals(name))
{
return song;
}
}
return null;
}
Override equals in Song based on the name of the song, and then call aSong.contains(new Song("Song 1"). This probably isn't a good idea, as I suspect that if you've got two Song objects with the same name but different lengths, you don't want to consider them as equal.
Build a Map<String, Song> so that you can look up songs in the list based on their name. This would be a good idea if you needed to look up multiple songs from the same list.
"Song 1" is a string, it's not the same as any Song object. Sounds to me that you are still at the stage where you think the language can guess what you want, rather than realising you have to tell it exactly and precisely what you want.
You could write a for loop and go though your list one by one, looking for the right title. Or perhaps you should switch to using a HashMap<String, Song>, that would let you look up a song by it's title.
You can define a function find that searches for and returns the first object that satisfies the given predicate.
public static <A> A find(final Iterable<A> iterable, final Predicate<A> predicate) {
for(final A item : iterable) {
if(predicate.isSatisfiedBy(item)) {
return item;
}
}
return null;
}
where Predicate is:
interface Predicate<A> {
boolean isSatisfiedBy(final A a);
}
You could use this function for your purpose as shown below:
final Song song = find(songs,
new Predicate<Song>() {
boolean isSatisfiedBy(final Song s) {
return s.getName().equals("Song 1");
}
}
);
Related
I have a list of objects "SaleItem". they are all objects of the same class. each object has a String field "name" and an int field "value". I want to see if one of the objects contains a name. It seems that I can't use the "contains" method to do this. I see two solutions. one is to iterate through all the objects to check if one has said name:
for (SaleItem item: myList) {
if (item.getName() == "banana") {
// do stuff
}
}
The other solution would be to create a new list of Strings from "myList" and use the contains method on that:
ArrayList<String> nameList = new ArrayList<>();
for (SaleItem item: myList) {
nameList.add(item.getName());
}
if (nameList.contains("banana")) {
// do stuff
}
I imagine the first method would be most efficient if I'm only doing it once, and the second would be more efficient if I'm doing it many times. Being a bit of a newbie without a formal education, I don't know what's proper in this situation.
Since SaleItem.getName() returns a string, you should be able to use "contains" method.
It seems like you have initialized the ArrayList or the SaleItem object incorrectly.
public class TestApp {
public static void main(String[] args) {
List<SaleItem> list = new ArrayList<SaleItem>();
SaleItem s1 = new SaleItem();
s1.setName("banana");
s1.setValue(1);
SaleItem s2 = new SaleItem();
s2.setName("apple");
s2.setValue(2);
list.add(s1);
list.add(s2);
for (SaleItem item: list) {
if (item.getName().contains("banana")) {
System.out.println("Pass");
}
}
}
}
class SaleItem {
private String name;
private int value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
Try with this code
public class SaleItem {
private String itemName;
public String getItemName() {
return itemName;
}
public SaleItem setItemName(String itemName) {
this.itemName = itemName;
return this;
}
#Override
public String toString() {
return "[SaleItem : { itemName = " + this.getItemName() + " }]";
}
public static void main(String[] args) {
ArrayList<SaleItem> nameList = new ArrayList<>();
nameList.add(new SaleItem().setItemName("banana"));
nameList.add(new SaleItem().setItemName("grape"));
nameList.add(new SaleItem().setItemName("watermelon"));
nameList.add(new SaleItem().setItemName("orange"));
nameList.add(new SaleItem().setItemName("guava"));
for (SaleItem item : nameList) {
if (item.toString().contains("banana")) {
// Do this
}
}
}
}
A List's .contains method isn't magical, it will generally just loop through the elements checking for equality, O(n) linear performance.
Your first solution is probably fine.
If you really did expect repeated access and wanted better than linear performance on subsequent lookups, you'd probably want to construct a Map<String,SaleItem>, or a Set<String> depending on what you wanted to do with it. But those solutions would normally only work on exact matches. Once you need case-insensitive matches, they have to be TreeMap or TreeSet with a case-insensitive comparator. And if you want partial matching (like using String.contains() or a regular expression), you'd want to go back to a linear search.
But don't do any of that unless you have to. Keep it simple.
I made a post earlier about a similar topic. However, I thought I would clarify something and change my question.
So this project I am doing is confusing me. I am only 5 weeks in and the question is asking me to create a method that returns a title of a photo in an array of photos. each photo has a title. This is the code:
public class Album {
private String albumtitle;
private ArrayList<Photo> photos;
/**
* This constructor should initialize the
* instance variables of the class.
*/
public Album(String title) {
this.albumtitle = title;
photos = new ArrayList<>();
}
/** When passed a title, this method should
* return the first Photo object in the album with
* a matching title. If there is no such object, it
* should return null.
*
* #param title A title to search for
* #return A Photo object, or null
*/
public Photo searchByTitle(String title) {
//TODO enter code here
}
}
Now, my lecturer said not to use for loops as the project is from chapter 1 to 4 (chapter 5 is for loops/iterations)
https://lms.uwa.edu.au/bbcswebdav/pid-1134902-dt-content-rid-16529804_1/courses/CITS1001_SEM-2_2018/lectures/BooksReadJournal.java.pdf
This is an example of what the lecturer did with a program about books without using for loops. However, notice it has (int index) as a parameter and then uses String title = bookTitles.get(index)
My point is, how do I do it without using for loop? I don't want them to feel as I have copied off the internet something we haven't learned.
Thanks,
If you are limited to avoid use the for-loop and use the if-else only, the recursive call is an alternative:
public Photo searchByTitle(String title) {
return searchByIndex(title, 0);
}
private Photo searchByIndex(String title, int index) {
if (index < photos.size()) { // Has next? If yes ...
Photo next = photos.get(index); // Get next
if (!title.equals(next.getPhotoName())) { // Does the title match? If not...
return searchByIndex(title, ++index); // Check the next one
} else return next; // Otherwise you found it
} return null; // ... if no next, return null
}
I assume the Photo class has a field String photoName accessible with a getter which is about to be compared with the String title.
Implement Comparable on Photo that returns true if the title is the same.
Construct a temporary Photo object with the given type.
Leverage the indexOf method on ArrayList to find index of Album with the Photo title.
Use get(int) to get the Album.
I've implemented the code from the comments above.
The idea here is to build a temporary object in the searchByTitle method and passing it to the List.indexOf method having the Photo class that overrides Object.equals.
public class Album {
class Photo {
private String title;
public Photo(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
#Override
public boolean equals(Object anObject) {
return title.equals(((Photo)anObject).getTitle());
}
}
private String albumtitle;
private ArrayList<Photo> photos;
/**
* This constructor should initialize the
* instance variables of the class.
*/
public Album(String title) {
this.albumtitle = title;
photos = new ArrayList<>();
}
/** When passed a title, this method should
* return the first Photo object in the album with
* a matching title. If there is no such object, it
* should return null.
*
* #param title A title to search for
* #return A Photo object, or null
*/
public Photo searchByTitle(String title) {
Photo tmp = new Photo(title);
int index = photos.indexOf(tmp);
if (index >= 0)
return photos.get(index);
return null;
}
}
This is a very basical implementation of equals which doesn't take into account null argument and its type.
You can bring another ArrayList of String which keeps names of photo titles. Then in search function search with photo title in String arrayList. If find an index then return the Photo object of that index from photoTitles, as you are inserting in both arrayList in same order.
public class Album {
private String albumtitle;
private ArrayList<Photo> photos;
private ArrayList<String> photoTitles;
public Album(String title) {
this.albumtitle = title;
photos = new ArrayList<>();
photoTitles = new ArrayList<>();
}
public Photo searchByTitle(String title) {
int index = photoTitles.indexOf(title);
if(index >= 0) {
return photos.get(index);
}
return null;
}
}
Just because I like the recursion idea but using it with an index search on a List is not optimal, let's use an iterator instead. The recursive method will simply check if there is one value to take, check and call again until we find the value or reach the end.
//Just hide the used of an iterator
public static Photo getFirstWithTitle(List<Photo> list, String value){
return getFirstWithTitle(list.iterator(), value);
}
//recursive method
private static Photo getFirstWithTitle(Iterator<Photo> it, String value){
if(it.hasNext()){
Photo p = it.next();
return p.getTitle().equals(value)?
p
: getFirstWithTitle(it, value);
} else
return null;
}
You can use built in binarySearch and a comparator. Probably the most elegant way and how I usually do it
public Photo searchByTitle(String title) {
Photo item = null
Comparator<Photo> comparator = new Comparator<Photo>() {
public int compare(Photo it1, Photo it2) {
return it1.getTitle().compareTo(it2.getTitle());
}
};
Collections.sort(photos, comparator); //This should go after you set the items, ie. you sort it once
int index = Collections.binarySearch(photos, comparator);
if (index >= 0) {
item = photos.get(index);
}
return item
}
Using Java you don't even need a 'if' statement. This can be achieved through this:
public Photo searchByTitle(String title) {
return photos.stream().filter(photo -> title.
equals(photo.getTitle())).findAny().get();
}
p.s: I did not have access to your enunciate question (the link provided)
I am trying to retrieve certain values from multiple objects under the same class. I have used a for each loop to iterate through each object, and would like to create an aggregated total, representing the rating and the cost of the item from the objects.
The For Each loop in my parent class:
for (Song songObj : Song.returnSongs()) {
totalSongCost += Double.parseDouble(songObj.getPrice());
totalSongRating += Integer.parseInt(songObj.getRating());
}
The Child class ArrayList meant to store objects:
private int rating;
private String title;
private double price;
private boolean favorite;
private static int counter = 0;
private static ArrayList songArray = new ArrayList();
/**
* Constructor for objects of class Song
*/
public Song()
{
// initialise instance variables
rating = 0;
title = "";
price = 0.0;
counter++;
songArray.add(this);
}
public static ArrayList returnSongs() {
return songArray;
}
When I compile the code I get an error message saying that an object cannot be converted to song. Is there a way to fix this, or an easier way to accomplish the same task?
If you've ever read the docs, you will know that ArrayList is actually a generic class. That means you can give ArrayList a type.
The type of stuff that an array list can store depends on what type you gave it. But if you don't give it any type, it stores Objects! Here,
for (Song songObj : Song.returnSongs()) {
you want to get Song objects from an array list of Object objects, which makes no sense to the compiler. As a result, the error appears.
The solution to this problem is of course, give the array list a type so that it knows what type it should store.
Change this
private static ArrayList songArray = new ArrayList();
to this:
private static ArrayList<Song> songArray = new ArrayList<>();
and change this:
public static ArrayList returnSongs() {
to this:
public static ArrayList<Song> returnSongs() {
ArrayList is a generic class. This means you can specify what class type it is meant to work with. if you change this:
private static ArrayList songArray = new ArrayList();
to this:
private static ArrayList<Song> songArray = new ArrayList<Song>();
Then the ArrayList class will understand that you're working with instances of Song.
Edit: as Jim Garrison pointed out, your returnSongs() method should also be changed to specify the class type in the same way.
public static ArrayList<Song> returnSongs() { ...
It's a little unusual to have the Song class be responsible for keeping track of all of the songs within the application. That seems outside of the responsibility of that class, and perhaps better suited to be handled within a different class, either within your parent class or a new type specially defined.
Additionally, be careful when using types like List and ArrayList. As your compiler will warn you, these require type parameters in angle brackets (i.e. List<Type>). You should make it a habit of addressing all compiler warnings, and of always specifying type parameters for generic types like List. In cases where you don't define your types correctly, things start to default to Object, which leads to the issue you faced here.
Below is an example of what this could look like, restructured to keep the Song class solely for attributes of the song itself:
import java.util.ArrayList;
import java.util.List;
public class Parent {
private static List<Song> songs = new ArrayList<Song>();
private static double totalSongCost = 0.0;
private static int totalSongRating = 0;
public static void main(String[] args) {
populateSongs();
for (Song song : songs) {
totalSongCost += songObj.getPrice();
totalSongRating += songObj.getRating();
}
}
private void populateSongs() {
songs.add(new Song(5, "Hey Jude", 12.5));
songs.add(new Song(4, "Angie", 11.5));
songs.add(new Song(0, "Other", 10.5));
}
}
Your song class would simply be this:
public class Song {
private int rating = 0;
private String title = "";
private double price = 0.0;
public Song(int rating, String title, double price) {
this.rating = rating;
this.title = title;
this.price = price;
}
// Compressed for brevity
public int getRating() { return rating; }
public String getTitle() { return title; }
public double getPrice() { return price; }
}
I have a Song class(field: String name) and an Album class (initialize an Arraylist fill with Song objects), I am adding a method (addSongAlbum(String name parameter))that first check if the song is already in the album. Everything works fine but now I want to check too if the song exist outside the album. How can I do this taking in consideration that the input of the method is a String?
public void addSongToAlbum(String name){
if(checkExist(name) == null){
album.add(checkExist(name));
System.out.println("Song "+name+" was successfully added");
} else {
System.out.println("This song is already in the album");
}
}
private Song checkExist(String name){
for(int i=0; i<album.size(); i++){
if(name.equals(album.get(i).getName())){
return album.get(i);
}
}
return null;
}
I'd create a central Songmanager object that is available to all objects working with Songs, like so
public class SongManager {
private static SongManager sMan;
private Map<String, Song> songs = new HashMap<String, Song>();
public static SongManager getInstance() {
if (sMan == null) {
sMan = new SongManager();
}
return sMan;
}
public void addSong(Song s) {
songs.put(s.getName, s);
}
public Song getSong(String name) {
return songs.get(name);
}
}
your songs can be simple container classes, like so
public class Song {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
You can access this manager everywhere by calling SongManager.getInstance(). Create a simple container class for songs all existing Songs to this Manager and in youraddSongMethod, simply call thegetSong` method to see if the song exists.
P.s. I know that the Singleton pattern I'm using here is very controversal (see e.g. What is so bad about singletons?, but to me it is the easiest working alternative here.
You would need to have a List of all the Song objects somewhere. Either add them to a static List when constructing (you would need to remember to remove them when no longer used to let the memory be garbage collected), or have a similar List of all Album objects and then search through those. The first one is useful if you want to check all Song objects that were created, the second one if you only want to check those that are in albums.
public class Example {
private static class Courses {
public final String name;
public final Courses[] children;
public Courses(String name, Courses ... children) {
this.name = name;
this.children = children;
}
}
public static void main(String[] args) {
Courses courses =
new Courses("School",
new Courses("Mathematics",
new Courses("Algebra"),
new Courses("Trig"),
new Courses("Calculus"),
new Courses("Calculus 2"),
new Courses("Geometry")),
new Courses("Sciences",
new Courses("Biology"),
new Courses("Chemistry"),
new Courses("Physics"),
new Courses("Business",
new Courses("Finances",
new Courses("Accounting"),
new Courses("Accounting 1"),
new Courses("Accounting 2"),
new Courses("Administration",
new Courses("Economics"),
new Courses("Business Studies"),
new Courses("Administration 1"),
new Courses("Accounting"))),
new Courses("Physical Education"))));
System.out.println(find(courses, "Economics", courses.name));
public static String find(Courses courses, String name, String currentPath) {
if((courses.name).equals(name)){
System.out.println(currentPath);
return currentPath + " / " + name;
}
else{
//System.out.println(currentPath);
for(Courses child:courses.children){
currentPath += " / " + child.name;
find(child, name, currentPath);
}
}
return currentPath + " / " + name;
}
}
So this is the code that I have acquired. I'm trying to determine what the right thinking pattern should be when coding this find courses method. This is an array but I'm thinking of it in like a tree like manner and trying to find the answer. IS that something you guys would do too? I'm trying to find a path like this School / Business / Administration / Economics. But either I'm getting the whole path or it's iterating through the whole thing. Also, what's the approach you guys will take to accomplish this. I wrote a recursive method to achieve this, but its not working out.
Thanks, for your help
CC
Thinking about it as a tree is obviously the way to go, as it is indeed a tree.
It might help you to think what you expect the method to do if the current value of courses was the parent of the node you want. That is in your case, if it's the Administration node. In your current implementation, you will iterate over all the children, never realizing you found the correct child!
As another hint, you would generally in recursion want to do something with the result of the recursive call. In your code, you call find(child, name, currentPath) and then you do nothing with the result!
Hope these hints help you.
Your question contains some opinion-based (sub)questions. These we cannot answer, but we can help with your recursive algorithm.
The if branch of your find method seems alright. It enters that branch when an exact match occurs. The problem is in your else branch, where you need recursion to keep looking in the children courses.
Note that your method declaration returns a String.
public static String find(Courses, String, String)
And note that, when you recursively call this function, you are ignoring its return value, rendering the recursive call useless.
for (Courses child: courses.children) {
currentPath += " / " + child.name;
find(child, name, currentPath); // <-- this return value is being ignored!
}
Start by assigning the return value of the recursive search to some variable, and define a return value for when the search doesn't find the provided name (you will not find Biology under Administration, for instance). I'll assume that an empty String means the search didn't find the course.
String result = find(child, name, currentPath);
if (!result.isEmpty()) {
// it has been found
}
Finally, note that you are changing the value of the currentPath variable on each iteration, by appending to it. That will result in erroneous paths, if the name is found after the first iteration. Assign that temporary path, for that iteration, to another variable.
I would do something like this
Course.java
public class Course {
private Course parentCourse;
private String name;
public Course(String name){
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Course getParentCourse() {
return parentCourse;
}
public void setParentCourse(Course parentCourse) {
this.parentCourse = parentCourse;
}
}
Courses.java
public class Courses {
private List<Course> courses;
public Courses(){
}
public List<Course> getCourses() {
return courses;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}
Main
Courses courses = new Courses();
List listCourses = new ArrayList<Course>();
Course generalMaths = new Course("General Maths");
Course linAlgebra = new Course("Linear Algebra");
linAlgebra.setParentCourse(generalMaths);
listCourses.add(generalMaths);
listCourses.add(linAlgebra);
courses.setCourses(listCourses);
Find path
for(Course course : courses.getCourses()){
StringBuffer coursePath = new StringBuffer();
coursePath.append(course.getName());
while(course.getParentCourse() != null){
course = course.getParentCourse();
coursePath.append(" | "+course.getName());
}
System.out.println(coursePath);
}