Currently im learning about stream and want to implement a method which accepts a string. The String starts with a certain word and ends with the same. The given example is "breadtunabread". The method return the word in between the bread.
public String getTopping(String s){
Stream<String> stream = Stream.of(s);
stream.filter(t -> t.startsWith("bread") && t.endsWith("bread")).
forEach(t -> Stream.of(t.split("bread")[1]).collect(Collectors.toList()));
}
I'd like to either save it to a List or change it directly so it returns a String.
Is it possible to get the first value from the stream and not use collect?
I somehow made it work using forEach and adding the value to an ArrayList and returning it but i'd like to know whether there is a way to do it directly using the stream.
Thanks in advance.
And to return just a String:
public String getTopping(String s, String toReplace) {
Stream<String> stream = Stream.of(s);
return stream.filter(t -> t.startsWith(toReplace) && t.endsWith(toReplace))
.findFirst()
.map(t -> t.replaceAll(toReplace, ""))
.orElseThrow(RuntimeException::new);
//.orElseThrow(() -> new NoBreadException("s"));
}
Stream<String> stream = Stream.of("breadtunabread");
List<String> stringList =
stream
.filter(t -> t.startsWith("bread") && t.endsWith("bread"))
.map(t -> (t.split("bread")[1]))
.collect(Collectors.toList());
Is this what you are looking for?
What others mentioned are correct that this is completely unnecessary. I posted this as you have mentioned that yuo are learning streams.
Just like #Naman pointed out, you don't need a Stream for this. String#replaceAll will quite literally replace all instances of the String (bread) with empty String values and in the end you get you're topping. Added the base parameter in case you're a monster like me and eat cheese between pieces of ham.
public static String getTopping(String value, String base) {
return value.replaceAll(base, "");
}
String topping = getTopping("breadtunabread", "bread")
Assuming you have a List of items you want to get the toppings of.
List<String> sandwhiches = Arrays.asList(
"breadtunabread",
"breadchickenbread",
"breadcheesebread",
"breadturkeybread",
"breadlambbread"
);
List<String> toppings = sandwhiches.stream()
.map(sandwhich -> getTopping(sandwhich, "bread"))
.collect(Collectors.toList());
Result
[tuna, chicken, cheese, turkey, lamb]
Related
I have a stream of data as shown below and I wish to collect the data based on a condition.
Stream of data:
452857;0;L100;csO;20220411;20220411;EUR;000101435;+; ;F;1;EUR;000100000;+;
452857;0;L120;csO;20220411;20220411;EUR;000101435;+; ;F;1;EUR;000100000;+;
452857;0;L121;csO;20220411;20220411;EUR;000101435;+; ;F;1;EUR;000100000;+;
452857;0;L126;csO;20220411;20220411;EUR;000101435;+; ;F;1;EUR;000100000;+;
452857;0;L100;csO;20220411;20220411;EUR;000101435;+; ;F;1;EUR;000100000;+;
452857;0;L122;csO;20220411;20220411;EUR;000101435;+; ;F;1;EUR;000100000;+;
I wish to collect the data based on the index = 2 (L100,L121 ...) and store it in different lists of L120,L121,L122 etc using Java 8 streams. Any suggestions?
Note: splittedLine array below is my stream of data.
For instance: I have tried the following but I think there's a shorter way:
List<String> L100_ENTITY_NAMES = Arrays.asList("L100", "L120", "L121", "L122", "L126");
List<List<String>> list= L100_ENTITY_NAMES.stream()
.map(entity -> Arrays.stream(splittedLine)
.filter(line -> {
String[] values = line.split(String.valueOf(DELIMITER));
if(values.length > 0){
return entity.equals(values[2]);
}
else{
return false;
}
}).collect(Collectors.toList())).collect(Collectors.toList());
I'd rather change the order and also collect the data into a Map<String, List<String>> where the key would be the entity name.
Assuming splittedLine is the array of lines, I'd probably do something like this:
Set<String> L100_ENTITY_NAMES = Set.of("L100", ...);
String delimiter = String.valueOf(DELIMITER);
Map<String, List<String>> result =
Arrays.stream(splittedLine)
.map(line -> {
String[] values = line.split(delimiter );
if( values.length < 3) {
return null;
}
return new AbstractMap.SimpleEntry<>(values[2], line);
})
.filter(Objects::nonNull)
.filter(tempLine -> L100_ENTITY_NAMES.contains(tempLine.getEntityName()))
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList());
Note that this isn't necessarily shorter but has a couple of other advantages:
It's not O(n*m) but rather O(n * log(m)), so it should be faster for non-trivial stream sizes
You get an entity name for each list rather than having to rely on the indices in both lists
It's easier to understand because you use distinct steps:
split and map the line
filter null values, i.e. lines that aren't valid in the first place
filter lines that don't have any of the L100 entity names
collect the filtered lines by entity name so you can easily access the sub lists
I would convert the semicolon-delimited lines to objects as soon as possible, instead of keeping them around as a serialized bunch of data.
First, I would create a model modelling our data:
public record LBasedEntity(long id, int zero, String lcode, …) { }
Then, create a method to parse the line. This can be as well an external parsing library, for this looks like CSV with semicolon as delimiter.
private static LBasedEntity parse(String line) {
String[] parts = line.split(";");
if (parts.length < 3) {
return null;
}
long id = Long.parseLong(parts[0]);
int zero = Integer.parseInt(parts[1]);
String lcode = parts[2];
…
return new LBasedEntity(id, zero, lcode, …);
}
Then the mapping is trivial:
Map<String, List<LBasedEntity>> result = Arrays.stream(lines)
.map(line -> parse(line))
.filter(Objects::nonNull)
.filter(lBasedEntity -> L100_ENTITY_NAMES.contains(lBasedEntity.lcode()))
.collect(Collectors.groupingBy(LBasedEntity::lcode));
map(line -> parse(line)) parses the line into an LBasedEntity object (or whatever you call it);
filter(Objects::nonNull) filters out all null values produced by the parse method;
The next filter selects all entities of which the lcode property is contained in the L100_ENTITY_NAMES list (I would turn this into a Set, to speed things up);
Then a Map is with key-value pairs of L100_ENTITY_NAME → List<LBasedEntity>.
You're effectively asking for what languages like Scala provide on collections: groupBy. In Scala you could write:
splitLines.groupBy(_(2)) // Map[String, List[String]]
Of course, you want this in Java, and in my opinion, not using streams here makes sense due to Java's lack of a fold or groupBy function.
HashMap<String, ArrayList<String>> map = new HashMap<>();
for (String[] line : splitLines) {
if (line.length < 2) continue;
ArrayList<String> xs = map.getOrDefault(line[2], new ArrayList<>());
xs.addAll(Arrays.asList(line));
map.put(line[2], xs);
}
As you can see, it's very easy to understand, and actually shorter than the stream based solution.
I'm leveraging two key methods on a HashMap.
The first is getOrDefault; basically if the value associate with our key doesn't exist, we can provide a default. In our case, an empty ArrayList.
The second is put, which actually acts like a putOrReplace because it lets us override the previous value associated with the key.
I hope that was helpful. :)
you're asking for a shorter way to achieve the same, actually your code is good. I guess the only part that makes it look lengthy is the if/else check in the stream.
if (values.length > 0) {
return entity.equals(values[2]);
} else {
return false;
}
I would suggest introduce two tiny private methods to improve the readability, like this:
List<List<String>> list = L100_ENTITY_NAMES.stream()
.map(entity -> getLinesByEntity(splittedLine, entity)).collect(Collectors.toList());
private List<String> getLinesByEntity(String[] splittedLine, String entity) {
return Arrays.stream(splittedLine).filter(line -> isLineMatched(entity, line)).collect(Collectors.toList());
}
private boolean isLineMatched(String entity, String line) {
String[] values = line.split(DELIMITER);
return values.length > 0 && entity.equals(values[2]);
}
I have to write a method that gets an Integer List and converts it into a String by adding "e" in front a number if it is even or "u" if it is uneven and then seperates them by ",".
Example: Input:List with numbers 1,2,4,3-->Output:"u1,e2,e4,u3".
The Problem is that I have to do it using lambda expressions and streams and I dont know how to differentiate between even and uneven numbers with them.
If I do it like this i can only do it for even or uneven Numbers:
public static String getString(List<Integer> list){
String s = list.stream()
.map(Object::toString)
.filter(i->Integer.parseInt(i)%2==0)
.map(i->"g"+i)
.collect(Collectors.joining(","));
return s;
}
How can i use something like an If statement in a stream to decide whether to put "e" or "u" in front of the number?
You can put the condition in the map operation. The easiest way is using the ternary condition operator.
BTW, there's no reason to convert the Integer to a String and then parse it again to an int.
public static String getString(List<Integer> list){
return list.stream()
.map(i->i%2==0?"e"+i:"u"+i)
.collect(Collectors.joining(","));
}
EDIT: you can also use an if-else statement, but that would look less elegant:
public static String getString(List<Integer> list){
return list.stream()
.map(i -> {
if (i%2==0) {
return "e"+i;
} else {
return "u"+i;
}
})
.collect(Collectors.joining(","));
}
Trying to understand how to use some java 8 features and was playing around with multidimensional array of objects, if I wanted to find the first instance of a value in a multidimensional array of objects.
Objects[][] someArray= .....
Arrays.stream(someArray)
.map(someArrayFirst -> Arrays.stream(someArrayFirst))
.map(unsure what to do here)
.filter(a -> a.equals("some value"))
.findFirst();
edit, thanks for the input. Just to help others out here is what I have now.
Arrays.stream(someArray)
.flatMap(someArrayFirst -> Arrays.stream(someArrayFirst))
.filter(MyCustomClass.class::isInstance)
.map(MyCustomClass.class::cast)
.filter(v -> v.value().equalsIgnoreCase("SomeString"))
.findFirst();
You are on the right track. You need to turn the two dimensions into a single stream and then take the first element that satisfies your condition:
String[][] array;
Optional<String> result = Arrays.stream(array)
.flatMap(Arrays::stream)
.filter("some value"::equals).findFirst();
The first stream produces a Stream<String[]>. The flat map turns each of the array elements into a Stream<String>. Then it's just filtering for the value you want and getting the first element that satisfies your condition.
static String[][] arr = new String[][]{{"x","y"},{"z","v"},{"b","z"}};
static String searchStr = "x";
static String searchObj = null;
public static void main(String... args) {
Arrays.stream(arr)
.forEach((subarr)->{
Optional<String> opt = Arrays.stream(subarr)
.filter((obj)->obj.equals(searchStr))
.findFirst();
if (opt.isPresent())
searchObj = opt.get();
});
System.out.println(searchObj);
}
or
static public String mapFlatMethod(String[][] arr, String searchStr) {
return Arrays.stream(arr).flatMap(row -> Stream.of(row))
.filter((obj)->obj.equals(searchStr))
.findFirst().get();
}
I have a class
class ColumnTags {
String Name;
Collection<String> columnSemanticTags;
// constructor and getter and setters and other relevant attributes
}
I want to get the columnSemanticTags from a list of ColumnTags for a given name.
The corresponding method is as follows
public Collection<String> getTags(String colName, List<ColumnTags> colList)
{
Collection<String> tags = new ArrayList();
for(ColumnTag col:colList){
if(colName.equals(col.getName())){
tags = col.getColumnSemanticTags();
break;
}
}
return tags;
}
Want to convert the for loop to a java stream . I have tried
tags = colList.stream().filter(col -> colName.equals(col.getName()))
.map(col -> col.getColumnSemanticTags())
.collect(Collectors.toCollection());
I am getting compilation error. I am not aware what should be the Supplier . Have tried ArrayList::new . I have also tried casting it to ArrayList , but no success.
Can someone advice me what am I assuming wrong or what should be the expected way to handle this scenario.
With the solution , can someone explain as to why .collect() is a wrong way of tackling this solution.
public Collection<String> getTags(String colName, List<ColumnTags> colList) {
return colList.stream().filter(col -> colName.equals(col.getName()))
.map(col -> col.getColumnSemanticTags())
.findFirst().orElse(new ArrayList<>());
}
An easier way of going about this would be to simply filter a Stream to find exactly what you're looking for. If it is found, then return it, otherwise return an empty ArrayList:
return colList.stream()
.filter(c -> colName.equals(c.getName()))
.map(ColumnTag::getColumnSemanticTags)
.findFirst()
.orElseGet(ArrayList::new);
If you really want to use collect, you must call flatMap. That merges all of the lists (which are come from map(col -> col.getColumnSemanticTags())) into a single stream which contains all of the items.
List<String> tags = colList.stream()
.filter(col -> colName.equals(col.getName()))
.map(col -> col.getColumnSemanticTags())
.flatMap(collection -> collection.stream())
.collect(Collectors.toList());
I have an entity Employee
class Employee{
private String name;
private String addr;
private String sal;
}
Now i have list of these employees. I want to filter out those objects which has name = null and set addr = 'A'. I was able to achieve like below :
List<Employee> list2= list.stream()
.filter(l -> l.getName() != null)
.peek(l -> l.setAddr("A"))
.collect(Collectors.toList());
Now list2 will have all those employees whose name is not null and then set addr as A for those employees.
What i also want to find is those employees which are filtered( name == null) and save them in DB.One way i achieved is like below :
List<Employee> list2= list.stream()
.filter(l -> filter(l))
.peek(l -> l.setAddr("A"))
.collect(Collectors.toList());
private static boolean filter(Employee l){
boolean j = l.getName() != null;
if(!j)
// save in db
return j;
}
1) Is this the right way?
2) Can we do this directly in lambda expression instead of writing separate method?
Generally, you should not use side effect in behavioral parameters. See the sections “Stateless behaviors” and “Side-effects” of the package documentation. Also, it’s not recommended to use peek for non-debugging purposes, see “In Java streams is peek really only for debugging?”
There’s not much advantage in trying to squeeze all these different operations into a single Stream pipeline. Consider the clean alternative:
Map<Boolean,List<Employee>> m = list.stream()
.collect(Collectors.partitioningBy(l -> l.getName() != null));
m.get(false).forEach(l -> {
// save in db
});
List<Employee> list2 = m.get(true);
list2.forEach(l -> l.setAddr("A"));
Regarding your second question, a lambda expression allows almost everything, a method does. The differences are on the declaration, i.e. you can’t declare additional type parameters nor annotate the return type. Still, you should avoid writing too much code into a lambda expression, as, of course, you can’t create test cases directly calling that code. But that’s a matter of programming style, not a technical limitation.
If you are okay in using peek for implementing your logic (though it is not recommended unless for learning), you can do the following:
List<Employee> list2= list.stream()
.peek(l -> { // add this peek to do persistence
if(l.getName()==null){
persistInDB(l);
}
}).filter(l -> l.getName() != null)
.peek(l -> l.setAddr("A"))
.collect(Collectors.toList());
You can also do something like this:
List<Employee> list2 = list.stream()
.filter(l->{
boolean condition = l.getName()!=null;
if(condition){
l.setAddr("A");
} else {
persistInDB(l);
}
return condition;
})
.collect(Collectors.toList());
Hope this helps!