Java search list of objects for variable - java

I have a List of Book
List<Book> books = new ArrayList<Book>();
public class Book {
String name;
List<Author> authors;
}
Book contains a List of Author which contains an ID, Name and Age
public class Author {
int ID;
String Name;
int Age;
}
I can iterate through the books List and return the Book where Name = x but I'm not sure how I can search Author and return where Name = y.

Add a method hasAuthor(String) to your Book which loops through the List<Author> list and compares the name. Like so:
boolean hasAuthor(String name) {
for (Author author : authors) {
if (author.name.equals(name)) {
return true;
}
}
return false;
}
To find books with a specific author, you loop over the List<Book> and invoke the hasAuthor(String) method.

Maybe this is off topic, but when solving such problems, having objects with nested lists, though logically simple, is a really bad idea. Its extremely hard to maintain, scale and work with.
If you use this structure throughout and come across a problem like:
Give me all the books written by this author.
You are toast, you will have to iterate over each and every book. Or start maintaining caches and what not. It gets worse when the nesting deepens.
Lets say, now you need to maintain each Author's list of hobbies too. Think of how would you implement something like : Give me all the books whose authors like skydiving!
I know this doesn't answer your question. Just my 2 cents.

Something primitive:
List<Author> authors;
...
Iterator<Author> ai = authors.iterator();
Author a;
while(ai.hasNext()){
a = ai.next();
if(a.name.equalsIgnoreCase(yourname)){
return a;
}
continue;
}
Edit: Too late :/

Related

HashMap with multiple values get.key() error

