I have to rank the teams in the array "names" (below) in order of who won the most games. If two teams won the same amount of games then I have to compare the wins of the teams that beat them. The code I have so far is below.
The full problem statement is given here: http://www.cs.duke.edu/csed/newapt/tournamentrank.html
So I want to use the comparator recursively. How can the Comparator can have access to the original data? I tried creating a Team class that takes a variable of the same class for the team that beat it, but that clearly doesn't work. Stuck here, please help!
public class TournamentRanker implements Comparator<String>{
public class Team {
String name;
Integer wins;
Team beatEm;
}
//HOW TO make maps visible to comparator?
public String[] rankTeams(String[] names, String[] lostTo) {
//map all teams to number of wins & to team that beat them
ArrayList<String> teams = new ArrayList<String>();
HashMap<String, Integer> Teamwins = new HashMap<String, Integer>();
HashMap<String, String> Whobeat = new HashMap<String, String>();
for(int x=0; x<names.length; x++)
{
if(!teams.contains(names[x]))
teams.add(names[x]);
if(!Teamwins.containsKey(names[x]))
Teamwins.put(names[x], 0);
Whobeat.put(names[x], lostTo[x]);
if(!Teamwins.containsKey(lostTo[x]) && !lostTo[x].equals(""))
Teamwins.put(lostTo[x], 0);
if(!lostTo[x].equals(""))
Teamwins.put(lostTo[x], (Teamwins.get(lostTo[x])+1));
}
for(String s: names)
{
Integer wins = Teamwins.get(s);
Team beatEm = new Team(Whobeat.get(s), Teamwins.get(Whobeat.get(s)), ????)
}
//SORT list & turn into ARRAY
Comparator<String> comp = new TournamentRanker();
Collections.sort(teams, comp);
String [] sortedTeams = new String[teams.size()];
return teams.toArray(sortedTeams);
}
//NEED to use compareTo***?? OTHER strategy????
//USE COMPARTOR - how to access all the data?
public int compare(String team1, String team2){
}
}
To make the maps visible, I suggest making the Comparator an inner class of TournamentRanker, and making the maps instance members of the TournamentRanker class, as follows:
public class TournamentRanker {
public class Team {
String name;
Integer wins;
Team beatEm;
}
// Map all teams to number of wins & to team that beat them
ArrayList<String> teams = new ArrayList<String>();
HashMap<String, Integer> Teamwins = new HashMap<String, Integer>();
HashMap<String, String> Whobeat = new HashMap<String, String>();
public String[] rankTeams(String[] names, String[] lostTo) {
TeamComparator teamComparator = new TeamComparator();
// Use teamComparator to sort teams.
...
}
private TeamComparator implements Comparator<String> {
public int compare(String team1, String team2){
// This function can now access the maps.
// Perform the comparison here.
...
}
}
}
If your goal is to write an object-oriented program, I would structure it as follows:
A Game class containing references to the two teams that played the game, and each team's score.
A Team class containing a Map of games played (Game objects) indexed by opposing team ID. If two teams can meet more than once than you'll need a "multi-map" that allows more than one value object per key.
A Tournament object containing both the Team instances (probably a Map indexed by team name) and Game objects (another Map indexed by some unique key of your choosing).
In your comparator, you can compare two teams' win-loss record, and in case of ties look at each team's individual games.
Related
I'm working on a highscore system that reads from a file line-by-line and adds all lines into a treemap, sorts the treemap and adds the scores and names into a new file, highest score being at the top.
I've gotten the system close but for some unknown reason the code is removing duplicate entries, for example, i have 3 scores.
1 : Sander
1 : Sander
2 : Mark
Printing my treemap would look like this:
I would like the code to show Sander twice.
I've been stuck for quite some time and would appriciate some help, here is my code:
public void sortScores() throws IOException {
File input = new File("scores.txt");
File output = new File("outputscores.txt");
FileInputStream fis = new FileInputStream(input);
FileOutputStream fos = new FileOutputStream(output);
BufferedReader in = new BufferedReader(new InputStreamReader(fis));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(fos));
String aLine;
while ((aLine = in.readLine()) != null) {
String[] scoreAndName = aLine.split(" : ");
int score1 = Integer.parseInt(scoreAndName[0]);
String name1 = scoreAndName[1];
unsortMap1.put(score1, name1);
}
Map<Integer, String> treeMap = new TreeMap<Integer, String>(
new Comparator<Integer>() {
#Override
public int compare(Integer o1, Integer o2) {
if (o1 >= o2) {
return -1;
} else {
return 1;
}
}
});
treeMap.putAll(unsortMap1);
System.out.println(treeMap);
}
That's exactly the way Map is supposed to work. From documentation:
An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.
put(key, value) replace the old value with the new one.
While you can make it work using Map<Integer, List<String>> (for example), that's cumbersome and doesn't look clean. List is a better data structure for this task.
I would define a class Score, use it to populate the list, and sort it with a custom comparator. You could also make Score implement Comparable instead of using a Comparator.
Here's an example that uses Comparator.comparingInt().
List<Score> scores = new ArrayList<>();
scores.add(new Score(1, "Name")); //adding an element
scores.sort(Comparator.comparingInt(Score::getScore)); //sorting
class Score {
private final int score;
private final String name;
Score(int score, String name) {
this.score = score;
this.name = name;
}
public int getScore() {
return score;
}
public String getName() {
return name;
}
}
You have chosen the wrong collection type.
A Map<Integer, String> will store only one String value for any possible Integer. So given a score (e.g. 1) it can store a single name. Not the same name multiple times.
It appears that you are trying to sort records representing scores. To do that, you are going to need to create a custom class whose instances represent the records.
You can sort the records a number of ways:
Put them into an array use Arrays.sort
Put them into a list and use Collections.sort
Implement your own sort algorithm. (Not recommended. It is better not to waste your time "reinventing the wheel".)
You will either need to implement a Comparator to order the records1, or make your record class implement Comparable.
For more details, please read the respective javadocs.
But you cannot use a TreeMap or TreeSet (or any other Map or Set implementations) for this. Your records are not unique, and those data structures will remove duplicates2.
1 - From Java 8, the Comparator interface has some static helper methods for creating comparators; e.g. Comparator.comparingInt().
2 - Not strictly true. You could do this if your records had a 3rd field containing a unique identifier which you used as a tie-breaker in the comparator. Or you could use a Map<YouRecord, Integer>, where the integer represents a count of records that are "equal".
I have a simple object like this
public class Person {
private int id;
private int age;
private String hobby;
//getters, setters
}
I want to group a list of Person by attributes
Output should be like this
Person count/Age/Hobby
2/18/Basket
5/20/football
With a chart for more understanding
X axis : hobby repartition
Y axis : count of person distribution
Colors represents age
I managed to group by one attribute using map, but I can't figure how to group by multiples attributes
//group only by age . I want to group by hobby too
personMapGroupped = new LinkedHashMap<String, List<Person>>();
for (Person person : listPerson) {
String key = person.getAge();
if (personMapGroupped.get(key) == null) {
personMapGroupped.put(key, new ArrayList<Person>());
}
personMapGroupped.get(key).add(person);
}
Then I retrieve the groupable object like this
for (Map.Entry<String, List<Person>> entry : personMapGroupped .entrySet()) {
String key = entry.getKey();// group by age
String value = entry.getValue(); // person count
// I want to retrieve the group by hobby here too...
}
Any advice would be appreciated.
Thank you very much
Implement methods for comparing people according to the different fields. For instance, if you want to group by age, add this method to Person:
public static Comparator<Person> getAgeComparator(){
return new Comparator<Person>() {
#Override
public int compare(Person o1, Person o2) {
return o1.age-o2.age;
}
};
}
Then you can simply call: Arrays.sort(people,Person.getAgeComparator()) or use the following code to sort a Collection:
List<Person> people = new ArrayList<>();
people.sort(Person.getAgeComparator());
To sort using more than one Comparator simultaneously, you first define a Comparator for each field (e.g. one for age and one for names). Then you can combine them using a ComparatorChain. You would use the ComparatorChain as follows:
ComparatorChain chain = new ComparatorChain();
chain.addComparator(Person.getNameComparator());
chain.addComparator(Person.getAgeComparator());
You could simply combine the attributes to a key.
for (Person person : listPerson) {
String key = person.getAge() + ";" + person.getHobby();
if (!personMapGrouped.contains(key)) {
personMapGrouped.put(key, new ArrayList<Person>());
}
personMapGrouped.get(key).add(person);
}
The count of entries is easy to determine by using personMapGrouped.get("18;Football").getSize().
I'm not sure about your requirements, but I'd probably use multiple maps (Google Guava's Multimap would make that easier btw) and sets, e.g. something like this:
//I'm using a HashMultimap since order of persons doesn't seem to be relevant and I want to prevent duplicates
Multimap<Integer, Person> personsByAge = HashMultimap.create();
//I'm using the hobby name here for simplicity, it's probably better to use some enum or Hobby object
Multimap<String, Person> personsByHobby = HashMultimap.create();
//fill the maps here by looping over the persons and adding them (no need to create the value sets manually
Since I use value sets Person needs a reasonable implementation of equals() and hashCode() which might make use of the id field. This also will help in querying.
Building subsets would be quite easy:
Set<Person> age18 = personsByAge.get(18);
Set<Person> basketballers = personsByHobby.get( "basketball" );
//making use of Guava again
Set<Person> basketballersAged18 = Sets.intersection( age18, basketballers );
Note that I made use of Google Guava here but you can achieve the same with some additional manual code (e.g. using Map<String, Set<Person>> and manually creating the value sets as well as using the Set.retainAll() method).
I have a hashMap called prizeWinners. The key for each pair in the hashMap is the year a nobel prize was won. The value is the name(s) of the winners. The value is an array of Strings, because in any given prize year, there can be up to 3 winners. My question is this: Given the name of a winner, return the year in which they won the prize. Interestingly enough, the return value also needs to be a string: public String getYearWon(String name), where name is the name of the winner.
public class NobelPrizeWinners
{
private HashMap<Integer, String[]> prizeWinners;
public NobelPrizeWinners
{
prizeWinners = new HashMap<Interger, String[]>();
prizeWinners.put(2009, new String[] {"Barack H. Obama"});
prizeWinners.put(2008, new String[] {"Martti Ahtisaari"};
prizeWinners.put(2007, new String[] {"IAEA", "Mohamed ElBaradei"});
//and many more
}
public String getYearWon(String name)
{
//code here
}
}
And this is where I get stuck: I cannot seem to access the array correctly to iterate through it and get the key. There are several methods in my assignment requiring me to do so (for example, to print out the names of all winners), but I only get the hashmap address, not the contents of the array itself.
Iterate through the prizeWinner keys (years). For each year, get the winners String array and convert it to a list, then call the contains method to see if the winners list contains the winner name passed as parameter. If it's the case, we stop looping and return the year as a String. If we went through all the years but didn't find the winner's name, we would return null
private HashMap<Integer, String[]> prizeWinners;
public PartsPortlet()
{
prizeWinners = new HashMap<Integer, String[]>();
prizeWinners.put(2009, new String[]{"Barack H. Obama"});
prizeWinners.put(2008, new String[]{"Martti Ahtisaari"};
prizeWinners.put(2007, new String[]{"IAEA", "Mohamed ElBaradei"});
//and many more
}
public String getYearWon(String name) {
for (int year : prizeWinners.keySet()) {
if (Arrays.asList(prizeWinners.get(year)).contains(name)) {
return String.valueOf(year);
}
}
return null;
}
Suppose I want to store phone numbers of persons. Which kind of collection should I use for key value pairs? And it should be helpful for searching. The name may get repeated, so there may be the same name having different phone numbers.
In case you want to use key value pair. Good choice is to use Map instead of collection.
So what should that map store ?
As far it goes for key. First thing you want to assure is that your key is unique to avoid collisions.
class Person {
long uniqueID;
String name;
String lastname;
}
So we will use the uniqueID of Person for key.
What about value ?
In this case is harder. As the single Person can have many phone numbers. But for simple task lest assume that a person can have only one phone number. Then what you look is
class PhoneNumberRegistry {
Map<Long,String> phoneRegistry = new HashMap<>();
}
Where the long is taken from person. When you deal with Maps, you should implement the hashCode and equals methods.
Then your registry could look like
class PhoneNumberRegistry {
Map<Person,String> phoneRegistry = new HashMap<>();
}
In case when you want to store more then one number for person, you will need to change the type of value in the map.
You can use Set<String> to store multiple numbers that will not duplicate. But to have full control you should introduce new type that not only store the number but also what king of that number is.
class PhoneNumberRegistry {
Map<Person,HashSet<String>> phoneRegistry = new HashMap<>();
}
But then you will have to solve various problems like, what phone number should i return ?
Your problem has different solutions. For example, I'll go with a LIST: List<Person>, where Person is a class like this:
public class Person{
private String name;
private List<String> phoneNumbers;
// ...
}
For collections searching/filtering I suggest Guava Collections2.filter method.
You should use this:
Hashtable<String, ArrayList<String>> addressbook = new Hashtable<>();
ArrayList<String> persons = new ArrayList<String>()
persons.add("Tom Butterfly");
persons.add("Maria Wanderlust");
addressbook.put("+0490301234567", persons);
addressbook.put("+0490301234560", persons);
Hashtable are save to not have empty elements, the ArrayList is fast in collect small elements. Know that multiple persons with different names may have same numbers.
Know that 2 persons can have the same number and the same Name!
String name = "Tom Butterfly";
String[] array = addressbook.keySet().toArray(new String[] {});
int firstElement = Collections.binarySearch(Arrays.asList(array),
name, new Comparator<String>() {
#Override
public int compare(String top, String bottom) {
if (addressbook.get(top).contains(bottom)) {
return 0;
}
return -1;
}
});
System.out.println("Number is " + array[firstElement]);
Maybe
List<Pair<String, String> (for one number per person)
or
List<Pair<String, String[]> (for multiple numbers per person)
will fit your needs.
Let's say I have an object of each class below, and I put each object in a hashmap where IDnumber is the key in both maps.
class1 {
int IDNumber = 123; //same person as class2
String name = John;
String company = Intel;
class2 {
int IDNumber = 123; //same person as class1
int income = 500;
int workYears = 3;
}
HashMap<Integer, class1> one = new Hashmap<Integer, class1>();
HashMap<Integer, class2> two = new HashMap<Integer, class2>();
Now, how can I mash these two HashMaps into a third HashMap so that I can have the key IDnumber, and the values name, company, income, and workyears?
You can not do that. You have two different classes, and java is not going to auto-magically make them one.
You could create a new third class to merge the info:
public Class3 {
public Class3(Class1 class1, Class2 class2){
//pull the info you want from each into variables in this class
}
}
Then loop through your map to get the entries, creating new Class3 instance for each and place them in a new HashMap<Integer, Class3>.
//gets the keys from the hashmap
Set<Integer> keys = one.keySet();
//merge the keys from the second hashmap
keys.addAll(two.keySet());
//new hashmap
Map<Integer, Class3> newMap = new HashMap<Integer, Class3>();
for (Integer key : keys){
//create new instance and place it in the map
newMap.put(key, new Class3(one.get(key), two.get(key));
}
Create a 3rd class called Combined in one of these 2 ways:
Combined {
class1 info1;
class2 info2;
}
Or, better:
Combined {
int IDNumber = 123;
String name = John;
String company = Intel;
int income = 500;
int workYears = 3;
}
Create a 3rd (empty) HashMap
Now iterate over all elements in the first HashMap you had before
For each entry, look up the same key in the 2nd HashMap:
If it is found, combine the information from these 2 entries and add it as a single entry of instance Combined to the 3rd HashMap. Then remove both of these entries from both HashMaps one and two.
If it is not found, then create a Combined instance anyway based on the entry in HashMap one and just set the unavailable information that would have come from a corresponding entry from HashMap two to null. Remove the entry from HashMap one.
Now the 1st HashMap should be empty. Iterate HashMap two to find any entries that did not have a corresponding ID in HashMap one. Add them to the 3rd HashMap as above.
You can't store multiple values (i.e. Class1 and Class2 in your case) with the same key in a java.util.Map. What you want is a Multimap. Guava has an implementation for this. The one you are looking for is ArrayListMultimap.
You need to make a new class which is a mashup of class1 and class2 along with a new Map for it. Each time you put something in one and 'two' you make a new mashup containing both objects and put it into three.
As your data structure is not solit you should use some adapter/wraper
public class UserClassWrapper {
private final UserClass1 class1;
private final UserClass2 class2;
UserWithDetail(UserClass1 class1, UserClass2 class2) {
//Check preconditions as class1 and class2 must not be null and have equal id
this.class1 = class1;
this.class2 = class2;
}
}
Then in your code you can declare a map:
private Map<Integer,UserClassWrapper> userClassWrappeMap = new HashMap<>();
You can use an ArrayList to create an HashMap with multiple of values for one key
ArrayList<> list = new ArrayList();
/* populate your list here with two class having the same ID */
HashMap<Integer, List> three = new Hashmap();
/* Put list on the Hashmap */
/* Redo the operation with another ID */
But it's not very optimized, if you aren't obliged to have an HashMap at final, use directly a multiHashMap: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/MultiHashMap.html
Are you expecting this kind of arrangment
class class1 {
int IDNumber = 123; //same person as class2
String name = "John";
String company = "Intel";
}
class class2 {
int IDNumber = 123; //same person as class1
int income = 500;
int workYears = 3;
}
public class MyData{
public static void main(String arg[]){
HashMap<Integer, class1> one = new HashMap<Integer, class1>();
HashMap<Integer, class2> two = new HashMap<Integer, class2>();
one.put(1, new class1());
two.put(2, new class2());
HashMap<Integer, Object> three = new HashMap<Integer, Object>();
three.putAll(one);
three.putAll(two);
System.out.println(three);
}
}