I have a method that sorts a List by different criteria and returns the name (an instance variable) of the one with maximum value. In case more than one instance is having the maximum, all of their names should be concatenated.
Let's say I have Class A as follows.
Class A {
...
String getName(){...}
int getValue1() {...}
int getValue2() {...}
...
int getValueN() {...}
...
}
I have a List<A> listToSort. I would normally sort this list as listToSort.sort(Comparator.comparing(A::getValue1)) or listToSort.sort(Comparator.comparing(A::getValue2)), so on and so forth. Then get the ones sharing the maximum value.
In a method I believe this should be done as:
String getMaxString (Comparator c) {
listToSort.sort(c);
...
}
and send Comparator.comparing(A.getValueX) as parameter to call it with different methods. (X here indicates an arbitrary number for the getValue function).
However, I need to also return other instances sharing the same values
I will need to pass the Class methods to my method and call on instances as:
String getMaxString (Comparator c) {
listToSort.sort(c);
int maxValue = listToSort.get(listToSort.size() - 1).getValueX();
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (listToSort.get(i).getValueX()() == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
How would I pass this method to call on instances here? Or do I need to consider another way?
Edit:
I have a list of Courses as List<Course> mylist where a course can be simplified as:
Class Course {
private String name;
private int capacity;
private int students;
...
//bunch of getters.
}
My task is to return Strings for the course(es) with maximum capacity, the course(es) with maximum registered students, the course(es) with most difficulty, the maximum filled percentage, the course(es) with the maximum number of TAs etc...
Edit 2:
As requested in the comment section.
List of
Course a (name "a", capacity 10, students 5)
Course b (name "b", capacity 20, students 5)
Course c (name "c", capacity 30, students 0)
Sorting based on capacity should return "c"
Sorting based on students should return "a b"
You can pass the getter method and create the Comparator in getMaxString:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
public class Foo {
static class AClass {
private final String name;
private final int value1;
private final int value2;
String getName() { return name; }
int getValue1() { return value1; }
int getValue2() { return value2; }
public AClass(String name, int value1, int value2) {
this.name = name;
this.value1 = value1;
this.value2 = value2;
}
}
static String getMaxString(Function<AClass,Integer> f, List<AClass> listToSort) {
listToSort.sort(Comparator.comparing(f));
int maxValue = f.apply(listToSort.get(listToSort.size() - 1));
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (f.apply(listToSort.get(i)) == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
public static void main(String[] args) {
List<AClass> list = new ArrayList<>();
list.add(new AClass("a", 1,2));
list.add(new AClass("b", 1,2));
list.add(new AClass("c", 2,1));
list.add(new AClass("d", 2,1));
System.out.println(getMaxString(AClass::getValue1, list));
System.out.println(getMaxString(AClass::getValue2, list));
}
}
As Tim Moore suggested above, it isn't necessary to sort the list (which has cost O(n*log n)), we can traverse it twice:
static String getMaxString2(ToIntFunction<AClass> f, List<AClass> listToSort) {
int maxValue = listToSort.stream().mapToInt(f).max().orElseThrow();
return listToSort.stream()
.filter(a -> maxValue == f.applyAsInt(a))
.map(AClass::getName)
.collect(Collectors.joining(", "));
}
Note that you should test your code with an empty list.
It's useful to look at the type signature for Comparator.comparing, because it sounds like you want to do something similar:
static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)
The interesting part is the type of keyExtractor. Roughly speaking, it's a function from the type of the object you're comparing, to the type of the field you want to use for the comparison. In our case, these correspond to the A class and Integer. Because these types are fixed in this example, you can declare a method with a signature like this:
String getMaxString(Function<A, Integer> property)
With the existing algorithm, it can be used this way:
String getMaxString(Function<A, Integer> property) {
listToSort.sort(Comparator.comparing(property));
int maxValue = property.apply(listToSort.get(listToSort.size() - 1));
String maxString = listToSort.get(listToSort.size() - 1).getName();
for (int i = listToSort.size() - 2; i >= 0; i--) {
if (listToSort.get(i).getValueN() == maxValue) {
maxString += ", " + listToSort.get(i).getName();
}
}
return maxString;
}
However, it isn't necessary or efficient to sort the entire list in order to determine the maximum elements, as this can be determined by iterating through the list once:
String getMaxString(Function<A, Integer> property) {
int maxValue = Integer.MIN_VALUE;
StringBuilder maxString = new StringBuilder();
for (A element : listToSort) {
int currentValue = property.apply(element);
if (currentValue > maxValue) {
// there is a new maximum, so start the string again
maxString = new StringBuilder(element.getName());
maxValue = currentValue;
} else if (currentValue == maxValue) {
// equal to the existing maximum, append it to the string
if (maxString.length() > 0) {
maxString.append(", ");
}
maxString.append(element.getName());
}
// otherwise, it's less than the existing maximum and can be ignored
}
return maxString.toString();
}
Either way, you can call it using the same method reference syntax:
getMaxString(A::getValueN)
Time complexity O(n) - only one iteration through the dataset.
Hope it'll help.
If something will be unclear fill free to raise a question.
Main
public class MaxClient {
public static void main(String[] args) {
Comparator<A> comp = Comparator.comparingInt(A::getVal1);
List<A> items = List.of(new A(1, 8), new A(2, 8), new A(5, 8), new A(5, 27), new A(3, 8));
items.stream()
.collect(new GetMax(comp))
.forEach(System.out::println);
}
}
Custom collector GetMax
public class GetMax implements Collector <A, Deque<A>, Deque<A>> {
private final Comparator<A> comp;
public GetMax(Comparator<A> comp) {
this.comp = comp;
}
#Override
public Supplier<Deque<A>> supplier() {
return ArrayDeque::new;
}
#Override
public BiConsumer<Deque<A>, A> accumulator() {
return (stack, next) -> {
if (!stack.isEmpty() && comp.compare(next, stack.peekFirst()) > 0) stack.clear();
if (stack.isEmpty() || comp.compare(next, stack.peekFirst()) == 0) stack.offerLast(next);
};
}
#Override
public BinaryOperator<Deque<A>> combiner() {
return (stack1, stack2) -> {
if (stack1.isEmpty()) return stack2;
if (stack2.isEmpty()) return stack1;
if (comp.compare(stack1.peekFirst(), stack2.peekFirst()) == 0) {
stack1.addAll(stack2);
}
return stack1;
};
}
#Override
public Function<Deque<A>, Deque<A>> finisher() {
return stack -> stack;
}
#Override
public Set<Characteristics> characteristics() {
return Set.of(Characteristics.UNORDERED);
}
}
Class A that I used for testing purposes
public class A {
private int val1;
private int val2;
public A(int val1, int val2) {
this.val1 = val1;
this.val2 = val2;
}
public int getVal1() {
return val1;
}
public int getVal2() {
return val2;
}
#Override
public String toString() {
return "A val1: " + val1 + " val2: " + val2;
}
}
OUTPUT
A val1: 5 val2: 8
A val1: 5 val2: 27
Thanks for posting the information I requested. Here is what I came up with.
Create a list of Course objects
List<Course> list = List.of(
new Course("a", 10, 5),
new Course("b", 20, 5),
new Course("c", 30, 0));
Stream the methods and apply them to the list
List<String> results = Stream.<Function<Course, Integer>>of(
Course::getCapacity,
Course::getStudents)
.map(fnc-> getMaxString(fnc, list))
.toList();
results.forEach(System.out::println);
print the results
c
a b
I wrote a simple method that takes a method reference and list and finds the maximum. It does not do any sorting.
allocate a list to hold the names
set the maximum to the lowest possible
iterate thru the list applying the method.
if the value is greater than the current max replace it and clear the current list of names.
otherwise, if equal, add a new name.
once done, return the formatted string.
static String getMaxString(Function<Course, Integer> fnc,
List<Course> list) {
List<String> result = new ArrayList<>();
int max = Integer.MIN_VALUE;
for (Course obj : list) {
int val = fnc.apply(obj);
if (val >= max) {
if (val > max) {
result.clear();
}
max = val;
result.add(obj.getName());
}
}
return String.join(" ", result);
}
import java.util.*;
class Distance {
private String name;
private int dist;
public Distance(String name, int dist) {
this.name = name;
this.dist = dist;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDist() {
return dist;
}
public void setDist(int dist) {
this.dist = dist;
}
public String toString() {
return "Distance [name=" + name + ", school street=" + dist + "]";
}
}
class DistanceComp {
public static Distance longdistance(Distance[] dim) {
Distance max = dim[0];
for (int i = 1; i < dim.length; i++) {
if (max.getDist() < dim[i].getDist())
max = dim[i];
}
return max;
}
public static Distance shortdistance(Distance[] dim) {
Distance min = dim[0];
for (int i = 1; i < dim.length; i++) {
if (min.getDist() > dim[i].getDist())
min = dim[i];
}
return min;
}
}
public class week03_01 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Distance[] dist = new Distance[3];
System.out.print(">> how many students? : ");
int num = in.nextInt();
for (int i = 0; i < num; i++) {
System.out.print(">> name and distance : ");
dist[i] = new Distance(in.next(), in.nextInt());
}
System.out.println("\na student with the longest commute to school : " + DistanceComp.longdistance(dist));
System.out.println("a student with the shortest commute to school : " + DistanceComp.shortdistance(dist));
System.out.println("school distance difference is " + );
}
}
I also want to print the "shool distance differecnce".
but it doesn't calculate. i think it has String types.
I think calculate only integer types in an array, but i don't know the code.
Or is s there any other way? Ask for advice.
In your DistanceComp class, create a method to subtract two Distances similar like you did for longdistance and shortdistance:
public static int subtractDistance(Distance dist1, Distance dist2) {
int difference = Math.abs(dist1.getDist() - dist2.getDist());
return difference;
}
Then, use that in your System.out:
System.out.println("school distance difference is " + DistanceComp.subtractDistance(DistanceComp.longdistance(dist), DistanceComp.shortdistance(dist)));
Some notes fyi:
Your code currently only works with 3 students.
Instead of using long names, assign them to a shorter-named variable. This helps with code readability.
I have an assignment that requires me to create a method called getExamRange that looks at an array which includes the exam scores of several students, takes the lowest and highest scores, and subtracts the minimum exam score from the maximum exam score. I also have to create a getMostImprovedStudent which run the getExamRange method on an array of Students and returns the student with the highest exam range. I'm having trouble getting the correct results when the code is run. What is causing this problem?
Here is the code for the Student.java class:
import java.util.*;
public class Student
{
private static final int NUM_EXAMS = 4;
private String firstName;
private String lastName;
private int gradeLevel;
private double gpa;
private int[] exams;
private int numExamsTaken;
/**
* This is a constructor. A constructor is a method
* that creates an object -- it creates an instance
* of the class. What that means is it takes the input
* parameters and sets the instance variables (or fields)
* to the proper values.
*
* Check out StudentTester.java for an example of how to use
* this constructor.
*/
public Student(String fName, String lName, int grade)
{
firstName = fName;
lastName = lName;
gradeLevel = grade;
exams = new int[NUM_EXAMS];
numExamsTaken = 0;
}
public int getExamRange()
{
Arrays.sort(exams);
int examScore1 = exams[0];
int examScore2 = 0;
int lastPos = 0;
for(int i = 0; i < exams.length - 1; i++)
{
lastPos++;
}
examScore2 = exams[lastPos];
return examScore2 - examScore1;
}
public String getName()
{
return firstName + " " + lastName;
}
public void addExamScore(int score)
{
exams[numExamsTaken] = score;
numExamsTaken++;
}
// This is a setter method to set the GPA for the Student.
public void setGPA(double theGPA)
{
gpa = theGPA;
}
/**
* This is a toString for the Student class. It returns a String
* representation of the object, which includes the fields
* in that object.
*/
public String toString()
{
return firstName + " " + lastName + " is in grade: " + gradeLevel;
}
}
and here is the code for the Classroom.java class:
public class Classroom
{
Student[] students;
int numStudentsAdded;
public Classroom(int numStudents)
{
students = new Student[numStudents];
numStudentsAdded = 0;
}
public Student getMostImprovedStudent()
{
int highestScore = 0;
int score = 0;
int location = 0;
int finalLocation = 0;
for(Student s: students)
{
score = s.getExamRange();
location++;
if(score > highestScore)
{
highestScore = score;
finalLocation = location;
}
}
return students[finalLocation - 1];
}
public void addStudent(Student s)
{
students[numStudentsAdded] = s;
numStudentsAdded++;
}
public void printStudents()
{
for(int i = 0; i < numStudentsAdded; i++)
{
System.out.println(students[i]);
}
}
}
Here are the directions for the assignment which state what the methods are supposed to do:
Taking our Student and Classroom example from earlier, you should fill in the method getMostImprovedStudent, as well as the method getExamRange. The most improved student is the one with the largest exam score range.
To compute the exam score range, you must subtract the minimum exam score from the maximum exam score.
For example, if the exam scores were 90, 75, and 84, the range would be 90 - 75 = 15.
Firstly let us look at the getExamRange function
public int getExamRange(){
if(exams == null ||exams.length == 0){
return 0;
}
int min = exams[0];
int max = exams[0];
for (int i : exams
) {
if(i<min){
min=i;
}
if(i>max){
max=i;
}
}
return max - min;
}
and now on getMostImprovedStudent
public Student getMostImprovedStudent()
{
if(students == null ||students[0] == null || students.length=0){
return null;
}
int highestScore = students[0].getExamRange();
int score = 0;
Student mostImprovedStudent = students[0]
for(int i=0;i<students.length;i++)
{
if(students[i]!=null){
score = students[i].getExamRange();
if(score > highestScore)
{
highestScore = score;
mostImprovedStudent = students[i];
}
}
}
return mostImprovedStudent;
}
Purse Class:
a. There will be an array of Coin objects (using the Coin Class) and a parallel array of integer counts. There should be single variables that hold the count of Coin objects added so far, total number of coins and total value in the purse.
b. The constructor should set the dimension of the arrays to be 10 and set the single instance variables to zero.
c. Three accessors are needed: count of Coin objects added so far, the total count of all the coins, and the total value in the purse.
d. The toString() method should output the values for each Coin in the Purse along with its count and value (count * Coin Value).
e. There will be an add() method that takes in both a Coin object and a count of how many are being added to the Purse. Place the Coin and count in the next available position in the parallel arrays. The total count and value in the purse should be incremented.
I wrote the Coin Class and Purse Class, but there is a glitch in the Purse Class because it is not counting the type of coins or the number of coins properly.
Coin Class:
import java.text.NumberFormat;
public class Coin {
private String name; //instance variable (a)
private double value;
private int year;
//Default Constructor (b)
public Coin(){
String name = " ";
double value = 0.0;
int year = 0;
}
//Parametrized Constructor (c)
public Coin(String name, double value, int year){
this.name = name;
this.value = value;
this.year = year;
}
//Accessors(d)
public String getName(){
return this.name;
}
public double getValue(){
return this.value;
}
public int getYear(){
return this.year;
}
//Mutators(d)
public void setName(String newName){
this.name = newName;
}
public void setValue(double newValue){
this.value = newValue;
checkValue();
}
public void setYear(int newYear){
this.year = newYear;
checkYear();
}
//Helper Method(e)
public void checkValue(){
if (value < 0.0){
this.value = 0.0;
}
}
//Helper Method (f)
public void checkYear(){
if((year >= 1900) || (year <= 2018)){
this.year = 0;
}
}
//toString method (g)
public String toString(){
String str = "Coin:" + this.name;
NumberFormat fmt = NumberFormat.getCurrencyInstance();
str += "\tvalue:" + fmt.format(this.value);
str += "\tyear:";
if (year!= 0)
str += this.year;
else
{
str += "Unknown";
}
return str;
}
}
Purse Class:
import java.text.NumberFormat;
public class Purse {
//a
private Coin[] coins;
private int[]counts;
private int coinCount;
private double totalValue;
private int totalCount;
//b (Constructor)
public Purse(){
coins = new Coin[10];
counts = new int[10];
coinCount = 0;
totalValue = 0.0;
totalCount = 0;
}
//c
public int getCoinCount(){
return coinCount;
}
public int getTotalCount(){
return totalCount;
}
public double getTotalValue(){
return totalValue;
}
//d (toString() method)
public String toString(){
String result = " ";
NumberFormat fmt = NumberFormat.getCurrencyInstance();
for(int i = 0; i<coinCount; i++){
result += fmt.format(counts[i] * coins[i].getValue())+coins[i]+counts[i];
}
return result;
}
//e
public void addCoin(Coin c,int n){
coins[coinCount]=c;
counts[coinCount]=n;
coinCount++;
totalCount += n;
totalValue += (n * c.getValue());
}
}
Driver:
public class CoinDriver {
public static void main(String[] args) {
Coin p = new Coin("Penny", 0.01, 2001);
Coin n = new Coin("Nickel", 0.05, 2001);
Coin d = new Coin("Dime", 0.10, 2001);
Coin q = new Coin("Quarter", 0.25, 2001);
Coin l = new Coin("Loonie", 1.00, 2001);
Coin t = new Coin ("Toonie", 2.00, 2001);
Purse purse = new Purse();
System.out.print("There are: " +purse.getCoinCount() + "types of coins, a total of:" + purse.getTotalCount()+ "coins, worth:" + purse.getTotalValue());
System.out.print(purse);
purse.addCoin(p,3);
purse.addCoin(n,5);
purse.addCoin(d,7);
System.out.print("There are: " +purse.getCoinCount() + "types of coins, a total of:" + purse.getTotalCount()+ "coins, worth:" + purse.getTotalValue());
System.out.print(purse);
}
}
The final Results should include the number of coins in the purse, the types of coins (total of), and the value of all the coins.
I have these monsters in the LinkedList and it iterates through it. In addition to that I want my output to look like this:
Screaming-Monster "Bob" with fightingpower 1
Fighting-Monster "Tom" with fightingpower 6
Fighting-Monster "Dave" with fightingpower 7
Scream-Fight-Monster "Steve" with fightingpower 3
How do I archieve this?
public class AdventureGame {
public static void main(String[] args) {
LinkedList <String> monsterList = new LinkedList <String>();
monsterList.add("Screaming-Monster \"Bob\"");
monsterList.add("Fighting-Monster \"Tom\"");
monsterList.add("Fighting-Monster \"Dave\"");
monsterList.add("Scream-Fight-Monster \"Steve\"");
for(int i=0; i<monsterList.size(); i++){
System.out.println(monsterList.get(i));
}
}}
You need to associate the 'fighting power' value with the monster, but you don't do this in your program. Values that relate to each other should be stored together. How about a class to represent a monster:
class Monster {
private String name;
private int fightingPower;
public Monster(String name, int fightingPower) {
this.name = name;
this.fightingPower = fightingPower;
}
public String getName() {
return name;
}
public int getFightingPower() {
return fightingPower;
}
}
Then you could make a list of Monsters like this:
LinkedList<Monster> monsterList = new LinkedList <Monster>();
monsterList.add(new Monster("Screaming-Monster \"Bob\"", 1));
monsterList.add(new Monster("Fighting-Monster \"Tom\"", 6));
monsterList.add(new Monster("Fighting-Monster \"Dave\"", 7));
monsterList.add(new Monster("Scream-Fight-Monster \"Steve\"", 3));
and you could print them out like this:
for(int i = 0; i < monsterList.size(); i++){
Monster monster = monsterList.get(i);
System.out.println(monster.getName() + " with fightingpower " + monster.getFightingPower());
}
Try creating a monster class that looks like this:
private String type;
private String name;
private int fightingpower;
monster(String type, String name, int fightingpower) {
this.type = type;
this.name = name;
this.fightingpower = fightingpower;
}
public String getStats() {
String stats = this.type + " \"" + this.name + "\" with fightingpower " + this.fightingpower;
return stats;
}
then in your class with your LinkedList do:
LinkedList <monster> monsterList = new LinkedList <>();
monsterList.add(new monster("Screaming-Monster", "Bob", 1))
//Repeat for every monster you want to have
for (int i = 0; i < monsterList.size(); i++) {
System.out.println(monsterList.get(i).getStats());
}
Further to #matt's fine answer, and as the title of your question is "LinkedList iterating", I'd like to point out that you should use a for-each loop to iterate over a linked list:
for (Monster monster : monsterList) {
System.out.println(monster.getName() + " with fightingpower "
+ monster.getFightingPower());
}
Using a for-each loop is O(n), while using an index and get() is O(1/2 n * n)