Let's say I have this class below:
class Product{
String name;
...
}
And then I make a list of products, List<Product> products. Let's say that there are 3 object with names=("A-Product-12", "A-Product-2", "A-Product-1").
I tried several ways, but it's always results to this order=("A-Product-1", "A-Product-12", "A-Product-2"), but I want it to be in this order=("A-Product-1", "A-Product-2", "A-Product-12")
I've tried using products.sort(), Arrays.sort(), stream().sorted() and using several comparators including Comparator.naturalOrder (which works against numeric only, but not with complex alphanumeric)
You need to write your own custom Comparator that will compare only number from this String (or implement compareTo method in Product class). It can look like this:
List<String> strings = Arrays.asList("A-Product-12", "A-Product-2", "A-Product-1");
Collections.sort(strings, new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
int n1 = Integer.parseInt(s1.split("-")[2]); // Get numeric part of first string
int n2 = Integer.parseInt(s2.split("-")[2]); // Get numeric part of second string
return Integer.compare(n1, n2); // Compare the numeric parts of the strings
}
});
System.out.println(strings); // Output: [A-Product-1, A-Product-2, A-Product-12]
Implementing the Comparable interface with the Product class allows us to sort Product objects directly.
public class Product implements Comparable<Product>
We then define how we want this class to be sorted by overriding the compareTo method. This is done in the Product class.
This is a clean way of implementing the sorting of products (a collection of Product objects). If we want to change the way they are sorted, we know exactly where to go to do this, and the changes will be implemented anywhere we have sorted Product objects. For example, let's say we want to add products starting with the letter B ("B-Product-3") and we want the products to be sorted first by this letter and then by the number at the end. We could do this with the following compareTo method in the Product class:
#Override
public int compareTo(Product product) {
int thisProductNumber = Integer.parseInt(name.split("-")[2]);
int compareToProductNumber = Integer.parseInt(product.name.split("-")[2]);
String thisBaseProductName = name.split("-")[0] + "-" + name.split("-")[1];
String compareToBaseProductName = product.name.split("-")[0] + "-" + product.name.split("-")[1];
return thisBaseProductName.compareTo(compareToBaseProductName) == 0 ?
thisProductNumber - compareToProductNumber : thisBaseProductName.compareTo(compareToBaseProductName);
}
Sorting is done with one line: Collections.sort(products);
If we have products with names=("A-Product-12", "A-Product-2", "B-Product-3", "A-Product-1")
Sorting them gives us ("A-Product-1", "A-Product-2", "A-Product-12", "B-Product-3")
Better to create a custom Comparator as #Pekinek mentioned.
Here's another version:
public class NumberComparator implements Comparator<Product> {
#Override
public int compare(Product p1, Product p2) {
String p1str = p1.getName().replaceAll("[^0-9]", "");
String p2str = p2.getName().replaceAll("[^0-9]", "");
int p1number = Integer.parseInt(p1str);
int p2number = Integer.parseInt(p2str);
return Integer.compare(p1number, p2number);
}
}
In main class you can do
Collections.sort(list,new NumberComparator());
The reason for your result is you were going to sort strings. when going to sort this string list, read left to right by getting each product name and ordered. when consider A-Product-12 and A-Product-2, In 'A-Product-12', it has '1' before the '2'. so that it give priority to 12 than 2 in 'A-Product-2'. If you want to get your expected result you can use your list with map or you can apply logic to sort using last characters those are petitioned after the last character '-'.
Related
I have a collection of strings in an array like this:
ArrayList<String> collection = new ArrayList<>();
That stores:
collection: ["(,0,D=1", "(,1,D=2", "),2,D=2", "),3,D=1", "(,4,D=1", "(,5,D=2", "),6,D=2", "),7,D=1"]
I have a lot of d=1 and d=2, as you can see. How do I organize this from 1 first to 2? I tried to use a for loop but the list can contain an infinite number of d=x's. Can you help me organize?
Also, please help me so I don't change the ORDER of any numbers. Example:
collection: ["(,0,D=1", "),3,D=1", "(,4,D=1", "),7,D=1", "(,1,D=2", "),2,D=2", "(,5,D=2", "),6,D=2"]
So like, every parentheses will be aligned.
I should note that collection[0] = "(,0,D=1"
You should use a class for the items, not a string, e.g. Class Item {char c; int i; int depth;} and ArrayList. Then you can easily sort the list with a custom Comparator.
You can implement your own Comparator to do the sorting. A Comparator is a sorting algorithms that you define for your application which written in programming language. Give Collections.sort() a Comparator basically you teach Java how you want to sort the list. And it will sort the list for you.
This implementation is based on the following assumptions:
The comparison will only take effect on the first D=x pattern, subsequent will be ignored.
Element is sorted in ascending order base on x.
Elements do not have D=x will be placed at the back
class DeeEqualComparator implements Comparator<String> {
private static final String REGEX = "D=([0-9])+";
#Override
public int compare(String s1, String s2) {
// find a D=x pattern from the element
Matcher s1Matcher = Pattern.compile(REGEX).matcher(s1);
Matcher s2Matcher = Pattern.compile(REGEX).matcher(s2);
boolean s1Match = s1Matcher.find();
boolean s2Match = s2Matcher.find();
if (s1Match && s2Match) {
// if match is found on s1 and s2, return their integer comparison result
Integer i1 = Integer.parseInt(s1Matcher.group(1));
Integer i2 = Integer.parseInt(s2Matcher.group(1));
return i1.compareTo(i2);
} else if (s1Match) {
// if only s1 found a match
return -1;
} else if (s2Match) {
// if only s2 found a match
return 1;
} else {
// if no match is found on both, return their string comparison result
return s1.compareTo(s2);
}
}
Test run
public static void main(String[] args) {
String[] array = {
// provided example
"(,0,D=1", "(,1,D=2", "),2,D=2", "),3,D=1", "(,4,D=1", "(,5,D=2", "),6,D=2", "),7,D=1"
// extra test case
, "exception-5", "exception-0", "D=68" };
List<String> list = Arrays.asList(array);
Collections.sort(list, new DeeEqualComparator());
System.out.print(list);
}
output
[(,0,D=1, ),3,D=1, (,4,D=1, ),7,D=1, (,1,D=2, ),2,D=2, (,5,D=2, ),6,D=2, D=68, exception-0, exception-5]
I have a program that calculates the correlation value between a currency and a stock value. I'm adding the "pairs" (currency name + ": " + Correlation Value) in to an ArrayList and if i print out the arraylist this is my output:
SDG: 0.6672481089755959
RON: 0.7950474904606127
MKD: 0.788195252851783
MXN: 0.8429550156320716
CAD: 0.7777753208834005
ZAR: 0.8254509631193871
I'm trying to think of a smart way to sort them by correlation value, from biggest to smallest, but can't think of a good way of doing this. Any ideas?
You could have three approaches:
Use Collections.sort with a custom comparator, which splits the string by : (and trims it) and then returns the .compareTo value of the numeric part of your string.
Create a new object maybe call it CurrencyCorrelation which has 2 properties (currencyName and correlation maybe?). The class will implement the Comparable interface and override the toString() method to yield the correlation as you'd like (currencyName + ": " + String.valueOf(correlation)). You would then call Collections.sort(...) without the need of specifying the comparator, as per option 1.
As per #Sasha Salauyou's recommendation, you could also declare the class, as per option 2 and then use Java 8 lamba expressions to define the comaparator, without the need of having your class extend the Comparable interface. This would look something like so: list.sort((e1, e2) -> e1.getCorrelation().compareTo(e2.getCorrelation()))
The second option would probably be better, with the first option requiring less changes.
You could store them in a TreeMap which is already sorted. From Docs:
The map is sorted according to the natural ordering of its keys, or by a Comparator provided at map creation time, depending on which constructor is used.
Assuming your elements are strings and that the correlation values are doubles, you can simply write your own comparator and sort:
Collections.sort(al, new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
double d1 = Double.parseDouble(s1.substring(s1.indexOf(": ") + 2));
double d2 = Double.parseDouble(s2.substring(s2.indexOf(": ") + 2));
return Double.compare(d1, d2);
}
});
/** Immutable class holding currency-correlation pair */
public static class CurrencyCor {
public final String currency;
public final Double correlation;
private CurrencyCor(String cur, Double cor) {
if (cur == null || cor == null)
throw new NullPointerException("Null argument(s)");
currency = cur;
correlation = cor;
}
#Override
public String toString() {
return String.format("%s: %s", currency, correlation)
}
}
// ...
List<CurrencyCor> list = new ArrayList<>();
list.add(new CurrencyCor("SDG", 0.6672481089755959));
list.add(new CurrencyCor("ZAR", 0.8254509631193871));
// ... add remaining pairs
list.sort((e1, e2) -> e1.correlation.compareTo(e2.correlation)); // sort by correlation value
list.forEach(System.out::println); // print out sorted pairs
I need a Collection that sorts the element, but does not removes the duplicates.
I have gone for a TreeSet, since TreeSet actually adds the values to a backed TreeMap:
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
And the TreeMap removes the duplicates using the Comparators compare logic
I have written a Comparator that returns 1 instead of 0 in case of equal elements. Hence in the case of equal elements the TreeSet with this Comparator will not overwrite the duplicate and will just sort it.
I have tested it for simple String objects, but I need a Set of Custom objects.
public static void main(String[] args)
{
List<String> strList = Arrays.asList( new String[]{"d","b","c","z","s","b","d","a"} );
Set<String> strSet = new TreeSet<String>(new StringComparator());
strSet.addAll(strList);
System.out.println(strSet);
}
class StringComparator implements Comparator<String>
{
#Override
public int compare(String s1, String s2)
{
if(s1.compareTo(s2) == 0){
return 1;
}
else{
return s1.compareTo(s2);
}
}
}
Is this approach fine or is there a better way to achieve this?
EDIT
Actually I am having a ArrayList of the following class:
class Fund
{
String fundCode;
BigDecimal fundValue;
.....
public boolean equals(Object obj) {
// uses fundCode for equality
}
}
I need all the fundCode with highest fundValue
You can use a PriorityQueue.
PriorityQueue<Integer> pQueue = new PriorityQueue<Integer>();
PriorityQueue(): Creates a PriorityQueue with the default initial capacity (11) that orders its elements according to their natural ordering.
This is a link to doc: https://docs.oracle.com/javase/8/docs/api/java/util/PriorityQueue.html
I need all the fundCode with highest fundValue
If that's the only reason why you want to sort I would recommend not to sort at all. Sorting comes mostly with a complexity of O(n log(n)). Finding the maximum has only a complexity of O(n) and is implemented in a simple iteration over your list:
List<Fund> maxFunds = new ArrayList<Fund>();
int max = 0;
for (Fund fund : funds) {
if (fund.getFundValue() > max) {
maxFunds.clear();
max = fund.getFundValue();
}
if (fund.getFundValue() == max) {
maxFunds.add(fund);
}
}
You can avoid that code by using a third level library like Guava. See: How to get max() element from List in Guava
you can sort a List using Collections.sort.
given your Fund:
List<Fund> sortMe = new ArrayList(...);
Collections.sort(sortMe, new Comparator<Fund>() {
#Override
public int compare(Fund left, Fund right) {
return left.fundValue.compareTo(right.fundValue);
}
});
// sortMe is now sorted
In case of TreeSet either Comparator or Comparable is used to compare and store objects . Equals are not called and that is why it does not recognize the duplicate one
Instead of the TreeSet we can use List and implement the Comparable interface.
public class Fund implements Comparable<Fund> {
String fundCode;
int fundValue;
public Fund(String fundCode, int fundValue) {
super();
this.fundCode = fundCode;
this.fundValue = fundValue;
}
public String getFundCode() {
return fundCode;
}
public void setFundCode(String fundCode) {
this.fundCode = fundCode;
}
public int getFundValue() {
return fundValue;
}
public void setFundValue(int fundValue) {
this.fundValue = fundValue;
}
public int compareTo(Fund compareFund) {
int compare = ((Fund) compareFund).getFundValue();
return compare - this.fundValue;
}
public static void main(String args[]){
List<Fund> funds = new ArrayList<Fund>();
Fund fund1 = new Fund("a",100);
Fund fund2 = new Fund("b",20);
Fund fund3 = new Fund("c",70);
Fund fund4 = new Fund("a",100);
funds.add(fund1);
funds.add(fund2);
funds.add(fund3);
funds.add(fund4);
Collections.sort(funds);
for(Fund fund : funds){
System.out.println("Fund code: " + fund.getFundCode() + " Fund value : " + fund.getFundValue());
}
}
}
Add the elements to the arraylist and then sort the elements using utility Collections.sort,. then implement comparable and write your own compareTo method according to your key.
wont remove duplicates as well, can be sorted also:
List<Integer> list = new ArrayList<>();
Collections.sort(list,new Comparator<Integer>()
{
#Override
public int compare(Objectleft, Object right) {
**your logic**
return '';
}
}
)
;
I found a way to get TreeSet to store duplicate keys.
The problem originated when I wrote some code in python using SortedContainers. I have a spatial index of objects where I want to find all objects between a start/end longitude.
The longitudes could be duplicates but I still need the ability to efficiently add/remove specific objects from the index. Unfortunately I could not find the Java equivalent of the Python SortedKeyList that separates the sort key from the type being stored.
To illustrate this consider that we have a large list of retail purchases and we want to get all purchases where the cost is in a specific range.
// We are using TreeSet as a SortedList
TreeSet _index = new TreeSet<PriceBase>()
// populate the index with the purchases.
// Note that 2 of these have the same cost
_index.add(new Purchase("candy", 1.03));
Purchase _bananas = new Purchase("bananas", 1.45);
_index.add(new Purchase(_bananas);
_index.add(new Purchase("celery", 1.45));
_index.add(new Purchase("chicken", 4.99));
// Range scan. This iterator should return "candy", "bananas", "celery"
NavigableSet<PriceBase> _iterator = _index.subset(
new PriceKey(0.99), new PriceKey(3.99));
// we can also remove specific items from the list and
// it finds the specific object even through the sort
// key is the same
_index.remove(_bananas);
There are 3 classes created for the list
PriceBase: Base class that returns the sort key (the price).
Purchase: subclass that contains transaction data.
PriceKey: subclass used for the range search.
When I initially implemented this with TreeSet it worked except in the case where the prices are the same. The trick is to define the compareTo() so that it is polymorphic:
If we are comparing Purchase to PriceKey then only compare the price.
If we are comparing Purchase to Purchase then compare the price and the name if the prices are the same.
For example here are the compareTo() functions for the PriceBase and Purchase classes.
// in PriceBase
#Override
public int compareTo(PriceBase _other) {
return Double.compare(this.getPrice(), _other.getPrice());
}
// in Purchase
#Override
public int compareTo(PriceBase _other) {
// compare by price
int _compare = super.compareTo(_other);
if(_compare != 0) {
// prices are not equal
return _compare;
}
if(_other instanceof Purchase == false) {
throw new RuntimeException("Right compare must be a Purchase");
}
// compare by item name
Purchase _otherPurchase = (Purchase)_other;
return this.getName().compareTo(_otherChild.getName());
}
This trick allows the TreeSet to sort the purchases by price but still do a real comparison when one needs to be uniquely identified.
In summary I needed an object index to support a range scan where the key is a continuous value like double and add/remove is efficient.
I understand there are many other ways to solve this problem but I wanted to avoid writing my own tree class. My solution seems like a hack and I am surprised that I can't find anything else. if you know of a better way then please comment.
I have a little problem with an assignment I'm working on. Basically, I have a file, with Student ID's and their first name in the following format:
17987 Beth
17950 Clark
17936 Aaron
I put the contents of the file in an array, and I need it sorted by the name, not the ID. If I use Arrays.sort(myArray) it will sort it automatically by ID. I'm having a hard time understanding the Comparator, so if you could explain it step by step, it would be easier for me.
Thank you!
You need to provide a Comparator that will look at the Strings passed to it, and they must have an understanding of what they are looking for. You can do this in a few different ways, but, if you know for certain about the content of the strings, then you can split the Strings based on their space and compare the second value. Alternatively, you could use a regular expression to extract those details.
class SecondWordComparator implements Comparator<String>
{
#Override
public int compare(String s1, String s2)
{
String[] a1 = s1.split(" ");
String[] a2 = s2.split(" ");
// you should ensure that there are actually two elements here
return a1[1].compareTo(a2[1]);
}
}
Use TreeMap, the entries will be sorted by key.
E.g.:
SortedMap<String, Integer> srtdMap = new TreeMap<String, Integer>();
srtdMap.put("Beth", 17987);
srtdMap.put("Aaron", 17936 );
srtdMap.put("Clark", 17950);
//key set always returns the same order by name
for(String name : srtdMap.keySet())
{
int id = srtdMap.get(name);
System.out.println("name = " + name + ", ID is " + id);
}
What you can do is run a for loop that would rearrange all the contents of the Array into a final array. Two for loops would be required. The first for loop will go through the Array and the for loop inside the first will look for the first letter and add it to the final Array/Arraylist .
For a object-oriented approach I would parse the file into a new class, Student, and save those in an Array(List). The class could extend Comparable<Student> There, you can separate the int ID and the String name and have the compareTo(Student student) method return name.compareTo(otherStudent.name). Then you can call the sort method and it will sort it as wanted.
As such:
public class Student implements Comparable<Student> {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public int compareTo(Student student) {
return name.compareTo(student.name);
}
}
I have an unsorted list but I want to sort in a custom way i.e.
item_one_primary.pls
item_one_secondary.pls
item_one_last.pls
item_two_last.pls
item_two_primary.pls
item_two_secondary.pls
item_three_secondary.pls
item_three_last.pls
item_three_primary.pls
Here is my predefined order : primary, secondary, last
Above unordered list once the ordering is applied should look like this :
item_one_primary.pls
item_one_secondary.pls
item_one_last.pls
item_two_primary.pls
item_two_secondary.pls
item_two_last.pls
item_three_primary.pls
item_three_secondary.pls
item_three_last.pls
I tried something with comparator but I end up something like this :
item_one_primary.pls
item_two_primary.pls
item_three_primary.pls
...
Does anyone have an idea how to get this sorted?
Here is some code I've used :
List<String> predefinedOrder;
public MyComparator(String[] predefinedOrder) {
this.predefinedOrder = Arrays.asList(predefinedOrder);
}
#Override
public int compare(String item1, String item2) {
return predefinedOrder.indexOf(item1) - predefinedOrder.indexOf(item2);
}
I didn't include the splits(first split by dot(.) second split by underscore(_) to get the item in pre-ordered list).
You have to use a Comparator that checks first the item number and only if they are equal, check your predefined order.
Try something like this:
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
String[] a1 = s1.split("_");
String[] a2 = s2.split("_");
/* If the primary elements of order are equal the result is
the order of the second elements of order */
if (a1[1].compareTo(a2[1]) == 0) {
return a1[2].compareTo(a2[2]);
/* If they are not equal, we just order by the primary elements */
} else {
return a1[1].compareTo(a2[1]);
}
}
This is just a basic example, some extra error checking would be nice.
A solution using the Google Guava API yields a simple and readable result:
// some values
List<String> list = Lists.newArrayList("item_one_primary", "item_one_secondary", "item_one_last");
// define an explicit ordering that uses the result of a function over the supplied list
Ordering o = Ordering.explicit("primary", "secondary", "last").onResultOf(new Function<String, String>() {
// the function splits a values by '_' and uses the last element (primary, secondary etc.)
public String apply(String input) {
return Lists.newLinkedList(Splitter.on("_").split(input)).getLast();
}
});
// the ordered result
System.out.println("o.sortedCopy(list); = " + o.sortedCopy(list));