Stream sorted method with comparator - java

I don't understand why code1 works but code2 doesn't compile. Please explain.
//Code1:
Stream<String> s = Stream.of("AA", "BB");
s.sorted(Comparator.reverseOrder())
.forEach(System.out::print);
//Code2:
Stream<String> s = Stream.of("AA", "BB");
s.sorted(Comparator::reverseOrder)
.forEach(System.out::print);
The difference between the two is code1 uses Comparator.reverseOrder() while code2 uses Comparator::reverseOrder

Because the first example is a factory-method so when you inspect it, you see that you get a comparator back.
But the second one is a method-reference which you could write like this:
Stream<String> s = Stream.of("AA", "BB");
s.sorted(() -> Comparator.reverseOrder()) // no semantic difference!
.forEach(System.out::print);
But it has a whole different meaning because this time you are given Stream#sorted() a Supplier<Comparator<?>> but it just needs a Comparator<?>
Small Sidenote: Don't store streams in variables, use them directly. So i would suggest you just write:
Stream.of("AA", "BB")
.sorted(Comparator.reverseOrder())
.forEach(System.out::print);

The error message from the compiler should tell you that.
sorted() expects a Comparator instance. Comparator.reverseOrder()returns a Comparator instance. So that works fine.
Comparator::reverseOrder is a method reference to the reverseOrder() method of Comparator. So your code basically says: each time you need to compare two strings, pass them as argument to Comparator.reverseOrder to compare them. But that can't possibly work. This method takes nothing as argument, and returns a Comparator. So it doesn't match the signature of a Comparator<String>, which is supposed to take two Strings as argument, and return an integer.
If you had a method such as
class Foo
public static int compareStrings(String s1, String s2) {
...
}
}
Then you could use
sorted((s1, s2) -> Foo.compareStrings(s1, s2))
which you can transform, using a method reference, to
sorted(Foo::compareStrings)
Because compareStrings, just like the unique abstract method of Comparator<String>, takes two Strings as argument and returns an int.

Related

TreeSet not arranging value in ascending order

I am trying to create a TreeSet to sort the strings which are inserted to be in an ascending order. I am using below code for entering values in TreeSet.
TreeSet<String> ts = new TreeSet<String>();
ts.add("#Test0");
ts.add("#Test1");
ts.add("#Test2");
ts.add("#Test3");
ts.add("#Test10");
ts.add("#Test4");
System.out.println("Tree set :: "+ts);
Output:
Tree set :: [#Test0, #Test1, #Test10, #Test2, #Test3, #Test4]
You've used the no-args TreeSet constructor. This means TreeSet will order its elements based on natural order. It's the way the objects compare themselves: It means the things you add must be of a type that implements Comparable<Self>. String does that: The String class is defined to implement Comparable<String>. However, the way strings compare themselves is lexicographically. 10 comes before 2 for the same reason that aa comes before b.
You have two routes available to fix this:
Don't put strings in there but some other object that implements Comparable and does it right. Perhaps a class Thingie {String name; int idx;}.
Pass a Comparator as first and only argument to your TreeSet class. Write code that determines that #Test10 comes before #Test2. Then, TreeSet uses this comparator to determine ordering and won't use the one built into strings.
Specify the Comparator to sort on the number part only. This removes all but the number portion, converts that to an integer and sorts on that.
TreeSet<String> ts = new TreeSet<String>(Comparator.comparing(
s -> Integer.valueOf(s.replace("#Test", ""))));
ts.add("#Test0");
ts.add("#Test1");
ts.add("#Test2");
ts.add("#Test3");
ts.add("#Test10");
ts.add("#Test4");
System.out.println(ts);
prints
[#Test0, #Test1, #Test2, #Test3, #Test4, #Test10]
This works for the shown example. You may need to modify it somewhat for more varied data. But it demonstrates the idea.
#Test10 comes before #Test2 because 1 comes before 2. That's how the default ordering of String works (String implements the interface Comparable to do this sorting).
To solve your issue you need to provide a custom Comparator to the TreeSet, and do the comparison by parsing the integer within the string:
TreeSet<String> ts = new TreeSet<String>(new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
return Integer.parseInt(s1.substring(5)) - Integer.parseInt(s2.substring(5));
}
});
The comparator can be constructed using the static convenience method:
TreeSet<String> ts = new TreeSet<>(Comparator.comparing(s -> Integer.parseInt(s.substring(5))));
As #Jems noted in the comment, strings are sorted lexichographically, so "#Test10" will come before "#Test2". If could however, supply a custom Comparator to define the order you need. E.g., if you know all the strings will have the form of "#Test" followed by a number, you could extract this number and sort accordingly:
TreeSet<String> ts =
new TreeSet<>(Comparator.comparingInt(s -> Integer.parseInt(s.substring(5))));