import java.util.*;
class Student{
final String name;
final String gender;
public String number;
static Map<String, ArrayList<String>> hm = new HashMap<String, ArrayList<String>>();
static ArrayList<String> nameandNumber = new ArrayList<>();
Student(String number, String name, String gender) {
this.name = name;
this.gender = gender;
this.number = number;
nameandNumber.add(this.name);
nameandNumber.add(this.number);
hm.put(this.gender,nameandNumber);
}
void getPersonByGender() {
String[] Liste = hm.get("Man").toArray(new String[0]);
for (int i = 0; i < Liste.length - 1; i += 2) {
System.out.println(Liste[i] + "\t<------>\t" + Liste[i + 1]);
}
}
}
hello guys i am creating a class and this class will return me 10 student information which I will give (according to the difference between men and women). When i try to use getPersonByGender's function this function gives me all students.
static ArrayList<String> nameandNumber = new ArrayList<>();
new is useful: Count the amount of times your code ever invokes new ArrayList. It's a fairly easy answer: Once. For your entire program.
If you only call new once, that means there is only one list. In the whole system.
No wonder then: This:
nameandNumber.add(this.number);
is called 10 times (because 10 students) for a single 'run' of your app. Thus, that one list you have must therefore have all these numbers added together - that's why you see all the data.
Your code is, unfortunately, layers of bad design decisions.
You can 'fix' the problem (but it'll still be fragile code that is hard to read), or you can 'fix' the design (which is more work).
Fix the problem
Instead of 1 list shared by all students which obviously can't work, you want to call new ArrayList for each student. Get rid of that static single arraylist, and instead make one every time:
Student(String number, String name, String gender) {
this.name = name;
this.gender = gender;
this.number = number;
var nameandNumber = new ArrayList<String>();
nameandNumber.add(this.name);
nameandNumber.add(this.number);
hm.put(this.gender, nameandNumber);
}
Now you call new ArrayList the right number of times throughout one run of your program, for example.
But you're still in trouble here - because you decided to use a List to represent a single idea (a student), you have confused yourself: A given gender maps to multiple students. Given that a single student is represented by a List<String>, multiple students would be a List<List<String>> and, oof, this is getting real complex, real fast.
We could plug away at fixing this further but let's take a step back and fix your design instead!
Fix the design
More generally, java's typing system is highly nominal: Types have names, and the more descriptive the name, the better.
Student is a far better, clearer name than List<String>. How is a reader of your code supposed to know that those List<String> objects specifically are intended to contain precisely 2 strings, the first of which is the student's name, the second of which is their student ID number? It doesn't say that anywhere. If you mess it up you get no compiler errors of any kind.
You have a type, right there, that properly describes that concept: Student!
So why not replace this bad code:
static Map<String, ArrayList<List<String>>> hm = new HashMap<String, ArrayList<List<String>>>();
With this much improved code:
static Map<String, List<Student>> genderMap = new HashMap<>();
It has all sorts of improvements:
It has a proper name. hm doesn't mean anything.
It uses <> to be shorter - you don't need to repeat that stuff.
It codes to the principle (List) instead of the concrete class.
It uses nominal types - this maps a gender string to a list of students. And the code reads the same way, that's good.
Putting it together:
class Student {
final String name;
final String gender;
final String number;
static Map<String, List<Student>> genderMap = new HashMap<>();
Student(String number, String name, String gender) {
this.name = name;
this.gender = gender;
this.number = number;
List<Student> studentsForThisGender = genderMap.get(gender);
if (studentsForThisGender == null) {
// There is no list yet; we need to make one.
genderMap.put(gender, studentsForThisGender = new ArrayList<>());
}
studentsForThisGender.add(this);
}
static void getPersonByGender() {
Student[] liste = genderMap.get("Man").toArray(new Student[0]);
for (Student student : liste) {
System.out.println(student.name + "\t<------>\t" + student.number);
}
}
}
Note:
getPersonByGender is now static - that's a thing you do to the concept of 'students' not any particula student.
Instead of this nebulous 'print list[0] - you just sorta have to know that's the name', we print student.name which documents itself.
We fixed the problem where you were list-confused.
If you get a little further along your java studies, that 'get the list of students for a given gender, and make a new list if neccessary' can be put more succintly. The last 4 lines can be reduced to:
genderMap.computeIfAbsent(gender, () -> new ArrayList<>()).add(this);
But I bet that syntax, and what is happening there, hasn't been covered yet in your java course.

How to specify a class property from a string in java

So I am reading from a file with scanner it has the similar format:
title, name, age
Mr, Matthew, 20
mr, Paul, 30
miss, Anne, 24
CSV^
class person{
String name, title;
int age;
public crimeData(String csv){
String[]list = csv.split(",", -1);
name = list[0];
title = list[1];
age = list[2];
}
}
Console Program
Scanner input = new Scanner(System.in);
System.out.println("Please select what data you want to load:");
String selection = input.next();
int temp = 0;
for(int i=0; i< header.length; i++){
if(header[i].equals(selection)){
temp = i;
break;
}
}
temp will give us the index of the option specified so if it is 2 we will want to access the age property
When my console application runs I prompt them(the user) for the data that they want.
So they may enter "age" So I am lost on how I may take this "age" String and access the person object with it.
The ideal case for the program output should be: 20,30,24 going through each age and printing
I take their input so String input = scanner.nextLine();
Then I loop through my array of person objects to get the index of the input. Once I have this index I then want to access the property of person at the index. So like if my index was 1 I would want to access the property 'name'.
In javascript I could take the string and say person['age'] although java's a whole different story. I have looked into java's "reflection API" although it's a heavy learning curve.
I have looked into java's "reflection API" although it's a heavy learning curve.
Well, Reflection is the way to go. It's widely used in many frameworks.
But perhaps a simpler solution will fit your needs. Use a switch to decide which attribute to return, and encapsulate this in a method of the Person class:
class Person {
private String name, title;
private int age;
public loadData(String csv){
String[] list = csv.split(",");
name = list[0];
title = list[1];
age = Integer.parseInt(list[2]);
}
public Object attribute(String attribute) {
switch (attribute) {
case "name": return this.name;
case "title": return this.title;
case "age": return this.age;
default: throw new RuntimeException("Invalid attribute: " + attribute);
}
}
}
Encapsulating the switch inside the method is in line with OOP principles, since it hides how attributes are stored from other objects, only exposing an interface to query them. Reflection breaks all encapsulation.
Though in general I am not in favor of using Map for holding fields for an object, if the number of properties is large and could even potentially vary across CSV files (e.g., some file has the University a person attended, another does not), then using a Map to hold the properties might be appropriate.
In this case, one would define a simple Person class:
public class Person {
Map<String, String> props = new HashMap<>();
public void addProperty(String propertyName, String value) {
// could add error checking to ensure propertyName not null/emtpy
props.put(propertyName, value);
}
/**
* returns the value of the property; may return null
*/
public String getProperty(String propertyName) {
return props.get(propertyName);
}
}
If it is know that certain attributes/properties will always be loaded, then accessors such as getName() could be added:
public String getName() {
return props.get("name");
}
public int getAge() {
String age = props.get("age");
// or throw exception if missing
return (age != null ? Integer.parseInt(age) : -1);
}
Though note I would expect name to not be a single entry for most datasets, as there typically would be last name, first name, etc. Nonetheless, the pattern for a limited number of commonly expected values is the same. Also, you can adapt so that you could get integer values directly for certain well-known fields.
Then, when you parse the file, you keep the title row that has the attribute definitions. Then for each row that you subsequently read, you create a new Person object, and then add the properties in order.
List<Person> allPersons = new ArrayList<>();
while ( (line = READ_NEXT_LINE) ) {
// NOTE: this is not a safe way to handle CSV files; should really
// use a CSV reader as fields could have embedded commas
attrs[] = line.split(",");
Person p = new Person();
for (int i = 0; i < titleRow.length; ++i) {
p.addProperty(titleRow[i], attrs[i]);
}
allPersons.add(p);
}
You can then get a specific Person by Person myPerson = allPersons.get(index_of_person), and much akin to the way you would have used Javascript, you can do String val = myPerson.getProperty("age").
If you need to search by a given attribute, you can then stream/loop over the allPersons and check of equivalence based upon a given property.
// find all people of a given age
List<Person> peopleAge20 = allPersons.stream()
.filter(p -> p.getAge() == 20)
.collect(Collectors.toList());
System.out.println(peopleAge20);
// summary statics (average age) for all people
IntSummaryStatistics stats =
allPersons.stream().mapToInt(p -> p.getAge()).summaryStatistics();
System.out.printf("Average age: %f\n", stats.getAverage());
Note that this approach does break the idea of a Javabean, but that may or may not be an issue depending upon your requirements.
First thing, we should add a constructor to your Person class.
class Person {
public Person(String name, String title, int age) {
this.name = name;
this.title = title;
this.age = age;
}
}
Now while you read the input you can use a Map as follows. Here after reading each line, we create a Person object and then using that person's age we make an entry in the map with key as age and value as Person.
Map<Integer, Person> mapOfPeople = new HashMap<>();
while (input.hasNextLine()) {
String line[] = input.nextLine().split(",");
Perso person = new Perso(line[1], line[0], Integer.parseInt(line[2].trim()));
mapOfPeople.put(person.getAge(), person);
}
Now to fetch a particular Person by age just do
mapOfPeople.get(20);

Infinite recursion in toString(), how can I prevent this?

I've searched through StackOverflow trying to figure this out "on my own", with no luck. I think I'm sitting on a problem of infinite recursion in my toString()-method, but I'm not certain as I'm quite new to this.
I think providing you with my code, first of all, will make the problem appear clearer to you:
import java.util.ArrayList;
public class User{
private String name;
private ArrayList<User> friends;
private static ArrayList<User> users = new ArrayList<User>();
public User(String name){
this.name = name;
this.friends = new ArrayList<User>();
users.add(this);
}
public void addFriend(User friend){
friends.add(friend);
}
public static void connect(User user1, User user2){
user1.addFriend(user2);
user2.addFriend(user1);
}
public static ArrayList<User> getUsers(){
return users;
}
public String toString(){
return String.format("%s : %s", name, friends);
}
}
I'm not sure if you can see what I'm trying to do, but the format of my toString is supposed to be like this:
name : {friend1, friend2, friend3, ...}
For example, a user “Alice” with friends “Bob” and “Charlie” would print like this
Alice : {Bob, Charlie, }
I'm not quite sure how I'm supposed to proceed to achieve this. Any help would be much appreciated. I also apologize if this has been answered before, but I didn't understand any of the answers I found earlier.
This is my main-method, might also be helpful:
public class Main{
public static void main(String args[]){
User bob = new User("Bob");
User alice = new User("Alice");
User charlie = new User("Charlie");
User.connect(alice, bob);
User.connect(alice, charlie);
System.out.println(User.getUsers());
}
}
You need to iterate over the "friends" List and just print the name of each. Because you're printing each friend, that includes the friends' friends which is why you're getting infinite recursion.
Try something like
public String toString() {
int count = 0;
StringBuilder sb = new StringBuilder("{\"name\"=" + name + ", \"friends\"=[");
for (User friend : friends) {
sb.append(friend.getName());
if (++count < friends.size()) sb.append(", ");
}
sb.append("]}");
return sb.toString();
}
I suspect that the issue is that when you call Alice.toString(), and you start iterating over the list of Alice's friends, it calls each friend's toString().
If Alice is friends with Bob, and Bob is ALSO friends with Alice, then you end up bouncing back and forth between Alice.toString() and Bob.toString() indefinitely.
From my pont of view, the problem could be that once you call to the toString, trying to display all the users, what happends is that in the toString method you call to the toString of the friends os the user, successively.
In order to avoid recursivity, try to iterate over the list of friends of the user and concat an empty string with the information of the friends of the user, not declaring again the friends of the friends. To sum up, avoid recursivity :)

