1

I am building a classical Nim game. So far, I've done the player part and the game part. Now, I am trying to sort(rank) the object in an array. I've built the following for sorting:

  1. playerList
  2. winRatio, which is set in the NimPlayer class with its getter.

The aim :

  1. to sort the winRatio descendingly, which means from the highest score to the lowest one. The ratio is calculated by score/gamePlayed.
  2. If there's a tie, sort using userName alphabetically.

I've referred to this issue: How to sort an array of objects in Java?

I know what I should do is use the Comparable or Comparator for sorting but from the article, they sort using the attributes in the object (All the information I've found so far). What I am dealing with is the continuously updated data outside the constructor.

What I've tried was directly printed out the data without sorting.

Here is my related code (NimPlayer):

public class NimPlayer {
private String userName;
private String familyName;
private String givenName;

static int counter;
private int score;
private int gamePlayed;
static NimPlayer[] playerList = new NimPlayer[2]; // set an array here

//define NimPlayer data type
public NimPlayer(String userName, String surName, String givenName) {
    this.userName = userName;
    this.familyName = surName;
    this.givenName = givenName;

}
// create new data using NimPlayer data type
public static void createPlayer(String userName, String familyName, String givenName) {
    if (counter<10) {
        playerList[counter++] = new NimPlayer(userName, familyName, givenName);
    } else {
        System.out.println("Cannot add more players.");
    }

// all the getter and setter related to the constructor; the getter of the player list.

}
public void setScore(int score) {
    this.score=score;
}
public int getScore() {
    return score;
}
public void setGamePlayed (int gamePlayed) {
    this.gamePlayed = gamePlayed;
}
public int getGamePlayed() {
    return gamePlayed;
}
public int getWinRatio () {
    return Math.round(Float.valueOf(getScore())/ (getGamePlayed()+1)*100) ;
}
}

This is my main class (Nimsys)

public static void searchAndPrintRankingData() {
    for (int i = 0; i < NimPlayer.getCounter(); i++) {

        String familyName = NimPlayer.getPlayer()[i].getFamilyName();
        String givenName = NimPlayer.getPlayer()[i].getGivenName();
        int score = NimPlayer.getPlayer()[i].getScore();
        int gamePlayed = NimPlayer.getPlayer()[i].getGamePlayed();
        double winRatio = score/(gamePlayed+1);//wrong calculation for testing

        System.out.println(winRatio+"% | "+gamePlayed+" games | "+givenName+" "+familyName);
    }
}

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    while (true) {
        System.out.print('$');
        String commandin = in.next();
if (commandin.equals("rankings")) {
            String commandOrder = in.nextLine().trim();

            if (commandOrder.equals("asc")) {
                //sort the data
                searchAndPrintRankingData();
            }

            if (commandOrder.equals("") || commandOrder.equals("desc")) {
                //sort the data

                searchAndPrintRankingData(); 
            }            
        }
        }

Any help is highly appreciated.

Woden
  • 1,054
  • 2
  • 13
  • 26
  • 1
    You should have a look at java collections framework first, there are plenty of data structures implementation already there that suits your expectation, if you do not have to implement your own. –  Apr 24 '20 at 08:09
  • Thanks for the recommendation. I am trying to deal with it without using `ArrayList`. Of course, `ArrayList` saves a lot of time and efforts. – Woden Apr 24 '20 at 08:39

4 Answers4

2

Use Interface Comparator

Do it as follows:

import java.util.Arrays;
import java.util.Comparator;

class NimPlayer {
    private String userName;
    private String familyName;
    private String givenName;

    private static int counter;
    private static final int SIZE = 4;
    private int score;
    private int gamePlayed;

    static NimPlayer[] playerList = new NimPlayer[SIZE];

    public NimPlayer(String userName, String surName, String givenName) {
        this.userName = userName;
        this.familyName = surName;
        this.givenName = givenName;
    }

    public static void createPlayer(String userName, String familyName, String givenName) {
        if (counter < SIZE) {
            playerList[counter++] = new NimPlayer(userName, familyName, givenName);
        } else {
            System.out.println("Cannot add more players.");
        }
    }

    public static int getCounter() {
        return counter;
    }

    public static NimPlayer[] getPlayerList() {
        return playerList;
    }

    public String getUserName() {
        return userName;
    }

    public String getFamilyName() {
        return familyName;
    }

    public String getGivenName() {
        return givenName;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public int getScore() {
        return score;
    }

    public void setGamePlayed(int gamePlayed) {
        this.gamePlayed = gamePlayed;
    }

    public int getGamePlayed() {
        return gamePlayed;
    }

    public int getWinRatio() {
        return Math.round((Float.valueOf(score) / gamePlayed) * 100);
    }

    @Override
    public String toString() {
        return "User Name: " + userName + ", Name: " + givenName + " " + familyName + ", Score: " + score
                + ", Games Played: " + gamePlayed + ", Win ratio: " + getWinRatio();
    }

}

public class Main {
    static void searchAndPrintRankingData() {
        NimPlayer[] players = NimPlayer.getPlayerList();
        Arrays.sort(players,
                Comparator.comparing((NimPlayer::getWinRatio)).reversed().thenComparing(NimPlayer::getUserName));

        Arrays.stream(players).forEach(System.out::println);
    }

    public static void main(String[] args) {
        NimPlayer.createPlayer("Avi", "Avinash", "Arvind");
        NimPlayer.createPlayer("Harry", "Potter", "Harry");
        NimPlayer.createPlayer("Vishy", "Anand", "Vishwanathan");
        NimPlayer.createPlayer("Bond", "Bond", "James");

        NimPlayer[] players = NimPlayer.getPlayerList();

        players[0].setGamePlayed(2);
        players[0].setScore(40);

        players[1].setGamePlayed(3);
        players[1].setScore(75);

        players[2].setGamePlayed(2);
        players[2].setScore(120);

        players[3].setGamePlayed(4);
        players[3].setScore(100);

        System.out.println("Unsorted: ");
        Arrays.stream(NimPlayer.getPlayerList()).forEach(System.out::println);

        System.out.println();

        System.out.println("Sorted on win ratio (then name, in case of tie): ");
        searchAndPrintRankingData();
    }
}

Output:

Unsorted: 
User Name: Avi, Name: Arvind Avinash, Score: 40, Games Played: 2, Win ratio: 2000
User Name: Harry, Name: Harry Potter, Score: 75, Games Played: 3, Win ratio: 2500
User Name: Vishy, Name: Vishwanathan Anand, Score: 120, Games Played: 2, Win ratio: 6000
User Name: Bond, Name: James Bond, Score: 100, Games Played: 4, Win ratio: 2500

Sorted on win ratio (then name, in case of tie): 
User Name: Vishy, Name: Vishwanathan Anand, Score: 120, Games Played: 2, Win ratio: 6000
User Name: Bond, Name: James Bond, Score: 100, Games Played: 4, Win ratio: 2500
User Name: Harry, Name: Harry Potter, Score: 75, Games Played: 3, Win ratio: 2500
User Name: Avi, Name: Arvind Avinash, Score: 40, Games Played: 2, Win ratio: 2000

Note: In case you wish to use ArrayList<NimPlayer> in searchAndPrintRankingData, given below is the code for the same:

static void searchAndPrintRankingData() {
    List<NimPlayer> players = new ArrayList<NimPlayer>(Arrays.asList(NimPlayer.getPlayerList()));
    Collections.sort(players,
            Comparator.comparing((NimPlayer::getWinRatio)).reversed().thenComparing(NimPlayer::getUserName));

    players.stream().forEach(System.out::println);
}
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • thank you so much, sir! I think using `ArrayList` will be the last way, right? Using only the array will provoke `NullPointerException` is there's a `null`? @Arvind – Woden Apr 24 '20 at 14:20
  • 1
    You are most welcome. I've updated the answer to use `NimPlayer[]` instead of `List`. I've also added my original definition of the method, `searchAndPrintRankingData` at the end as a`Note`. – Arvind Kumar Avinash Apr 24 '20 at 14:28
  • 1
    Thank you so much, sir. It worked well and your code is so CLEAN to read. I am dealing with the printing format now. Probably I'll finish it by tomorrow. Have a nice day. @Arvind – Woden Apr 24 '20 at 15:24
1

For sort ascending use the code link you mentioned. As long as the players have been added to the array, you can sort them. The code below is the key (you will need to adapt variable names etc) You will need to add winRation etc to NimPlayer constructor and as element of the array, then it can sort it.

@Test
public void sortBooks() {
Book[] books = {
        new Book("foo", "1", "author1", "pub1"),
        new Book("bar", "2", "author2", "pub2")
};

// 1. sort using Comparable
Arrays.sort(books);
System.out.println(Arrays.asList(books));

// 2. sort using comparator: sort by id
Arrays.sort(books, new Comparator<Book>() {
    @Override
    public int compare(Book o1, Book o2) {
        return o1.id.compareTo(o2.id);
    }
});
System.out.println(Arrays.asList(books));
}

Change this :

 return o1.id.compareTo(o2.id);

To:

return o1.winRatio.compareTo(o2.winRatio);
AdMac
  • 31
  • 4
  • Actually, I tried this method hours ago, but please forgive me that I don't know how to pass the function without using `toString()`. I wanna print out the result in `Nimsys` which is my main method. Therefore, I eager to know how to handle this problem. – Woden Apr 24 '20 at 08:48
  • 1
    If the sort books function is called from NimPlayer class, you could create a ToString there when you call from that class. Without going into detail, that should work using oop principles – AdMac May 01 '20 at 00:17
1

It should work by implementing the Comparable interface, as the compareTo method will be called each time you want to sort and will have the updated data.

To sort first by winRatio, and if equal, by name, it can work with the following code:

public class NimPlayer implements Comparable<NimPlayer> {

    ...

    @Override
    public int compareTo(NimPlayer o) {
        // get the comparison of the win ratios
        int ratioCompare = this.getWinRatio().compareTo(o.getWinRatio());
        // if the winRatio is equal, return the reverse comparison of the usernames
        return (ratioCompare != 0) ? ratioCompare : o.userName.compareTo(this.userName);
    }

}

Then, to sort the array, you just need to use the Arrays class:

Arrays.sort(NimPlayer.playerList);
Arrays.sort(NimPlayer.playerList, Collections.reverseOrder()); // for desc order

The problem you may face with that is if you always want to have the username ordered ASC. In that case, your only option is to implement two different comparators, one for ascending and another for descending orders:

Comparator<NimPlayer> ascComparator = (p1, p2) -> {
    int ratioCompare = p1.getWinRatio().compareTo(p2.getWinRatio());
    return (ratioCompare != 0) ? ratioCompare : p1.getUserName().compareTo(p2.getUserName());
};

Comparator<NimPlayer> descComparator = (p1, p2) -> {
    int ratioCompare = p2.getWinRatio().compareTo(p1.getWinRatio());
    return (ratioCompare != 0) ? ratioCompare : p1.getUserName().compareTo(p2.getUserName());
};

To sort using those comparators, just pass the comparators into the Arrays.sort method:

Arrays.sort(NimPlayer.playerList, ascComparator);
Arrays.sort(NimPlayer.playerList, descComparator);
PBarri
  • 317
  • 1
  • 4
  • I got stuck when writing the `Comparator` part. I put this into `NimPlayer` class. I can only put one argument inside the brackets. Again, thanks for your patience and time in solving this problem. – Woden Apr 24 '20 at 07:56
  • which java version are you using? if using < 8, then the code I put won't work (you could use the code AdMac put in his response for Java 7). If using >= 8, please update the question with your new code, and the exact point that raises the error – PBarri Apr 24 '20 at 08:01
  • I checked with my java version on Windows. It should be version 8. I'm thinking that if my VsCode goes wrong with it. – Woden Apr 24 '20 at 08:21
  • https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html Here is the documentation that I didn't see anything like your code. Did I miss something? – Woden Apr 24 '20 at 08:37
  • 1
    I'm creating the comparators using Java 8 lambdas. You can check a good article about that in this link: https://www.baeldung.com/java-8-sort-lambda – PBarri Apr 24 '20 at 08:58
1

Your NimPlayer needs to implement Comparable interface:

public class NimPlayer implements Comparable<NimPlayer> {

    @Override
    public int compareTo(NimPlayer other) {
        if (other.getWinRatio() == this.getWinRatio()) {
            return this.getUserName().compareTo(other.getUserName());
        } else if (other.getWinRatio() > this.getWinRatio()) {
            return 1;
        }
        return -1;
    }

    public static NimPlayer[] getPlayer() {
        Arrays.sort(playerList);
        return playerList;
    }
}

Also, you need to fix the size of your playerList, which is set to 2 static NimPlayer[] playerList = new NimPlayer[2];, and then you try to add up to 10 players :)

And when adding players to the list you'd better compared counter to actual playerList.length;

Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
  • Thanks for pointing out the problem. I never thought about the counter and the length of the playerList. In this problem, after sorting and creating the `getPlayerList()`, it doesn't have to be returned? And, how should I call these two methods here to `Nimsys`? @Alex – Woden Apr 24 '20 at 08:08
  • 1
    Sorry, I did not notice that the sorted arrays was not returned - fixed that and updated name of the getter for the list. So your `Nimsys` code could remain untouched - method `compareTo` is called under the hood of `Arrays.sort` and when you call `getPlayer`, you'll get sorted array – Nowhere Man Apr 24 '20 at 14:28