How i can create String comparator through lambda?

I am trying to sort list of string through my own comparator. I saw the below template for list of developers.
//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());
My code: (this is giving me compile error)
List<String> s = new ArrayList<>();
Collections.sort(s, (String a, String b)-> {
return a.length() > b.length()
});
This code is not compiling. Can someone help me what is wrong with what I am doing?
Arrays.sort is used for sorting Arrays not for Collection objects, use Collections.sort to sort ArrayList
Collections.sort(s,(a,b)->a.length()-b.length());
And corresponding lambda expression is wrong (String a,String b)-> a.length() > b.length() which returns Boolean value. Where Comparator.compare should return int value. And also the type of the parameters can be explicitly declared or it can be inferred from the context.
int compare(T o1, T o2)
You can also use List.sort
s.sort((a,b)->a.length()-b.length());
There are 2 unrelated problems with your code:
[1] There are two ways to write lambdas.
You can either write a single expression serving as the return value:
s.sort((a, b) -> a.length() - b.length());
Or you can involve a code block; but if you do that, return (and semicolons) have to show up:
s.sort((a, b) -> {
return a.length() - b.length();
});
Generally if you can make it work with a single expression, just do that; it's shorter. In your example you're using braces (so, the second form), but you don't have the return keyword nor the required semicolon.
Also, the types of the lambda arguments are optional. Generally if from context it is clear, just omit these: (a, b) -> a.length() - b.length()
[2] a comparator should return 0 if the arguments are equal, a negative number (any negative number, it doesn't matter which one) if a is 'smaller' than b, and a positive number if a is 'larger' than b. You are returning a boolean (you are using a greater than sign; the result of that expression is 'true' or 'false'; you need a number instead.
The solution seems trivial here: use minus instead.
[3] a bonus tip: There are utility methods for comparing based on field: Comparator.comparingInt(String::length) will do what you want in a way that is easier to read and less error prone. Also, instead of Collections.sort(list, comparator) you can just write list.sort(comparator); easier on the eyes, shorter, and more idiomatic.
Let's put it all together:
List<String> s = new ArrayList<>();
s.sort(Comparator.comparingInt(String::length));

Using method reference to remove elements from a List

What's wrong with my code?
I want to remove all the elements starting with A from the List list:
public static void main(String[] args) {
Predicate<String> TTT = "A"::startsWith;
List<String> list = new ArrayList<>();
list.add("Magician");
list.add("Assistant");
System.out.println(list); // [Magician, Assistant]
list.removeIf(TTT);
System.out.println(list); // expected output: [Magician]
}
However, removeIf doesn't remove anything from the list.
"A"::startsWith is a method reference that can be assigned to a Predicate<String>, and when that Predicate<String> is tested against some other String, it would check whether the String "A" starts with that other String, not the other way around.
list.removeIf(TTT) won't remove anything from list, since "A" doesn't start with neither "Magician" nor "Assistant".
You can use a lambda expression instead:
Predicate<String> TTT = s -> s.startsWith("A");
The only way your original "A"::startsWith predicate would remove anything from the list is if the list would contain the String "A" or an empty String.
BiPredicate<String, String> b1 = String::startsWith;
BiPredicate<String, String> b2 = (string, prefix) -> string.startsWith(prefix);
System.out.println(b1.test("chicken", "chick"));
System.out.println(b2.test("chicken", "chick"));
The method reference combines two techniques. **startsWith()**
is an instance method. This means that the first parameter in the lambda is used
as the instance on which to call the method. The second parameter is passed to the
startsWith() method itself. This is example of how method references save a
good bit of typing.

Check data present in list of object array

I have a list of object :
List<Object[]> list = new ArrayList<>();
Object[] object = {"test", "test1", "test2"};
list.add(object);
List contains some data.
I have another string String str = "test";
I am using below code. What are best other ways:
for (Object []object1 : list) {
for (Object obj : object1) {
if (obj.equals("test")) {
System.out.println("true");
}
}
}
How to check this string present in above list with minimum of code.
Java 8 introduced Streams which are powerful, yet code-compact as you demanded. This answer uses more features of Java 8 sucha as Lambdas and Method References.
Here is a one-liner instruction:
boolean containsObject = list.stream().flatMap(Arrays::stream).filter(s->str.equals(s) ).findFirst().isPresent();
Here how it works:
boolean containsObject = list.stream() // Turning the List into a Stream of Arrays
.flatMap(Arrays::stream) // flattening the 2D structure into a single-dimensional stream of Objects (Note: using a Method reference)
.filter(s->str.equals(s)) // Filtering the flat stream to check for equality (Note: using a Lambda expression)
.findFirst() // Demands to find the first Occurence that passed the Filter test
.isPresent(); // Collapse the stream and returns the result of the above demand (Note: the Stream makes no computation until this instruction)
This solution is code-compact, and brings the nice features of Streams such as parallelization and laziness.
If you convert the Object[]s to lists, then you can call their contains(Object). You could either have list be a List<List<Object>>, or you could leave it with Object[] and wrap the Object[]s in a List as-needed.
Example of the "convert as needed":
for(Object[] object1 : list)
if(Arrays.asList(object1).contains("test"))
System.out.println("true");
Personally, I would have list be a List<List>. Whenever you add to it, just wrap your arrays in a list. Assuming arr is an Object[], that means list.add(Arrays.asList(arr));.
Alexander's answer is also correct (I think; I didn't examine it too closely), but I find long strings of stream operators to be less readable. If you disagree with my opinion on that, then use the stream operators.

