I'm trying to use a lambda expression to sort an ArrayList of Objects (i.e. rows imported from a CSV file), WITHOUT classes (like "Employee", or "Person", or "Animal", etc.), so there are no Getters/Setters for the Objects at all. So I'm essentially BufferedReading in rows from a CSV file, assigning them to objects, and adding those objects to an ArrayList. I want to then sort by the last value/index in each object. For example...
These are my CSV rows:
Mark,10,0,34
Tom,2,0,19
Billy,2,0,7
...and I would like to sort them by the far right numbers in ascending order, so my sorted ArrayList would look like:
Billy,2,0,7
Tom,2,0,19
Mark,10,0,34
Is this even possible using a lambda expression? What I have for code so far looks like this:
// Import the csv file and assign each row to a list of objects
List<Object> olReturn = null;
try (BufferedReader br = new BufferedReader(new FileReader("stats_log.csv"))) {
olReturn = br.lines().sorted((x,y) -> x.LASTVALUEHERE() - y.LASTVALUEHERE()).collect(Collectors.toList());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (Object obIndObject : olReturn) {
System.out.println(obIndObject);
}
I know how to do it if the object classes had getters for the values I want to sort by, but that doesn't apply here, so just seeing if there's a potential solution, or if it's impossible?
tl;dr
List < String > sorted =
List.of(
"Mark,10,0,34" ,
"Tom,2,7,19" ,
"Billy,2,0,7"
)
.stream()
.sorted( ( String s1 , String s2 ) -> Integer.valueOf( s1.split( "," )[ 3 ] ).compareTo( Integer.valueOf( s2.split( "," )[ 3 ] ) ) )
.toList();
Streams
You asked:
use a lambda expression to sort an ArrayList of Objects (i.e. rows imported from a CSV file),
In other words, you want to sort a list of String objects by the last part delimited with a COMMA (,).
For each string, we need to do a String#split call, returning an array of parts. We expect exactly four parts given your example data. So take the last part. Arrays in Java annoyingly use an index, that is, zero-based counting. So the 4th item is an index of 3, syntactically [3].
This code is not efficient, as sorting involves many repetitive calls to compareTo. For each call we are performing the splitting all over again. Not efficient, but it works.
List < String > inputs =
new ArrayList <>(
List.of(
"Mark,10,0,34" ,
"Tom,2,7,19" ,
"Billy,2,0,7"
)
);
List < String > sorted =
inputs
.stream()
.sorted( ( String s1 , String s2 ) -> Integer.valueOf( s1.split( "," )[ 3 ] ).compareTo( Integer.valueOf( s2.split( "," )[ 3 ] ) ) )
.toList(); // Before Java 16: .collect( Collectors.toList() );
System.out.println( "sorted = " + sorted );
See this code run live at IdeOne.com.
sorted = [Billy,2,0,7, Tom,2,7,19, Mark,10,0,34]
Records
Your Question really does not make sense in that it explicitly avoids using classes. Java is an object-oriented language. Classes are its bread-and-butter, its raison d'ĂȘtre.
If you are simply reluctant to define a class because of the requisite boilerplate, I have good news for you. Java 16 brings the new records feature.
Records are a brief way to declare a class whose main purpose is to communicate data transparently and immutably. You simply declare the types and names of all the class member fields. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.
So an entire class can be written on one short line.
record Person ( String name , int departmentId , int projectId , int employeeId ) {}
Populate a collection of Person objects based on your input data.
List < Person > persons = new ArrayList <>( inputs.size() );
for ( String input : inputs )
{
String[] parts = input.split( "," );
Person p = new Person( parts[ 0 ] , Integer.valueOf( parts[ 1 ] ) , Integer.valueOf( parts[ 2 ] ) , Integer.valueOf( parts[ 3 ] ) );
persons.add( p );
}
Sort that list by the last member field, employeeId.
Collections.sort(
persons ,
new Comparator < Person >()
{
#Override
public int compare ( Person p1 , Person p2 )
{
return Integer.compare( p1.employeeId() , p2.employeeId() );
}
}
);
Here is complete example.
package work.basil.demo;
import java.util.*;
import java.lang.*;
public class RecordDemo
{
public static void main ( String[] args )
{
List < String > inputs =
new ArrayList <>(
List.of(
"Mark,10,0,34" ,
"Tom,2,7,19" ,
"Billy,2,0,7"
)
);
record Person(String name , int departmentId , int projectId , int employeeId)
{
}
List < Person > persons = new ArrayList <>( inputs.size() );
for ( String input : inputs )
{
String[] parts = input.split( "," );
Person p = new Person( parts[ 0 ] , Integer.valueOf( parts[ 1 ] ) , Integer.valueOf( parts[ 2 ] ) , Integer.valueOf( parts[ 3 ] ) );
persons.add( p );
}
System.out.println( "persons = " + persons );
Collections.sort(
persons ,
new Comparator < Person >()
{
#Override
public int compare ( Person p1 , Person p2 )
{
return Integer.compare( p1.employeeId() , p2.employeeId() );
}
}
);
System.out.println( "persons = " + persons );
}
}
When run.
persons = [Person[name=Mark, departmentId=10, projectId=0, employeeId=34], Person[name=Tom, departmentId=2, projectId=7, employeeId=19], Person[name=Billy, departmentId=2, projectId=0, employeeId=7]]
persons = [Person[name=Billy, departmentId=2, projectId=0, employeeId=7], Person[name=Tom, departmentId=2, projectId=7, employeeId=19], Person[name=Mark, departmentId=10, projectId=0, employeeId=34]]
In that code above, notice another nice thing about records: You can declare records locally, within a method, as well as nested in a class or in a separate class (.java file). And while the Java team was at it, they now allow local enum definitions, and local interface definitions too. All that is in Java 16 and later.
I updated based on the OP's comment
public static void main (String[] args) {
List<String> list =
Arrays.asList("Mark,10,0,34", "Wyle,2,0,19", "Tom,2,0,19", "Billy,2,0,7");
Comparator<String> cmp = new Comparator<String>() {
public int compare (String o1, String o2) {
int diff = (Integer.valueOf(o1.substring(o1.lastIndexOf(',')+1))).compareTo(Integer.valueOf(o2.substring(o2.lastIndexOf(',')+1)));
return (diff == 0)
? o1.compareTo(o2)
: diff;
}
};
Collections.sort(list, cmp);
System.out.println(list);
}
This outputs:
[Billy,2,0,7, Tom,2,0,19, Wyle,2,0,19, Mark,10,0,34]
This example will compare based on the last set of numbers OR alphabetically cases where the last set of numbers are the same.
Ok, it's not using a lambda expression, however I was able to modify the solution found here to fit my needs. Take a look:
This is the custom Comparator class:
private class MyObjectComparator<MyObject> implements Comparator<MyObject> {
/**
* {#inheritDoc}
*/
#Override
public int compare(MyObject o1, MyObject o2) {
return Integer.parseInt(o1.toString().split(",")[3]) - Integer.parseInt(o2.toString().split(",")[3]);
}
}
As you can see, I parse the integer of the 3rd indexed value after splitting the string. In other words, I take each string "object", split them, then reference the value I want to reference. Then, I just call this class using:
// Import the csv file and assign each row to a list of objects
List<Object> olReturn = null;
try (BufferedReader br = new BufferedReader(new FileReader("stats_log.csv"))) {
olReturn = br.lines().collect(Collectors.toList());
// call to comparator here
Collections.sort(olReturn, new MyObjectComparator());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
and this gets me what I need. Thanks for working that out with me!
br.lines() return Stream of String, so underlying object would be String, therefore you just need to do some String indexing to get what you need
olReturn = br.lines().sorted(Comparator.comparing(x -> Long.valueOf(x.substring(x.lastIndexOf(",") + 1)))).collect(Collectors.toList());
You should avoid calculating split and Integer.parseInt in Comparator as it is inefficient.
Try this.
List<String> list =
Arrays.asList("Mark,10,0,34", "Wyle,2,0,19", "Tom,2,0,19", "Billy,2,0,7");
List<String> result = list.stream()
.map(line -> new Object() {
int sortKey = Integer.parseInt(line.split(",")[3]);
String self = line;
})
.sorted(Comparator.comparingInt(object -> object.sortKey))
.map(object -> object.self)
.collect(Collectors.toList());
System.out.println(result);
output:
[Billy,2,0,7, Wyle,2,0,19, Tom,2,0,19, Mark,10,0,34]
Is there any Utility function in Java to convert List<Object[]> to List<Object>
No, there's no method that does this directly for you. You can write a nested for loop or use the flatMap of the stream API as follows:
List<Object> flat = objArrs.stream()
.flatMap(Stream::of)
.collect(Collectors.toList());
You can easily write one yourself:
public static<T> List<T> append (List<T[]> input) {
List<T> res = new ArrayList<T>();
for(T[] subarr : input) {
if(subarr != null) {
int n = subarr.length;
for(int i = 0; i < n; i++) {
res.add(subarr[i]);
}
}
}
return res;
}
The function appends the different arrays and null arrays are ignored, null elements are however not. Thus if the input is [null,[a,b],[null,null],[c,null,d],null]. The output is [a,b,null,null,c,null,d].
No.
Why don't you just write the function yourself? It would probably be faster than asking this question and waiting for an answer.
In Java 8 you can do it with Streams :
List<Object[]> list = ...
List<Object> l = list.stream()
.flatMap(arr -> Stream.of(arr))
.collect(Collectors.toList());
As others have already said, there is no utility and creating one yourself wouldn't be hard, for example using old school for loops:
public List<Object> flatten( List<Object[]> source )
{
// if ( source == null ) return null; // which check you use it up to you
List<Object> result = new ArrayList<Object>();
if ( source == null ) return result; // Personally I like this check
for ( Object[] array: source )
{
if ( array == null ) continue; // skip nulls
for ( Object object: array )
{
result.add(object);
}
}
return result;
}
I have two lists ( not java lists, you can say two columns)
For example
**List 1** **Lists 2**
milan hafil
dingo iga
iga dingo
elpha binga
hafil mike
meat dingo
milan
elpha
meat
iga
neeta.peeta
I'd like a method that returns how many elements are same. For this example it should be
3 and it should return me similar values of both list and different values too.
Should I use hashmap if yes then what method to get my result?
Please help
P.S: It is not a school assignment :) So if you just guide me it will be enough
EDIT
Here are two versions. One using ArrayList and other using HashSet
Compare them and create your own version from this, until you get what you need.
This should be enough to cover the:
P.S: It is not a school assignment :) So if you just guide me it will be enough
part of your question.
continuing with the original answer:
You may use a java.util.Collection and/or java.util.ArrayList for that.
The retainAll method does the following:
Retains only the elements in this collection that are contained in the specified collection
see this sample:
import java.util.Collection;
import java.util.ArrayList;
import java.util.Arrays;
public class Repeated {
public static void main( String [] args ) {
Collection listOne = new ArrayList(Arrays.asList("milan","dingo", "elpha", "hafil", "meat", "iga", "neeta.peeta"));
Collection listTwo = new ArrayList(Arrays.asList("hafil", "iga", "binga", "mike", "dingo"));
listOne.retainAll( listTwo );
System.out.println( listOne );
}
}
EDIT
For the second part ( similar values ) you may use the removeAll method:
Removes all of this collection's elements that are also contained in the specified collection.
This second version gives you also the similar values and handles repeated ( by discarding them).
This time the Collection could be a Set instead of a List ( the difference is, the Set doesn't allow repeated values )
import java.util.Collection;
import java.util.HashSet;
import java.util.Arrays;
class Repeated {
public static void main( String [] args ) {
Collection<String> listOne = Arrays.asList("milan","iga",
"dingo","iga",
"elpha","iga",
"hafil","iga",
"meat","iga",
"neeta.peeta","iga");
Collection<String> listTwo = Arrays.asList("hafil",
"iga",
"binga",
"mike",
"dingo","dingo","dingo");
Collection<String> similar = new HashSet<String>( listOne );
Collection<String> different = new HashSet<String>();
different.addAll( listOne );
different.addAll( listTwo );
similar.retainAll( listTwo );
different.removeAll( similar );
System.out.printf("One:%s%nTwo:%s%nSimilar:%s%nDifferent:%s%n", listOne, listTwo, similar, different);
}
}
Output:
$ java Repeated
One:[milan, iga, dingo, iga, elpha, iga, hafil, iga, meat, iga, neeta.peeta, iga]
Two:[hafil, iga, binga, mike, dingo, dingo, dingo]
Similar:[dingo, iga, hafil]
Different:[mike, binga, milan, meat, elpha, neeta.peeta]
If it doesn't do exactly what you need, it gives you a good start so you can handle from here.
Question for the reader: How would you include all the repeated values?
You can try intersection() and subtract() methods from CollectionUtils.
intersection() method gives you a collection containing common elements and the subtract() method gives you all the uncommon ones.
They should also take care of similar elements
If you are looking for a handy way to test the equality of two collections, you can use org.apache.commons.collections.CollectionUtils.isEqualCollection, which compares two collections regardless of the ordering.
Are these really lists (ordered, with duplicates), or are they sets (unordered, no duplicates)?
Because if it's the latter, then you can use, say, a java.util.HashSet<E> and do this in expected linear time using the convenient retainAll.
List<String> list1 = Arrays.asList(
"milan", "milan", "iga", "dingo", "milan"
);
List<String> list2 = Arrays.asList(
"hafil", "milan", "dingo", "meat"
);
// intersection as set
Set<String> intersect = new HashSet<String>(list1);
intersect.retainAll(list2);
System.out.println(intersect.size()); // prints "2"
System.out.println(intersect); // prints "[milan, dingo]"
// intersection/union as list
List<String> intersectList = new ArrayList<String>();
intersectList.addAll(list1);
intersectList.addAll(list2);
intersectList.retainAll(intersect);
System.out.println(intersectList);
// prints "[milan, milan, dingo, milan, milan, dingo]"
// original lists are structurally unmodified
System.out.println(list1); // prints "[milan, milan, iga, dingo, milan]"
System.out.println(list2); // prints "[hafil, milan, dingo, meat]"
Of all the approaches, I find using org.apache.commons.collections.CollectionUtils#isEqualCollection is the best approach. Here are the reasons -
I don't have to declare any additional list/set myself
I am not mutating the input lists
It's very efficient. It checks the equality in O(N) complexity.
If it's not possible to have apache.commons.collections as a dependency, I would recommend to implement the algorithm it follows to check equality of the list because of it's efficiency.
Using java 8 removeIf
public int getSimilarItems(){
List<String> one = Arrays.asList("milan", "dingo", "elpha", "hafil", "meat", "iga", "neeta.peeta");
List<String> two = new ArrayList<>(Arrays.asList("hafil", "iga", "binga", "mike", "dingo")); //Cannot remove directly from array backed collection
int initial = two.size();
two.removeIf(one::contains);
return initial - two.size();
}
Simple solution :-
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "d", "c"));
List<String> list2 = new ArrayList<String>(Arrays.asList("b", "f", "c"));
list.retainAll(list2);
list2.removeAll(list);
System.out.println("similiar " + list);
System.out.println("different " + list2);
Output :-
similiar [b, c]
different [f]
Assuming hash1 and hash2
List< String > sames = whatever
List< String > diffs = whatever
int count = 0;
for( String key : hash1.keySet() )
{
if( hash2.containsKey( key ) )
{
sames.add( key );
}
else
{
diffs.add( key );
}
}
//sames.size() contains the number of similar elements.
I found a very basic example of List comparison at List Compare
This example verifies the size first and then checks the availability of the particular element of one list in another.
public static boolean compareList(List ls1, List ls2){
return ls1.containsAll(ls2) && ls1.size() == ls2.size() ? true :false;
}
public static void main(String[] args) {
ArrayList<String> one = new ArrayList<String>();
one.add("one");
one.add("two");
one.add("six");
ArrayList<String> two = new ArrayList<String>();
two.add("one");
two.add("six");
two.add("two");
System.out.println("Output1 :: " + compareList(one, two));
two.add("ten");
System.out.println("Output2 :: " + compareList(one, two));
}
protected <T> boolean equals(List<T> list1, List<T> list2) {
if (list1 == list2) {
return true;
}
if (list1 == null || list2 == null || list1.size() != list2.size()) {
return false;
}
// to prevent wrong results on {a,a,a} and {a,b,c}
// iterate over list1 and then list2
return list1.stream()
.filter(val -> !list2.contains(val))
.collect(Collectors.toList())
.isEmpty() &&
list2.stream()
.filter(val -> !list1.contains(val))
.collect(Collectors.toList())
.isEmpty();
}