Understanding functional operator: Lambda - java

I have an an arraylist called Semesters of Semester objects
private ArrayList<Semester> Semesters
I wrote the following code to printout a string using the objects(Semester) in the list(Semesters)
String report = year + "\n";
for (Semester s : Semesters) {
report += s.toString() + "\n";
}
Then I got a tip on netbeans where the entire above code can be written in one line like this:
String report = year + "\n";
report = Semesters.stream().map((s) -> s.toString() + "\n").reduce(report, String::concat);
I am an intermediate java student and I am trying to understand this feature which I find to be very useful but can't quite get my head around it.
Please help me understand how these two pieces of code are the same.

I haven't worked with Java lambdas, but going by the map and reduce\fold operations from the functional programming paradigm, this is how the 2 snippets are equivalent:
map basically applies the particular function to each element in the collection. In this case, that function/method is s.toString() + "\n". This is the same as iterating over the collection and applying the function to each element via a foreach loop.
reduce is a fold operation i.e. it concatenates each element returned by the map operation to the original string report.
So in your original code
String report = year + "\n";
for (Semester s : Semesters) {
report += s.toString() + "\n";
}
You basically operate on the element s, and then concatenate the result of that operation to report. Using a lambda, you first operate on the whole collection in the map phase, and then traverse the collection returned during mapping and concatenate each element to report in the reduce phase.

Here is the equivalent functionality broken down using anonymous classes:
Function<Semester, String> toStrFn = new Function<Semester, String>() {
#Override
public String apply(Semester semester) {
return semester + "\n";
}
};
BinaryOperator<String> reducer = new BinaryOperator<String>() {
#Override
public String apply(String lastResult, String currentValue) {
return lastResult.concat(currentValue);
}
};
String report = year + "\n";
Stream<Semester> semesterStream = Semesters.stream();
Stream<String> stringStream = semesterStream.map(toStrFn);
report = stringStream.reduce(report, reducer);

