This question already has answers here:
How to sort an ArrayList in Java [duplicate]
(3 answers)
Closed 4 years ago.
i am trying to sort an list array that has Strings that contain Integers and letters but when i do it the regular way i get some wierd output:
relevant code:
List<String> words = new ArrayList<>();
words.add("9 hello");
words.add("98 food");
words.add("105 cat");
words.add("2514 human");
words.add("3 pencil");
words.sort(Comparator.reverseOrder());
i am expecting :
"2514 human"
"105 cat"
"98 food"
"9 hello"
"3 pencil"
but i am getting something like this:
"98 food"
"9 hello"
"3 pencil"
"2514 human"
"105 cat"
any suggestions?
I think you should create a class to represent the elements in the list.
For instance:
public class WordCount {
public static final Comparator<WordCount> BY_COUNT;
private static final Pattern PATTERN
= Pattern.compile("\\s*([0-9]+)\\s+(.*)");
public final int count;
public final String word;
public static WordCount parse(String s) {
Matcher matcher = PATTERN.matcher(s);
if (!matcher.matches()) {
throw new IllegalArgumentException("Syntax error: " + s);
}
return new WordCount(
Integer.parseInt(matcher.group(1)), matcher.group(2));
}
public WordCount(int count, String word) {
this.count = count;
this.word = word;
}
#Override
public String toString() {
return count + " " + word;
}
static {
BY_COUNT = (WordCount o1, WordCount o2) -> {
int r = Integer.compare(o1.count, o2.count);
if (r == 0) {
r = o1.word.compareTo(o2.word);
}
return r;
};
}
}
Your code would then become:
List<WordCount> words = new ArrayList<>();
words.add(WordCount.parse("9 hello"));
words.add(WordCount.parse("98 food"));
words.add(WordCount.parse("105 cat"));
words.add(WordCount.parse("2514 human"));
words.add(WordCount.parse("3 pencil"));
words.sort(WordCount.BY_COUNT.reversed());
words.forEach((wc) -> {
System.out.println(wc);
});
With the following result:
2514 human
105 cat
98 food
9 hello
3 pencil
Use comporator to solve your problem like this
Add this code inside you class
private static Comparator<String> ORDER_MYLIST = new Comparator<String>() {
public int compare(String d, String d1) {
int first = Integer.parseInt(d.split(" ")[0]);//since you have space
int second = Integer.parseInt(d1.split(" ")[0]);
return second - first;//change the order if you want
}
};
Add this code in your calling function
Collections.sort(words, ORDER_MYLIST);
Output:
[2514 human, 105 cat, 98 food, 9 hello, 3 pencil]
Will be as you expect.
This link will give you better understanding on how comporator works
https://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html
You need to have a custom comparator to satisfy your requirement.
Java 8 solution:
List<String> words = new ArrayList<>();
words.add("9 hello");
words.add("98 food");
words.add("105 cat");
words.add("2514 human");
words.add("3 pencil");
// Sort the existing list
words.sort(Comparator.comparing(s -> Integer.parseInt(s.split(" ")[0]), Comparator.reverseOrder()));
// To create new sorted list
List<String> sortedWords = words.stream()
.sorted(Comparator.comparing(s -> Integer.parseInt(s.split(" ")[0]), Comparator.reverseOrder()))
.collect(Collectors.toList());
What you've got is exactly lexicographical order because not numbers are compared but their digits alphabetically.
If you want other behavior then you should consider implementing your own Comparator in which you'll parse the numbers.
Related
I wan to create a method for which we give text(String) as an input argument.
The method will return the number of words without repetition.
For example: "cat, dog, Cat, Bird, monkey"
return value:4
How can I compare each Collections item with each other?
What I already have:
public class WordsCounter {
public static void main(String[] args) {
uniqueWordsCounter("cat, dog, Cat, Bird, monkey");
}
public static void uniqueWordsCounter(String text) {
String processedText = text.toLowerCase().replaceAll(",", "");
String[] words = processedText.split("\\s");
List<String> wordsList = Arrays.asList(words);
}
}
One way is to use the distinct() operation from the stream API:
import java.util.*;
public class WordsCounter {
public static void main(String[] args) {
uniqueWordsCounter("cat, dog, Cat, Bird, monkey");
}
public static void uniqueWordsCounter(String text) {
String[] words = text.toLowerCase().split(",\\s*");
List<String> wordsList = Arrays.asList(words);
System.out.println(wordsList);
System.out.println("Count of distinct elements: "
+ wordsList.stream().distinct().count());
}
}
Example run:
$ java Demo.java
[cat, dog, cat, bird, monkey]
Count of distinct elements: 4
Note splitting on comma followed by optional whitespace instead of your replacing commas and then splitting, to help simplify things.
You can use a set to keep track of all the unique elements present in your string after you separate it using the delimiter ","
In your example, you are keeping cat and Cat as same ( ignoring case ) . Thus, you can use this logic.
public class WordsCounter {
public static void main(String[] args) {
int count = uniqueWordsCounter("cat,dog,Cat,Bird,monkey");
System.out.println(count);
}
public static int uniqueWordsCounter(String text) {
String str[] = text.split(",");
Set<String> set = new HashSet<>() ;
for( String temp : str)
{
if ( !set.contains(temp.toLowerCase()))
{
set.add(temp);
}
}
return set.size();
}
}
and the output is
4
I got:
a list of lists of TextInfo objects. Each TextInfo object contains a piece of text and a toString override method to return the text. By the Y value of a TextInfo we can conclude what TextInfo's are on the same line (custom problem)
I want:
a list of strings. Each string is the result of concatenation of all elements of one sublist. And I want to make use of streams as much as possible.
So far:
List<String> allLinesByYCoordinate = groupAllTextLineByLineBasedOnY(allTextInfosOnPage);
public static List<String> groupAllTextLineByLineBasedOnY(List<TextInfo> allTextInfo) {
Map<Object, List<TextInfo>> groupedtextInfosPerLine = allTextInfo.stream().
collect(Collectors.groupingBy(x -> x.getY()));
List<String> allLines = new ArrayList<>();
for (Map.Entry<Object, List<TextInfo>> groupedtextInfos: groupedtextInfosPerLine.entrySet()) {
String temp = "";
for (TextInfo textInfo: groupedtextInfos.getValue()) {
temp += textInfo;
}
allLines.add(temp);
}
return allLines;
}
You might agree that the method groupAllTextLineByLineBasedOnY looks a bit too oldschool. I 'm trying to execute a concatenation on each of the lists of TextInfo's in the big list, resulting in a list of strings where each string used to be a list of TextInfo's
I'm hoping to find a concise stream() solution
Let's refactor a little at a time.
First, you should never build a string in a loop, as it can be very inefficient; use StringBuilder instead. However, we can instead stream and collect into a string. Also notice here that we using Map.values() instead of calling getValue() inside the loop.
public static List<String> groupAllTextLineByLineBasedOnY(List<TextInfo> allTextInfo) {
Map<Object, List<TextInfo>> groupedtextInfosPerLine = allTextInfo.stream()
.collect(Collectors.groupingBy(x -> x.getY()));
List<String> allLines = new ArrayList<>();
for (List<TextInfo> groupedtextInfos: groupedtextInfosPerLine.values()) {
allLines.add(groupedtextInfos.stream().map(Object::toString).collect(Collectors.joining()));
}
return allLines;
}
In the next refactoring, we can get rid of the intermediate list allLines and instead stream the textInfos and collect them into a List:
public static List<String> groupAllTextLineByLineBasedOnY(List<TextInfo> allTextInfo) {
Map<Object, List<TextInfo>> groupedtextInfosPerLine = allTextInfo.stream()
.collect(Collectors.groupingBy(x -> x.getY()));
return groupedtextInfosPerLine.values().stream()
.map(textInfos -> textInfos.stream().map(Object::toString).collect(Collectors.joining()))
.toList();
}
Finally, we can get rid of the groupedtextInfosPerLine variable:
public static List<String> groupAllTextLineByLineBasedOnY(List<TextInfo> allTextInfo) {
return allTextInfo.stream()
.collect(Collectors.groupingBy(TextInfo::getY)).values().stream()
.map(textInfos -> textInfos.stream().map(Object::toString).collect(Collectors.joining()))
.toList();
}
I think your looking for a solution like this
import java.util.Collection;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Collectors;
class Scratch {
public static void main(String[] args) {
// Setup some example data
List<TextInfo> textInfos = List.of(
new TextInfo("line 3 a", 3),
new TextInfo("line 1 a", 1),
new TextInfo("line 2 a", 2),
new TextInfo("line 1 b", 1),
new TextInfo("line 3 b", 3),
new TextInfo("line 1 c", 1)
);
// This is the actual answer
Collection<String> allLines = textInfos.stream()
.collect(Collectors.toMap(
TextInfo::getY, // line number as key
TextInfo::toString, // convert TextInfo to String
(a, b) -> a + b, // Merge TextInfo on the same line
TreeMap::new)) // Ensure in order
.values();
// You would return allLines from the method
System.out.println(allLines);
}
static class TextInfo {
String text;
int y;
public TextInfo(String text, int y) {
this.text = text;
this.y = y;
}
public int getY() { return y; }
#Override
public String toString() { return text; }
}
}
If you run the code you print
[line 1 aline 1 bline 1 c, line 2 a, line 3 aline 3 b]
I'm developing a Java Application that reads a lot of strings data likes this:
1 cat (first read)
2 dog
3 fish
4 dog
5 fish
6 dog
7 dog
8 cat
9 horse
...(last read)
I need a way to keep all couple [string, occurrences] in order from last read to first read.
string occurrences
horse 1 (first print)
cat 2
dog 4
fish 2 (last print)
Actually i use two list:
1) List<string> input; where i add all data
In my example:
input.add("cat");
input.add("dog");
input.add("fish");
...
2)List<string> possibilities; where I insert the strings once in this way:
if(possibilities.contains("cat")){
possibilities.remove("cat");
}
possibilities.add("cat");
In this way I've got a sorted list where all possibilities.
I use it like that:
int occurrence;
for(String possible:possibilities){
occurrence = Collections.frequency(input, possible);
System.out.println(possible + " " + occurrence);
}
That trick works good but it's too slow(i've got millions of input)... any help?
(English isn’t my first language, so please excuse any mistakes.)
Use a Map<String, Integer>, as #radoslaw pointed, to keep the insertion sorting use LinkedHashMap and not a TreeMap as described here:
LinkedHashMap keeps the keys in the order they were inserted, while a TreeMap is kept sorted via a Comparator or the natural Comparable ordering of the elements.
Imagine you have all the strings in some array, call it listOfAllStrings, iterate over this array and use the string as key in your map, if it does not exists, put in the map, if it exists, sum 1 to actual result...
Map<String, Integer> results = new LinkedHashMap<String, Integer>();
for (String s : listOfAllStrings) {
if (results.get(s) != null) {
results.put(s, results.get(s) + 1);
} else {
results.put(s, 1);
}
}
Make use of a TreeMap, which will keep ordering on the keys as specified by the compare of your MyStringComparator class handling MyString class which wraps String adding insertion indexes, like this:
// this better be immutable
class MyString {
private MyString() {}
public static MyString valueOf(String s, Long l) { ... }
private String string;
private Long index;
public hashcode(){ return string.hashcode(); }
public boolean equals() { // return rely on string.equals() }
}
class MyStringComparator implements Comparator<MyString> {
public int compare(MyString s1, MyString s2) {
return -s1.getIndex().compareTo(s2.gtIndex());
}
}
Pass the comparator while constructing the map:
Map<MyString,Integer> map = new TreeMap<>(new MyStringComparator());
Then, while parsing your input, do
Long counter = 0;
while (...) {
MyString item = MyString.valueOf(readString, counter++);
if (map.contains(item)) {
map.put(map.get(item)+1);
} else {
map.put(item,1);
}
}
There will be a lot of instantiation because of the immutable class, and the comparator will not be consistent with equals, but it should work.
Disclaimer: this is untested code just to show what I'd do, I'll come back and recheck it when I get my hands on a compiler.
Here is the complete solution for your problem,
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DataDto implements Comparable<DataDto>{
public int count = 0;
public String string;
public long lastSeenTime;
public DataDto(String string) {
this.string = string;
this.lastSeenTime = System.currentTimeMillis();
}
public boolean equals(Object object) {
if(object != null && object instanceof DataDto) {
DataDto temp = (DataDto) object;
if(temp.string != null && temp.string.equals(this.string)) {
return true;
}
}
return false;
}
public int hashcode() {
return string.hashCode();
}
public int compareTo(DataDto o) {
if(o != null) {
return o.lastSeenTime < this.lastSeenTime ? -1 : 1;
}
return 0;
}
public String toString() {
return this.string + " : " + this.count;
}
public static final void main(String[] args) {
String[] listOfAllStrings = {"horse", "cat", "dog", "fish", "cat", "fish", "dog", "cat", "horse", "fish"};
Map<String, DataDto> results = new HashMap<String, DataDto>();
for (String s : listOfAllStrings) {
DataDto dataDto = results.get(s);
if(dataDto != null) {
dataDto.count = dataDto.count + 1;
dataDto.lastSeenTime = System.nanoTime();
} else {
dataDto = new DataDto(s);
results.put(s, dataDto);
}
}
List<DataDto> finalResults = new ArrayList<DataDto>(results.values());
System.out.println(finalResults);
Collections.sort(finalResults);
System.out.println(finalResults);
}
}
Ans
[horse : 1, cat : 2, fish : 2, dog : 1]
[fish : 2, horse : 1, cat : 2, dog : 1]
I think this solution will be suitable for your requirement.
If you know that your data is not going to exceed your memory capacity when you read it all into memory, then the solution is simple - using a LinkedList or a and a LinkedHashMap.
For example, if you use a Linked list:
LinkedList<String> input = new LinkedList();
You then proceed to use input.add() as you did originally. But when the input list is full, you basically use Jordi Castilla's solution - but put the entries in the linked list in reverse order. To do that, you do:
Iterator<String> iter = list.descendingIterator();
LinkedHashMap<String,Integer> map = new LinkedHashMap<>();
while (iter.hasNext()) {
String s = iter.next();
if ( map.containsKey(s)) {
map.put( s, map.get(s) + 1);
} else {
map.put(s, 1);
}
}
Now, the only real difference between his solution and mine is that I'm using list.descendingIterator() which is a method in LinkedList that gives you the entries in backwards order, from "horse" to "cat".
The LinkedHashMap will keep the proper order - whatever was entered first will be printed first, and because we entered things in reverse order, then whatever was read last will be printed first. So if you print your map the result will be:
{horse=1, cat=2, dog=4, fish=2}
If you have a very long file, and you can't load the entire list of strings into memory, you had better keep just the map of frequencies. In this case, in order to keep the order of entry, we'll use an object such as this:
private static class Entry implements Comparable<Entry> {
private static long nextOrder = Long.MIN_VALUE;
private String str;
private int frequency = 1;
private long order = nextOrder++;
public Entry(String str) {
this.str = str;
}
public String getString() {
return str;
}
public int getFrequency() {
return frequency;
}
public void updateEntry() {
frequency++;
order = nextOrder++;
}
#Override
public int compareTo(Entry e) {
if ( order > e.order )
return -1;
if ( order < e.order )
return 1;
return 0;
}
#Override
public String toString() {
return String.format( "%s: %d", str, frequency );
}
}
The trick here is that every time you update the entry (add one to the frequency), it also updates the order. But the compareTo() method orders Entry objects from high order (updated/inserted later) to low order (updated/inserted earlier).
Now you can use a simple HashMap<String,Entry> to store the information as you read it (I'm assuming you are reading from some sort of scanner):
Map<String,Entry> m = new HashMap<>();
while ( scanner.hasNextLine() ) {
String str = scanner.nextLine();
Entry entry = m.get(str);
if ( entry == null ) {
entry = new Entry(str);
m.put(str, entry);
} else {
entry.updateEntry();
}
}
Scanner.close();
Now you can sort the values of the entries:
List<Entry> orderedList = new ArrayList<Entry>(m.values());
m = null;
Collections.sort(orderedList);
Running System.out.println(orderedList) will give you:
[horse: 1, cat: 2, dog: 4, fish: 2]
In principle, you could use a TreeMap whose keys contained the "order" stuff, rather than a plain HashMap like this followed by sorting, but I prefer not having either mutable keys in a map, nor changing the keys constantly. Here we are only changing the values as we fill the map, and each key is inserted into the map only once.
What you could do:
Reverse the order of the list using
Collections.reverse(input). This runs in linear time - O(n);
Create a Set from the input list. A Set garantees uniqueness.
To preserve insertion order, you'll need a LinkedHashSet;
Iterate over this set, just as you did above.
Code:
/* I don't know what logic you use to create the input list,
* so I'm using your input example. */
List<String> input = Arrays.asList("cat", "dog", "fish", "dog",
"fish", "dog", "dog", "cat", "horse");
/* by the way, this changes the input list!
* Copy it in case you need to preserve the original input. */
Collections.reverse(input);
Set<String> possibilities = new LinkedHashSet<String>(strings);
for (String s : possibilities) {
System.out.println(s + " " + Collections.frequency(strings, s));
}
Output:
horse 1
cat 2
dog 4
fish 2
I am currently trying to order an ArrayList of String elements (called myarraylist) according to the numbers found after the word "Item". Here is a snippet of what is in myarraylist:
Item 1
Item 2
Item 3
...
Item 9
Item 10
I would like to order myarraylist to the following order:
Item 10
Item 9
Item 8
...
Item 3
Item 2
Item 1
So far, this is what I have tried:
Collections.sort(myarraylist, String.CASE_INSENSITIVE_ORDER);
Collections.reverse(myarraylist);
However, this orders the myarraylist in the following order
Item 9
Item 8
...
Item 3
Item 2
Item 10
Item 1
As you can see, Item 10 is out of place, because it reads "10" by its first number, "1". Does anyone know how to appropriately order myarraylist in reverse numerical order?
That's because the default String Comparator uses lexicographical order -- i.e. character by character, as in a dictionary. Since "1" comes before "2", any String starting with "1" will precede any other starting with "2".
You should use a custom comparator to implement Natural Sorting. A good example would be Alphanum, from Dave Koelle, which you would use like this:
Collections.sort(myarraylist, new AlphanumComparator());
I use this simple class to order my Strings:
public abstract class StringOrderer {
public static ArrayList<String> order(ArrayList<String> items, boolean ascending) {
Collections.sort(items, new StringComparator());
// reverse the order
if(!ascending) Collections.reverse(items);
return items;
}
class StringComparator implements Comparator<String> {
#Override
public int compare(String s1, String s2) {
// use the users default locale to sort the strings
Collator c = Collator.getInstance(Locale.getDefault());
return c.compare(s1, s2);
}
}
}
The basic idea is that i have a custom Comparator that uses the default Locale.
Since you are using String in the Array list it alway checks characters. Better you try using Integer. It may works for you.
As String can not be extended, it is better to change ArrayList<String> to ArrayList<ClassWithStringAttribute> then implement Comparable in ClassWithStringAttribute when you need custom comparison. For your particular case and illustration, the following class should work, though not a nice approach.
myarraylist= StringSorter.getSortedList(myarraylist);
Will give you a sorted list
public class StringSorter implements Comparable<StringSorter>{
String tempString;
public StringSorter(String data) {
this.tempString = data;
}
public static List<String> getSortedList(List<String> unsortedList){
List<StringSorter> tempList=new ArrayList<StringSorter>();
for (String current : unsortedList) {
tempList.add(new StringSorter(current));
}
Collections.sort(tempList);
List<String> sortedString=new ArrayList<String>();
for (StringSorter current : tempList) {
sortedString.add(current.tempString);
}
return sortedString;
}
#Override
public int compareTo(StringSorter other) {
Integer otherInt=Integer.parseInt(other.tempString.replaceFirst("Item ", ""));
Integer thisInt=Integer.parseInt(this.tempString.replaceFirst("Item ", ""));
if(otherInt>thisInt){
return -1;
}else if(otherInt<thisInt){
return 1;
}else{
return 0;
}
}
}
How about this custom Comparator,
public class StringNumSuffixComparator implements Comparator<String>{
#Override
public int compare(String o1, String o2) {
String str1 = o1;
String str2 = o2;
Integer num1 = Integer.parseInt(str1.replaceAll("\\D+",""));
Integer num2 = Integer.parseInt(str2.replaceAll("\\D+",""));
return num2.compareTo(num1);
}
}
To sort them with Collections.sort(),
Collections.sort(items, new StringNumSuffixComparator());
This question already has answers here:
Java: How to sort List of Lists by their size?
(3 answers)
Closed 9 months ago.
I have List of Lists(list rows contain list column). I want to sort my rows in ListRZS by length(.size()).
[[123, 189, 277], [11], [145, 211, 299], [156, 222, 310], [167, 233, 255], [189, 266], [200, 277], [211, 288], [245, 299], [233], [244]]
Shoul be:
[[11], [233], [244], [189, 266],[200, 277], [211, 288], [245, 299], [123, 189, 277], [145, 211, 299], [156, 222, 310], [167, 233, 255]]
Question: How to write working comporator/comparable for this situation.
public class Start {
public static void main(String[] args) {
System.out.println("Start reading from Xls");
ReaderXls1Column read = new ReaderXls1Column();
ReaderXls readrzs = new ReaderXls();
List<String> listASU = new ArrayList<String>(read.ReaderXls1Column("Text1obj",0,1)); // Creating Lists
List<List<String>> listRZS = new ArrayList<List<String>>(readrzs.ReadXls("Text1obj",2,12));
System.out.println(listASU);
System.out.println(listRZS);
System.out.println("Reading is over");
System.out.println(listRZS);
WriterXls.main("Text1obj",listRZS);
System.out.println("Writing is over");
}
My attempt without comparator, comparable(of course, not working right)
int k=0;
while ( k<listRZS.size()){
for (int j=0;j<10;j++) {
List<String> tmplist = new ArrayList<String>();
if (listRZS.get(k).size()>listRZS.get(k+1).size()) {Collections.copy(listRZS.get(k),tmplist); listRZS.remove(k); listRZS.add(k+1,tmplist);}
else {};
}
Collections.sort(listRZS.get(k)); // sort each row, solution above
k++;
}
A comparator just needs to implement one method (compare), that takes two items, and returns:
0 if they are equal
positive value if the second is less than the first
negative value if the second is greater than the first.
When sorting integer values, a typical pattern to accomplish this is to reduce each object received in the compare method to an integer, then, if we call them a and b, simply return a - b.
Example (see it running at ideone.com/rcmDbi):
import java.util.*;
class Test {
public static void main(String[] args) {
List<Integer> a = Arrays.asList(1);
List<Integer> b = Arrays.asList(1,2);
List<Integer> c = Arrays.asList(1,2,3);
List<Integer> d = Arrays.asList(1,2,3,4);
List<List<Integer>> test = Arrays.asList(d,b,c,a);
Collections.sort(test, ListSizeComparator.INSTANCE);
for (List<Integer> list : test) {
System.out.println(list.size());
}
}
enum ListSizeComparator implements Comparator<List> {
INSTANCE;
public int compare(List one, List other) {
return one.size() - other.size();
}
}
}
(By the way, I've used an enum for the enum singleton pattern because our Comparator stores no state.)
Try like this:
List<List<String>> myList; // load myList with values
Then do the sorting as follows:
Collections.sort(myList, new Comparator<List<String>>(){
#Override
public int compare(List<String> arg0, List<String> arg1) {
return arg1.size() - arg0.size();
}
});