Search for certain String inside array of objects

I couldn't find an answer so I have to post a new question - although it seems to be similiar to few another questions that were asked already here - but to the point:
I have got some list, for example:
private static List<Book> books = new ArrayList<>();
It contains objects of class Book, where I have got
private String title;
private String author;
and getters&setters.
Now I have got a new object Book (got from webform through parameters/servlet) and I want to check if title of new book is already on list books.
If it is already on that list, print some console output. If it is not, I want to add the whole new object to list. In order to do that, I tried to compare titles using regular foreach loops. Sometimes it worked out, sometimes not (sometimes my function cwere comparing two titles in a right way but sometimes not - I don't know why). I know there is a better way of doing this using Java 8. But I'm newbie when it comes to Java 8. Could somebody show me a good way of doing this?
I can't tell why you want to do it specifically with java-8 features, but it could be done like this:
boolean isPresent = books.stream()
.anyMatch(book -> book.getTitle().equals(inputBook.getTitle()));
if(isPresent){
... console print
} else {
add it to books
}
I think that this could be made a bit more generic btw, to have a method that would accept a Predicate.
private boolean exists(List<T> list, Predicate<T> predicate) {
return list.stream().anyMatch(predicate);
}
So that you could match anything you like later:
boolean exists = exists(books, p -> p.getTitle().equals(b.getTitle()));
Or :
Predicate<Book> first = p -> p.getTitle().equals(b.getTitle());
Predicate<Book> second = first.and(p -> p.getTitle().equals(b.getTitle()));
boolean exists = exists(books, second);
You can try with findAny and Optional
Optional<Book> found = books.stream().filter(p -> p.getTitle().equalsIgnoreCase(newBook.getTitle())).findAny();
if (found.isPresent()){
System.out.println(found.get());
}
else {
books.add(newBook);
}
If you wanted to be a little fancy and methodical you could create a 'Library' class that inherits or has-a ArrayList. What you need then is an equals function for book that uses String .equals() on the title. The contains method could then call this looping over the elements.
First you must create the getter's & setter's for the class Book. A demonstration of that would be like this:
class Book{
private String title;
private String author;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
Now you can do the following to surf through every objects' title and compare it with the newly entered objects' title.
Scanner in = new Scanner(System.in);
List<Book> books = new ArrayList<>();
Book b = new Book();
System.out.println("Enter the title:");
String t = in.nextLine();
b.setTitle(t); //optional
for (Book book:books
) {
if(book.getTitle().equals(t))
System.out.println("Title exists");
else
System.out.println("Title does not exist");
}

Using a method on an object of an arraylist in Java

public class Book
{
private String isbn, author, area;
private int length;
public Book(String isbn, String author, String area, int length)
{
this.isbn=isbn;
this.author=author;
this.area=area;
this.length=length;
}
public boolean isLong()
{
if (length>500)
return true;
else
return false;
}
}
public class BookCollection
{
private ArrayList<Book> bookList = new ArrayList<Book>();
public BookCollection()throws IOException
{
Scanner fileScan;
String oneLine;
String isbn, author, area;
int length;
fileScan = new Scanner (new File("books.txt"));
while (fileScan.hasNext())
{
isbn=fileScan.next();
author=fileScan.next();
area=fileScan.next();
length=fileScan.nextInt();
bookList.add(new Book(isbn, author, area, length));
}
}
public class TestBookCollection
{
public static void main (String[] args)throws IOException
{
BookCollection books = new BookCollection();
System.out.println(books);
}
}
All right here is the relevant code. My project is to read a text file that has the information about these books and put them into an arraylist of book objects. My question is: how would I go about envoking the isLong() method found in class Book on an object in an arraylist? The point of the method is, if an object has >500 pages that it returns true. If not it will return false. I'm just kind of confused about the logic and I have never really worked with Arraylists before.
You can add another method to BookCollection:
public void printLongBooks() {
for (Book book : bookList) {
if (book.isLong()) {
well, book is long ...
} else {
obviously, it is short ...
}
The above uses the so called "for each" looping style in Java that you can use to loop every array/collection; instead of the "old school" for (int i=0; i<...) counting loop.
and within your main method, you simply invoke the new method:
books.printLongBooks()
And some generic hints:
A) isLong() can be reduced to a one-liner: return length > 500;
B) reading stuff from a file is not what you directly do in the constructor. Instead, you should create a dedicated method to that, which you then might call within the constructor
Rather than putting the logic to create book list inside a constructor, you should create a method named as getBookCollection() that returns an arraylist.
public List<Book> BookCollection()throws IOException
{
List<Book> bookList = new ArrayList<Book>();
//logic to create booklist
return booklist;
}
Once you have list of books, you can run an enhanced for loop and check for pages in each book.
for(Book book: bookList){
if(book.isLong()){
//logic
} else {
//logic
}
}
You can use a for loop to iterate through the elements of the ArrayList and call the method on each one of its objects.
for(Book book : bookList){
boolean isLong = book.isLong();
}
You have a List<Book>, where each index in the List contains a Book. Use the List.get(index) method to get the Object in the given List at index. For example: bookList.get(0) gets the Book at index 0. Once you have that Object, you can use it normally.
I assume that you have some way to get the bookList inside BookCollection, and the name of a Book?
public static void main(String[] args){
BookCollection books = new BookCollection(); // Make a new BookCollection
List<Book> booksInCollection = books.getBooksList(); // Get the Books inside BookCollection
// Go through each book in booksInCollection
for(int index = 0; index < booksInCollection.size(); index++){
// Get the Book Object at index
Book currentBook = booksInCollection.get(index);
if(currentBook.isLong()){
System.out.println(currentBook.getName() + " is long!");
}
}
}
Firstly (if you don't want to use streams etc.) you need to get object from ArrayList. You can do it by using ArrayList.get(int index) and then invoking the method or by using for each loop:
for(Book book : bookList) {
System.out.println(book.isLong());
}
The problem is that you cannot access private field (bookList) from main. There are several ways to make it work.
Create a public method inside BookCollection
Change field to public and then access it directly books.bookList
Make a getter to your bookList in BookCollection
Make BookCollection extends ArrayList
What you've done here is created a decorator (BookCollection) which wraps an ArrayList with additional functionality (in this case, a constructor which pre-fills it based on a file). This gives you two options for how to make use of a Book in the List:
Make the List accessible via a getter, and then work it out. Makes statements long but this is acceptable for small amounts of usage:
if(books.getBooks().get(index).isLong()) {
//The book at index is long
}
More commonly, you'll make methods in the decorator which offer common functionality, like so:
public boolean isLong(int index) {
return bookList.get(index).isLong();
}
Then you just call the decorator method from your business logic. You can even make more complex methods such as the one offered by GhostCat.

Categories