semesters.stream() will give a Stream<Semester> object for one time usage (stateful like iterator).
.map(...) is given a function interface: an interface with only one method with parameter type Semester.
The lambda exoression will generate an instance of a generated interface containing a method with Semester parameter and String as result type.
Hence map yields a Stream
Map will later iterate over the Semester-s, apply the method and collect the String.
Stream<String>.reduce(String startValue, stepFunction) will let the operation (iterating) start collecting a single String, initializing to startValue, on every step: `result = result.concat(return value of map method).
I hope I have got the explanation right.
System.out.println("Mapping Semester to String...");
Stream<String> sems = Semesters.stream().map((s) -> {
System.out.println("map call " + s);
return s + "\n";
});
System.out.println("Reducing String stream to String...");
String report = sems.reduce(year + "\n", String::concat);
The above shows eager/lazy evaluation. Maybe look into the Builder Pattern, for what happens under the hood. Try out parallel and filter.
String report = year + "\n"
+ semesters.stream()
.filter(...)
.sorted(...)
.map(Semester::toString)
.collect(Collectors.joining("\n"));

Related

String vs StringBuilder

I have a condition like :
public String createId(List<String> list)
{
String id="";
if(list.contains("name"))
id+="TEST VALUE NAME";
if(list.contains("age"))
id+="Test Value AGE";
.
.
. likewise many if condition
return id;
}
As per my understanding we should use StringBuilder in loop condition and String in simple concatenation. So here wanted to ask I should use String or StringBuilder? Kindly suggest
StringBuilder is the best for this scenario because it's mutable. the String is immutable so when you modify the string it creates a new object.
It seems that for the given task it would be better to get rid of the multiple duplicated if statements by defining a list of the keys to match the input list and use Stream API to generate the string id, e.g. Collectors.joining with delimiter or without the delimiter.
Assuming that there is a single rule to create a part of the id: append "Test Value " + key.toUpperCase(), the implementation may look as follows:
final List<String> keys = Arrays.asList(
"name", "age" /* and other needed keys*/
);
public String createId(List<String> list) {
return keys
.stream()
.filter(list::contains)
.map(String::toUpperCase)
.map(str -> "Test Value " + str)
.collect(Collectors.joining("_")); // or Collectors.joining()
}
System.out.println(createId(Arrays.asList("age", "name", "surname")));
// output: Test Value NAME_Test Value AGE
If custom parts should be provided for name, age, etc., a Map of matches should be prepared and used, also it may make sense to convert the input list into Set<String to facilitate look-ups:
final Map<String, String> keys = new LinkedHashMap<>(); {
// fill the map in special order
keys.put("name", "Name Part");
keys.put("age", "Test Age");
/* and other needed keys*/
}
public String createId(List<String> list) {
Set<String> words = new HashSet<>(list);
return keys.keySet()
.stream()
.filter(words::contains) // faster lookup O(1) at the cost of another collection
.map(keys::get)
.collect(Collectors.joining("_")); // or Collectors.joining()
}
System.out.println(createId(Arrays.asList("age", "surname", "name")));
// output: Name Part_Test Age
In general your understanding is correct about when to use String concatenation vs StringBuilder. The Java Language Specification says
To increase the performance of repeated string concatenation, a Java
compiler may use the StringBuffer class or a similar technique to
reduce the number of intermediate String objects that are created by
evaluation of an expression.
For the larger majority of cases you should use whichever method results in better readability and maintainability.

How to display LinkedList<String[]> in Java

there are many posts on how to display the elements of LinkedList<String>, but I can't find something that I understand for LinkedList<String[]>.
The following code shows the output:
LinkedList:[[Ljava.lang.String;#5305068a, [Ljava.lang.String;#1f32e575
whereas I'm looking for the output:
LinkedList:[[Audi SQ5,341], [LandRover Discovery,306]].
and I don't want to overwrite the toString().
Code:
LinkedList<String[]> cars2 = new LinkedList<>();
String[] split_result = new String[2];
split_result = "Audi SQ5,341".split(",");
cars2.add(split_result);
split_result = "LandRover Discovery,306".split(",");
cars2.add(split_result);
// Displaying the size of the list
System.out.println("The size of the linked list is: " + cars2.size());
// Displaying the Strings in the linkedlist ??? (not their addresses)
System.out.println("LinkedList:" + cars2);
cars2.forEach(element -> System.out.println(element));
I would use a Java Stream for that. It has the helpful joining collector:
LinkedList<String[]> cars2 = new LinkedList<>();
String output = cars2
.stream()
.map(Arrays::toString)
.collect(Collectors.joining(", ", "[", "]"));
Your problem is that while default Java collections like LinkedList implement a nice toString to print their memebers, arrays do not. So either you have to iterate over your collection and print the results yourself, as some other answers suggest, or you have to put objects into your LinkedLists that have a nice toString implementation. Those can either be collections themselves List<List<String>>, or you can define a class to contain your data.
class CarInfo {
// these should be encapsulated with getters/setters, I'm being lazy.
public String model;
public String value; // I don't know what this represents, or if it's a string or int
public void toString() { return model + ", " + value; }
}
If you use Lombok, this is very easy with the #Data annotation, which will generate toString and getters/setters for you.
#Data
class CarInfo {
String model;
String value;
}
However, it won't be exactly the same as your desired output. If you want it specifically formatted with the brackets as you described, you'd have to do your own toString anyway.

Loop fusion of Stream in Java-8 (how it works internally)

I'm reading the book 'Java in Action'.
And I saw an example code of Stream in the book.
List<String> names = menu.stream()
.filter(d -> {
System.out.println("filtering" + d.getName());
return d.getCalories() > 300;
})
.map(d -> {
System.out.println("mapping" + d.getName());
return d.getName();
})
.limit(3)
.collect(toList());
When the code is executed, the result is as follows.
filtering __1__.
mapping __1__.
filtering __2__.
mapping __2__.
filtering __3__.
mapping __3__.
That is, because of limit(3), the log message is printed only 3 times!
In this book, this is called in "loop fusion."
But, I don't understand this.
Because, if you know whether an object is filtered, you have to calculate the filtering function. Then, "filtering ..." message is should be printed, I think.
Please, Explain me about how the loop fusion works internally.
“Because, if you [want to] know whether an object is filtered, you have to calculate the filtering function”, is right, but perhaps your sample data wasn’t sufficient to illustrate the point. If you try
List<String> result = Stream.of("java", "streams", "are", "great", "stuff")
.filter(s -> {
System.out.println("filtering " + s);
return s.length()>=4;
})
.map(s -> {
System.out.println("mapping " + s);
return s.toUpperCase();
})
.limit(3)
.collect(Collectors.toList());
System.out.println("Result:");
result.forEach(System.out::println);
it will print
filtering java
mapping java
filtering streams
mapping streams
filtering are
filtering great
mapping great
Result:
JAVA
STREAMS
GREAT
Showing that
In order to find three elements matching the filter, you might have to evaluate more than three elements, here, four element are evaluated, but you don’t need to evaluate subsequent elements once you have three matches
The subsequent mapping function only need to be applied to matching elements. This allows to conclude that it is irrelevant whether .map(…).limit(…)or .limit(…).map(…) was specified.
This differs from the relative position of .filter and .limit which is relevant.
The term “loop fusion” implies that there is not a filtering loop, followed by a mapping loop, followed by a limit operation, but only one loop (conceptionally), performing the entire work, equivalent to the following single loop:
String[] source = { "java", "streams", "are", "great", "stuff"};
List<String> result = new ArrayList<>();
int limit = 3;
for(String s: source) {
System.out.println("filtering " + s);
if(s.length()>=4) {
System.out.println("mapping " + s);
String t = s.toUpperCase();
if(limit-->0) {
result.add(t);
}
else break;
}
}
I think you got it wrong. limit is actually called short-circuiting (because it is executed only 3 times).
While loop fusion is filter and map executed at a single pass. These two operations where merged into a single one that is executed at each element.
You do not see output like this:
filtering
filtering
filtering
mapping
mapping
mapping
Instead you see filter followed immediately by a map; so these two operations were merged into a single one.
Generally you should not care how that is done internally (it build a pipeline of these operations), because this might change and it is implementation specific.

How can I unit test a function that makes a string based on HashMap values?

I am trying to unit test a function that takes a HashMap and concatenates the keys into a comma separated string. The problem is that when I iterate through the HashMap using entrySet (or keySet or valueSet) the values are not in the order I .put() them in. IE:
testData = new HashMap<String, String>(0);
testData.put("colA", "valA");
testData.put("colB", "valB");
testData.put("colC", "valC");
for (Map.Entry<String, String> entry : testData.entrySet()) {
System.out.println("TestMapping " + entry.getKey());
}
Gives me the following output:
TestMapping colB
TestMapping colC
TestMapping colA
The string created by the SUT is ColB,ColC,ColA
How can I unit test this, since keySet(), valueSet(), etc are somewhat arbitrary in their order?
This is the function I am trying to test:
public String getColumns() {
String str = "";
for (String key : data.keySet()) {
str += ", " + key;
}
return str.substring(1);
}
There is no point in iterating over the HashMap in this case. The only reason to iterate over it would be to construct the expected String, in other words, perform the same operation as the method under test, so if you made an error implementing the method, you are likely to repeat the error when implementing the same for the unit test, failing to spot the error.
You should focus on the validity of the output. One way to test it, is to split it into the keys and check whether they match the keys of the source map:
testData = new HashMap<>();
testData.put("colA", "valA");
testData.put("colB", "valB");
testData.put("colC", "valC");
String result = getColumn();
assertEquals(testData.keySet(), new HashSet<>(Arrays.asList(result.split(", "))));
You are in control of the test data, so you can ensure that no ", " appears within the key strings.
Note that in its current form, your question’s method would fail, because the result String has an additional leading space. You have to decide whether it is intentional (in this case, you have to change the test to assertEquals(testData.keySet(), new HashSet<>(Arrays.asList(result.substring(1) .split(", "))));) or a spotted bug (then, you have to change the method’s last line to return str.substring(2);).
Don’t forget to make a testcase for an empty map…
HashMap does not maintain insertion order....If you want insertion order to be maintained use a linkedhashmap

Fastest way to put contents of Set<String> to a single String with words separated by a whitespace?

I have a few Set<String>s and want to transform each of these into a single String where each element of the original Set is separated by a whitespace " ".
A naive first approach is doing it like this
Set<String> set_1;
Set<String> set_2;
StringBuilder builder = new StringBuilder();
for (String str : set_1) {
builder.append(str).append(" ");
}
this.string_1 = builder.toString();
builder = new StringBuilder();
for (String str : set_2) {
builder.append(str).append(" ");
}
this.string_2 = builder.toString();
Can anyone think of a faster, prettier or more efficient way to do this?
With commons/lang you can do this using StringUtils.join:
String str_1 = StringUtils.join(set_1, " ");
You can't really beat that for brevity.
Update:
Re-reading this answer, I would prefer the other answer regarding Guava's Joiner now. In fact, these days I don't go near apache commons.
Another Update:
Java 8 introduced the method String.join()
String joined = String.join(",", set);
While this isn't as flexible as the Guava version, it's handy when you don't have the Guava library on your classpath.
If you are using Java 8, you can use the native
String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
method:
Returns a new String composed of copies of the CharSequence elements joined together with a copy of the specified delimiter.
For example:
Set<String> strings = new LinkedHashSet<>();
strings.add("Java"); strings.add("is");
strings.add("very"); strings.add("cool");
String message = String.join("-", strings);
//message returned is: "Java-is-very-cool"
Set implements Iterable, so simply use:
String.join(" ", set_1);
As a counterpoint to Seanizer's commons-lang answer, if you're using Google's Guava Libraries (which I'd consider the 'successor' to commons-lang, in many ways), you'd use Joiner:
Joiner.on(" ").join(set_1);
with the advantage of a few helper methods to do things like:
Joiner.on(" ").skipNulls().join(set_1);
// If 2nd item was null, would produce "1, 3"
or
Joiner.on(" ").useForNull("<unknown>").join(set_1);
// If 2nd item was null, would produce "1, <unknown>, 3"
It also has support for appending direct to StringBuilders and Writers, and other such niceties.
Maybe a shorter solution:
public String test78 (Set<String> set) {
return set
.stream()
.collect(Collectors.joining(" "));
}
or
public String test77 (Set<String> set) {
return set
.stream()
.reduce("", (a,b)->(a + " " + b));
}
but native, definitely faster
public String test76 (Set<String> set) {
return String.join(" ", set);
}
I don't have the StringUtil library available (I have no choice over that) so using standard Java I came up with this ..
If you're confident that your set data won't include any commas or square brackets, you could use:
mySet.toString().replaceAll("\\[|\\]","").replaceAll(","," ");
A set of "a", "b", "c" converts via .toString() to string "[a,b,c]".
Then replace the extra punctuation as necesary.
Filth.
I use this method:
public static String join(Set<String> set, String sep) {
String result = null;
if(set != null) {
StringBuilder sb = new StringBuilder();
Iterator<String> it = set.iterator();
if(it.hasNext()) {
sb.append(it.next());
}
while(it.hasNext()) {
sb.append(sep).append(it.next());
}
result = sb.toString();
}
return result;
}
I'm confused about the code replication, why not factor it into a function that takes one set and returns one string?
Other than that, I'm not sure that there is much that you can do, except maybe giving the stringbuilder a hint about the expected capacity (if you can calculate it based on set size and reasonable expectation of string length).
There are library functions for this as well, but I doubt they're significantly more efficient.
This can be done by creating a stream out of the set and then combine the elements using a reduce operation as shown below (for more details about Java 8 streams check here):
Optional<String> joinedString = set1.stream().reduce(new
BinaryOperator<String>() {
#Override
public String apply(String t, String u) {
return t + " " + u;
}
});
return joinedString.orElse("");

Categories

Resources