Java stream toArray() convert to a specific type of array

Maybe this is very simple but I'm actually a noob on Java 8 features and don't know how to accomplish this. I have this simple line that contains the following text:
"Key, Name"
and I want to convert that line into a String array, separating each value by the comma (,), however, I also want to trim every field before returning the final array, so I did the following:
Arrays.stream(line.split(",")).map(String::trim).toArray();
However, this returns an Object[] array rather than a String[] array. Upon further inspection, I can confirm that the contents are actually String instances, but the array itself is of Object elements. Let me illustrate this, this is what the debugger says of the returned object:
Object[]:
0 = (String) "Key"
1 = (String) "Name"
As far as I can tell, the problem is in the return type of the map call, but how can I make it return a String[] array?
Use toArray(size -> new String[size]) or toArray(String[]::new).
String[] strings = Arrays.stream(line.split(",")).map(String::trim).toArray(String[]::new);
This is actually a lambda expression for
.toArray(new IntFunction<String[]>() {
#Override
public String[] apply(int size) {
return new String[size];
}
});
Where you are telling convert the array to a String array of same size.
From the docs
The generator function takes an integer, which is the size of the desired array, and produces an array of the desired size. This can be concisely expressed with an array constructor reference:
Person[] men = people.stream()
.filter(p -> p.getGender() == MALE)
.toArray(Person[]::new);
Type Parameters:
A - the element type of the resulting array
Parameters:
generator - a function which produces a new array of the desired type and the provided length
String[]::new is a function that invokes the new "pseudo-method" for the String[] type just like String::trim is a function that invokes the real trim method of the String type. The value passed to the String::new function by toArray is the size of the collection on the right-hand side of the .toArray() method invocation.
If you replaced String[]::new with n->new String[n] you might be more comfortable with the syntax just like you could replace String::trim with the less cool s->s.trim()

Categories