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 am having a hard time understanding the right syntax to sort Maps which values aren't simply one type, but can be nested again.
I'll try to come up with a fitting example here:
Let's make a random class for that first:
class NestedFoo{
int valA;
int valB;
String textA;
public NestedFoo(int a, int b, String t){
this.valA = a;
this.valB = b;
this.textA = t;
}
}
Alright, that is our class.
Here comes the list:
HashMap<Integer, ArrayList<NestedFoo>> sortmePlz = new HashMap<>();
Let's create 3 entries to start with, that should show sorting works already.
ArrayList<NestedFoo> l1 = new ArrayList<>();
n1 = new NestedFoo(3,2,"a");
n2 = new NestedFoo(2,2,"a");
n3 = new NestedFoo(1,4,"c");
l1.add(n1);
l1.add(n2);
l1.add(n3);
ArrayList<NestedFoo> l2 = new ArrayList<>();
n1 = new NestedFoo(3,2,"a");
n2 = new NestedFoo(2,2,"a");
n3 = new NestedFoo(2,2,"b");
n4 = new NestedFoo(1,4,"c");
l2.add(n1);
l2.add(n2);
l2.add(n3);
l2.add(n4);
ArrayList<NestedFoo> l3 = new ArrayList<>();
n1 = new NestedFoo(3,2,"a");
n2 = new NestedFoo(2,3,"b");
n3 = new NestedFoo(2,2,"b");
n4 = new NestedFoo(5,4,"c");
l3.add(n1);
l3.add(n2);
l3.add(n3);
l3.add(n4);
Sweet, now put them in our Map.
sortmePlz.put(5,l1);
sortmePlz.put(2,l2);
sortmePlz.put(1,l3);
What I want now, is to sort the Entire Map first by its Keys, so the order should be l3 l2 l1.
Then, I want the lists inside each key to be sorted by the following Order:
intA,intB,text (all ascending)
I have no idea how to do this. Especially not since Java 8 with all those lambdas, I tried to read on the subject but feel overwhelmed by the code there.
Thanks in advance!
I hope the code has no syntatical errors, I made it up on the go
You can use TreeSet instead of regular HashMap and your values will be automatically sorted by key:
Map<Integer, ArrayList<NestedFoo>> sortmePlz = new TreeMap<>();
Second step I'm a little confused.
to be sorted by the following Order: intA,intB,text (all ascending)
I suppose you want to sort the list by comparing first the intA values, then if they are equal compare by intB and so on. If I understand you correctly you can use Comparator with comparing and thenComparing.
sortmePlz.values().forEach(list -> list
.sort(Comparator.comparing(NestedFoo::getValA)
.thenComparing(NestedFoo::getValB)
.thenComparing(NestedFoo::getTextA)));
I'm sure there are way of doing it with lambda but it is not actually required. See answer from Schidu Luca for a lambda like solution.
Keep reading if you want an 'old school solution'.
You cannot sort a map. It does not make sense because there is no notion of order in a map. Now, there are some map objects that store the key in a sorted way (like the TreeMap).
You can order a list. In your case, makes the class NestedFoo comparable (https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html). Then you can invoke the method Collections.sort (https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#sort-java.util.List-) on your lists.
Use TreeMap instead of HashMap, it solves the 1st problem: ordering entries by key.
After getting the needed list from the Map, you can sort the ArrayList by valA, valB, text:
l1.sort(
Comparator.comparing(NestedFoo::getValA).thenComparing(NestedFoo::getValB).thenComparing(NestedFoo::getTextA)
);
And change your NestedFoo class definition like this:
class NestedFoo {
int valA;
int valB;
String textA;
public NestedFoo(int a, int b, String t) {
this.valA = a;
this.valB = b;
this.textA = t;
}
public int getValA() {
return valA;
}
public void setValA(int valA) {
this.valA = valA;
}
public int getValB() {
return valB;
}
public void setValB(int valB) {
this.valB = valB;
}
public String getTextA() {
return textA;
}
public void setTextA(String textA) {
this.textA = textA;
}
}
When using treemap for sorting keep in mind that treemap uses compareTo instead of equals for sorting and to find duplicity. compareTo should be incosistent with equals and hashcode when implemented for any object which will be used as key. You can look for a detailed example on this link https://codingninjaonline.com/2017/09/29/unexpected-results-for-treemap-with-inconsistent-compareto-and-equals/
This question already has answers here:
Storing arrays in Set and avoiding duplicates
(6 answers)
Closed 9 years ago.
I have set of string array and i want to remove duplicate elements from this...
String[] arr1 = {"a1","b1"};
String[] arr2 = {"a2","b2"};
Set<String[]> mySet = new HashSet<String[]>();
mySet.add(arr1);
mySet.add(arr2);
mySet.add(new String[] {"a1","b1"});
System.out.print(mySet.size());
Currently mySet looks like this:
[{"a1","b1"},{"a2","b2"},{"a1","b1"}]
But I want like this:
[{"a1","b1"},{"a2","b2"}]
I know some ways...
Every time I need to run inner loop and check whether its duplicate or not.
Can I override the set's behavior? (hashcode or equals)? ( i do not know how....)
Do I need to change data structure for this? (linkedhashset or list or any other suitable data structure for this?)
Arrays inherit from Object and don't override the hashCode and equals methods. A HashSet uses a Map implementation, which in turn, uses hashCode and equals to avoid duplicate elements.
You can use a TreeSet with a custom Comparator that compares the String arrays for equality.
Set<String[]> mySet = new TreeSet<>(new Comparator<String[]>() {
#Override
public int compare(String[] o1, String[] o2) {
return Arrays.equals(o1, o2)? 0 : Arrays.hashCode(o1) - Arrays.hashCode(o2);
}
});
Note that this will only neglect duplicate arrays with the same corresponding elements. If the order of elements is different, it won't be considered as a duplicate.
If you want to be able to discard unordered duplicates, for e.g., {a1, b1} and {b1, a1}, use this:
#Override
public int compare(String[] o1, String[] o2) {
int comparedHash = o1.hashCode() - o2.hashCode();
if(o1.length != o2.length) return comparedHash;
List<String> list = Arrays.asList(o1);
for(String s : o2) {
if(!list.contains(s)) return comparedHash;
}
return 0;
}
The array hashcode is independent of the contents of the array (it inherits the Object hashcode, which uses the array's reference).
However, List would do what you want. It uses a hashcode based on the elements in the List . From Java Docs:
int hashCode = 1;
for (E e : list)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
Example:
List<String> list1 = Arrays.asList("a1","b1");
List<String> list2 = Arrays.asList("a2","b2");
Set<List<String>> mySet = new HashSet<List<String>>();
mySet.add(list1);
mySet.add(list2);
mySet.add(Arrays.asList("a1","b1")); // duplicate won't be added
System.out.print(mySet.size()); // size = 2
Arrays uses identity-based Object.hashCode() implementation and there is no easy way to check if they are equal. If it all you still want to go ahead with your problem I would suggest you to use TreeSet with Comparator
Though not fail proof approach, but you should be able to build fine tuned solution out of my example,
public static void main(String[] args) {
String[] arr1 = {"a1","b1"};
String[] arr2 = {"a2","b2"};
Set<String[]> mySet = new TreeSet<String[]>(new ArrayComparator());
mySet.add(arr1);
mySet.add(arr2);
mySet.add(new String[] {"a1","b1"});
System.out.println(mySet.size());
for(String[] aa: mySet){
System.out.println(aa[0]+" , "+aa[1]);
}
}
}
class ArrayComparator implements Comparator {
#Override
public int compare(Object o1, Object o2) {
String[] ar1 =(String[]) o1;
String[] ar2 =(String[]) o2;
if(ar1.length!=ar2.length){
return -1;
}
for(int count=0;count<ar1.length;count++){
if(!ar1[count].equals(ar2[count])){
return -1;
}
}
return 0;
}
Why not use a List implementation? The list.equals will compare elements in each list and determine equality.
List<String> arr1 = new ArrayList<String>();
arr1.add("a1");
arr1.add("b1");
List<String> arr2 = new ArrayList<String>();
arr2.add("a2");
arr2.add("b2");
Set<List<String>> mySet = new HashSet<List<String>>();
mySet.add(arr1);
mySet.add(arr2);
List<String> arr3 = new ArrayList<String>();
arr3.add("a1");
arr3.add("b1");
mySet.add(arr3);
System.out.print(mySet.size());
You suggest overriding equals and hashcode methods. HashSet is backed by a hashmap that uses the hashcode function as its key. So actually you need to override hashcode to represent your equals criteria.
One problem with this. I believe String and therefore String [] are declared as final, so you can't extend them :(
instead of taking array of string you can create a class Like this..
public class String1 implements Comparable<String1>{
String str1;
String str2;
public String1(String a, String b) {
str1 = a;
str2 = b;
}
public String getStr1() {
return str1;
}
}
public String getStr2() {
return str2;
}
#Override
public String toString() {
return "String1 [str1=" + str1 + ", str2=" + str2
+ "]";
}
#Override
public int compareTo(String1 o) {
if(str1.contentEquals(o.getStr1()) && str2.contentEquals(o.getStr2())) return 0 ;
return 1;
}
}
And after that insteed of string you can take this one class object.
replace HashSet with TreeSet. Like this .
String1 arr1 =new String1("a1","b1");
String1 arr2 =new String1("a2","b2");
Set<String1> mySet = new TreeSet<String1>();
mySet.add(arr1);
mySet.add(arr2);
mySet.add(new String1("a1","b1"));
System.out.print(mySet.size());
System.out.println(mySet.toString());
So this will sort as well this will check for duplicate also.
try to this code.............
import java.util.HashSet;
import java.util.Set;
public class setDemo {
static Set<String[]> mySet = new HashSet<String[]>();
static Set tempSet = new HashSet();
public static void main(String[] args) {
String[] arr1 = {"a1","b1"};
String[] arr2 = {"a2","b2"};
addObject(arr1);
addObject(arr2);
addObject(new String[] {"a1","b1"});
System.out.print(mySet.size());
// System.out.println(tempSet);
}
public static void addObject(String[] o){
StringBuffer sb = new StringBuffer();
for(Object obj:o){
sb.append(obj.toString());
}
if(!tempSet.contains(sb.toString())){
tempSet.add(sb.toString());
mySet.add(o);
}
}
}
Try something like this...
public static void main(String... args) {
String[] arr1 = {"a1","b1"};
String[] arr2 = {"a2","b2"};
Set<String[]> mySet = new HashSet<String[]>();
mySet.add(arr1);
mySet.add(arr2);
String str[] =new String[] {"a1","b1"};
long t1 = System.nanoTime();
boolean b =checkContains(str,mySet);
long t2=System.nanoTime();
long t = t2-t1;
System.out.println("time taken : " + t );
System.out.println(b);
if(!b)
{
mySet.add(str);
}
}
public static boolean checkContains(String[] str, Set mySet)
{
Iterator it = mySet.iterator();
while(it.hasNext())
{
String[] arr = (String[])it.next();
if(arr[0].equals(str[0]) && arr[1].equals(str[1]) )
{
return true;
}
}
return false;
}
OP :
time taken : 184306
true
Here instead of keeping Set you can use Set<SomeClass> and the override the hash and equals method for the class SomeClass so it will solve your problem.
Assuming I have
final Iterable<String> unsorted = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
What can I do to transform this unsorted list into this:
[PREFZ, PREFA, BAR, FOO, PREFOO, ZOO]
(a list which begin with known values that must appears first (here "PREFA" and "PREFZ") and the rest is alphabetically sorted)
I think there are some usefull classes in guava that can make the job (Ordering, Predicates...), but I have not yet found a solution...
I would keep separate lists.
One for known values and unknown values. And sort them separately, when you need them in a one list you can just concatenate them.
knownUnsorted.addAll(unsorted.size - 1, unknonwUnsorted);
I suggest filling List with your values and using Collections.sort(...).
Something like
Collections.sort(myList, new FunkyComparator());
using this:
class FunkyComparator implements Comparator {
private static Map<String,Integer> orderedExceptions =
new HashMap<String,Integer>(){{
put("PREFZ", Integer.valueOf(1));
put("PREFA", Integer.valueOf(2));
}};
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
Integer i1 = orderedExceptions.get(s1);
Integer i2 = orderedExceptions.get(s2);
if (i1 != null && i2 != null) {
return i1 - i2;
}
if (i1 != null) {
return -1;
}
if (i2 != null) {
return +1;
}
return s1.compareTo(s2);
}
}
Note: This is not the most efficient solution. It is just a simple, straightforward solution that gets the job done.
I would first use Collections.sort(list) to sort the list.
Then, I would remove the known items, and add them to the front.
String special = "PREFA";
if (list.remove(special)
list.add(0, special);
Or, if you have a list of array of these values you need in the front you could do:
String[] knownValues = {};
for (String s: knownValues) {
if (list.remove(s))
list.add(0, s);
}
Since I'm a fan of the guava lib, I wanted to find a solution using it. I don't know if it's efficient, neither if you find it as simple as others solution, but it's here:
final Iterable<String> all = asList("FOO", "BAR", "PREFA", "ZOO", "PREFOO", "PREFZ");
final List<String> mustAppearFirst = asList("PREFZ", "PREFA");
final Iterable<String> sorted =
concat(
Ordering.explicit(mustAppearFirst).sortedCopy(filter(all, in(mustAppearFirst))),
Ordering.<String>natural().sortedCopy(filter(all, not(in(mustAppearFirst)))));
You specifically mentioned guava; along with Sylvain M's answer, here's another way (more as an academic exercise and demonstration of guava's flexibility than anything else)
// List is not efficient here; for large problems, something like SkipList
// is more suitable
private static final List<String> KNOWN_INDEXES = asList("PREFZ", "PREFA");
private static final Function<Object, Integer> POSITION_IN_KNOWN_INDEXES
= new Function<Object, Integer>() {
public Integer apply(Object in) {
int index = KNOWN_INDEXES.indexOf(in);
return index == -1 ? null : index;
}
};
...
List<String> values = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
Collections.sort(values,
Ordering.natural().nullsLast().onResultOf(POSITION_IN_KNOWN_INDEXES).compound(Ordering.natural())
);
So, in other words, sort on natural order of the Integer returned by List.indexOf(), then break ties with natural order of the object itself.
Messy, perhaps, but fun.
I would also use Collections.sort(list) but I think I would use a Comparator and within the comparator you could define your own rules, e.g.
class MyComparator implements Comparator<String> {
public int compare(String o1, String o2) {
// Now you can define the behaviour for your sorting.
// For example your special cases should always come first,
// but if it is not a special case then just use the normal string comparison.
if (o1.equals(SPECIAL_CASE)) {
// Do something special
}
// etc.
return o1.compareTo(o2);
}
}
Then sort by doing:
Collections.sort(list, new MyComparator());
I have an array of filenames and need to sort that array by the extensions of the filename. Is there an easy way to do this?
Arrays.sort(filenames, new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
// the +1 is to avoid including the '.' in the extension and to avoid exceptions
// EDIT:
// We first need to make sure that either both files or neither file
// has an extension (otherwise we'll end up comparing the extension of one
// to the start of the other, or else throwing an exception)
final int s1Dot = s1.lastIndexOf('.');
final int s2Dot = s2.lastIndexOf('.');
if ((s1Dot == -1) == (s2Dot == -1)) { // both or neither
s1 = s1.substring(s1Dot + 1);
s2 = s2.substring(s2Dot + 1);
return s1.compareTo(s2);
} else if (s1Dot == -1) { // only s2 has an extension, so s1 goes first
return -1;
} else { // only s1 has an extension, so s1 goes second
return 1;
}
}
});
For completeness: java.util.Arrays and java.util.Comparator.
If I remember correctly, the Arrays.sort(...) takes a Comparator<> that it will use to do the sorting. You can provide an implementation of it that looks at the extension part of the string.
You can implement a custom Comparator of Strings. Make it sort them by the substring after the last index of '.'. Then pass in the comparator and your array into
Arrays.sort(stringArray, yourComparator);
// An implementation of the compare method
public int compare(String o1, String o2) {
return o1.substring(o1.lastIndexOf('.')).compareTo(o2.substring(o2.lastIndexOf('.'));
}
Comparators are often hard to get exactly right, and the comparison key has to be generated for every comparison which for most sorting algorithms mean O(n log n). Another approach is to create (key, value) pairs for each item you need to sort, put them in a TreeMap, and then ask for the values as these are sorted according to the key.
For instance
import java.util.Arrays;
import java.util.TreeMap;
public class Bar {
public static void main(String[] args) {
TreeMap<String, String> m2 = new TreeMap<String, String>();
for (String string : Arrays.asList(new String[] { "#3", "#2", "#1" })) {
String key = string.substring(string.length() - 1);
String value = string;
m2.put(key, value);
}
System.out.println(m2.values());
}
}
prints out
[#1, #2, #3]
You should easily be able to adapt the key calculation to your problem.
This only calculates the key once per entry, hence O(n) - (but the sort is still O(n log n)). If the key calculation is expensive or n is large this might be quite measurable.
Create a Comparator and compare the string extensions. Take a look at the following
http://java.sun.com/j2se/1.4.2/docs/api/java/util/Comparator.html
Then pass in your List of strings to Arrays.sort(List, Comparator)
Create your own Comparator that treats the strings as filenames and compares them based on the extensions. Then use Arrays.sort with the Comparator argument.
String DELIMETER = File.separator + ".";
List<String> orginalList = new CopyOnWriteArrayList<>(Arrays.asList(listOfFileNames));
Set<String> setOfuniqueExtension = new TreeSet<>();
for (String item : listOfFileNames) {
if (item.contains(".")) {
String[] split = item.split(DELIMETER);
String temp = "." + split[split.length - 1];
setOfuniqueExtension.add(temp);
}
}
List<String> finalListOfAllFiles = new LinkedList<>();
setOfuniqueExtension.stream().forEach((s1) -> {
for (int i = 0; i < orginalList.size(); i++) {
if (orginalList.get(i).contains(s1)) {
finalListOfAllFiles.add(orginalList.get(i));
orginalList.remove(orginalList.get(i));
i--;
}
}
});
orginalList.stream().filter((s1) -> (!finalListOfAllFiles.contains(s1))).forEach((s1) -> {
finalListOfAllFiles.add(s1);
});
return finalListOfAllFiles;
If you just want to group the files by their extension and do not care about the actual alphabetical order, you can use this:
I think the simplest thing you can do that also works when the filenname does not have a "." is to just reverse the names and compare them.
Arrays.sort(ary, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
String r1 = new StringBuffer(o1).reverse().toString();
String r2 = new StringBuffer(o2).reverse().toString();
return r1.compareTo(r2);
}
});
Its a shame that java's string does not even have a reverse().