Java 8 Comma Separated String to Object property - java

I have three comma-separated lists (list of bus, car, cycle) and I am trying to write them into Java object properties using Java 8 streams.
Please find below what I have tried :
public class Traffic {
public int car;
public int bus;
public int cycle;
public Traffic(int car, int bus,int cycle){
this.car = car;
this.bus = bus;
this.cycle = cycle;
}
}
public class Test {
public static void main(String[] args) {
String bus = "5,9,15,86";
String car = "6,12,18,51";
String cycle = "81,200,576,894";
String[] busArray = bus.split(",");
String[] carArray = car.split(",");
String[] cycleArray = cycle.split(",");
List<Traffic> trafficList =
Arrays.stream(values)
.mapToInt(Integer::parseInt)
.mapToObj((int i,j) -> new Traffic(i,j))
.collect(Collectors.toList());
}
}
I was struggling with getting all streams up and injected into object properties. (I want to create 4 objects in this case populating all 3 properties.)
Basically, I am looking for something like below:
List<Traffic> trafficList =
Arrays.stream(carArray,busArray,cycleArray)
.mapToInt(Integer::parseInt)
.mapToObj((int i,j,k) -> new Traffic(i,j,k))
.collect(Collectors.toList());

If you want to create 4 objects of Traffic then you can use the following :
List<Traffic> collect = IntStream.range(0, busArray.length)
.mapToObj(i -> new Traffic(Integer.parseInt(busArray[i]),
Integer.parseInt(carArray[i]),
Integer.parseInt(cycleArray[i])))
.collect(Collectors.toList());

You just have to split your string and then map each value to your object.
Here I assume the value can be passed through the constructor of your Traffic object. If not, you can create it and set its value in 2 separate lines. The mapToInt is necessary if the value is expected to be an integer.
String original = "5,9,15,86";
String[] values = original.split(",");
List<Traffic> trafficList =
Arrays.stream(values)
.mapToInt(Integer::parseInt)
.map(Traffic::new)
.collect(Collectors.toList());

Define a constructor in the class Traffic that takes an integer as argument and assigns it to value attribute in the class.
static class Traffic {
private int value;
public Traffic(int value) {
this.value = value;
}
}
Now assuming the comma delimited string is in a string commandList, something like below.
String commaList = "1,3,5,6,7,8,9,100";
Following stream instruction will return a list of Traffic objects with the value assigned.
List<Traffic> listOfIntegers =
Arrays.asList(commaList.split(","))
.stream()
.map(e -> new Traffic(Integer.valueOf(e)))
.collect(Collectors.toList());

If you really want an array, you can try the following
Arrays.stream("5,9,15,86".split(","))
.map(Traffic::new)
.toArray(Traffic[]::new);
If a List<Traffic> is also okay for you i recommend this one
Arrays.stream("5,9,15,86".split(","))
.map(Traffic::new)
.collect(Collectors.toList());
And lastly if you only have a constructor for Integer for example, you can map the stram to int like
Arrays.stream("5,9,15,86".split(","))
.mapToInt(Integer::valueOf)
.mapToObj(Traffic::new)
.collect(Collectors.toList());
EDIT
I answered this question before the question was edited, that's why it is only a partial answer
EDIT2
Okay i got it, i used map instead of mapToObj what a huge mistake... But i found it out thanks to #JavaMan's helpful answers (notice that if you are using IntelliJ it offers you to replace map with mapToObj)

Related

Filter objects from a list that have the same member

I have a list of objects. The object looks like this:
public class Slots {
String slotType;
Visits visit;
}
public class Visits {
private long visitCode;
private String agendaCode;
private String scheduledTime;
private String resourceType;
private String resourceDescription;
private String visitTypeCode;
...
}
I need to find the elements that have the same agendaCode, visitTypeCode and scheduledTime and for the life of me I can't get it done.
I tried this:
Set<String> agendas = slotsResponse.getContent().stream()
.map(Slots::getVisit)
.map(Visits::getAgendaCode)
.collect(Collectors.toUnmodifiableSet());
Set<String> visitTypeCode = slotsResponse.getContent().stream()
.map(Slots::getVisit)
.map(Visits::getVisitTypeCode)
.collect(Collectors.toUnmodifiableSet());
Set<String> scheduledTime = slotsResponse.getContent().stream()
.map(Slots::getVisit)
.map(Visits::getScheduledTime)
.collect(Collectors.toUnmodifiableSet());
List<Slots> collect = slotsResponse.getContent().stream()
.filter(c -> agendas.contains(c.getVisit().getAgendaCode()))
.filter(c -> visitTypeCode.contains(c.getVisit().getVisitTypeCode()))
.filter(c -> scheduledTime.contains(c.getVisit().getScheduledTime()))
.collect(Collectors.toList());
But it's not doing what I thought it would. Ideally I would have a list of lists, where each sublist is a list of Slots objects that share the same agendaCode, visitTypeCode and scheduledTime. I struggle with functional programming so any help or pointers would be great!
This is Java 11 and I'm also using vavr.
Since you mentioned you're using vavr, here is the vavr way to solve this question.
Supposed you have your io.vavr.collection.List (or Array or Vector or Stream or similar vavr collection) of visits:
List<Visits> visits = ...;
final Map<Tuple3<String, String, String>, List<Visits>> grouped =
visits.groupBy(visit ->
Tuple.of(
visit.getAgendaCode(),
visit.getVisitTypeCode(),
visit.getScheduledTime()
)
);
Or with a java.util.List of visits:
List<Visits> visits = ...;
Map<Tuple3<String, String, String>, List<Visits>> grouped = visits.stream().collect(
Collectors.groupingBy(
visit ->
Tuple.of(
visit.getAgendaCode(),
visit.getVisitTypeCode(),
visit.getScheduledTime()
)
)
);
The easiest way is to define a new class with necessaries fields (agendaCode, visitTypeCode and scheduledTime). Don't forget about equals/hashcode.
public class Visits {
private long visitCode;
private String resourceType;
private String resourceDescription;
private Code code;
...
}
class Code {
private String agendaCode;
private String scheduledTime;
private String visitTypeCode;
...
#Override
public boolean equals(Object o) {...}
#Override
public int hashCode() {...}
}
Then you can use groupingBy like:
Map<Code, List<Slots>> map = slotsResponse.getContent().stream()
.collect(Collectors.groupingBy(s -> s.getVisit().getCode()));
Also you can just implement equals method inside Visits only for agendaCode, visitTypeCode and scheduledTime. In this case use groupingBy by s.getVisit()
I love Ruslan's idea of using Collectors::groupingBy. Nevertheless, I don't like creating a new class or defining a new equals method. Both of them coerces you to a single Collectors::groupingBy version. What if you want to group by other fields in other methods?
Here is a piece of code that should let you overcome this problem:
slotsResponse.getContent()
.stream()
.collect(Collectors.groupingBy(s -> Arrays.asList(s.getVisit().getAgendaCode(), s.getVisit().getVisitTypeCode(), s.getVisit().getScheduledTime())))
.values();
My idea was to create a new container for every needed field (agendaCode, visitTypeCode, scheludedTime) and compare slots on these newly created containers. I would have liked doing so with a simple Object array, but it doesn't work - arrays should be compared with Arrays.equals which is not the comparison method used by Collectors::groupingBy.
Please note that you should store somewhere or use a method to define which fields you want to group by.
The fields you want to group by are all strings. You can define a function which concatenate those fields values and use that as key for your groups. Example
Function<Slots,String> myFunc = s -> s.getVisit().agendaCode + s.getVisit().visitTypeCode + s.getVisit().scheduledTime;
// or s.getVisit().agendaCode +"-"+ s..getVisit().visitTypeCode +"-"+ s.getVisit().scheduledTime;
And then group as below:
Map<String,List<Slots>> result = slotsResponse.getContent().stream()
.collect(Collectors.groupingBy(myFunc));

Java 8 find first instance of value in multidimensional array of objects

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();
}

Java: Identify common paths in ArrayList<String> using Lambdas

I got an array of elements like :
ArrayList<String> t = new ArrayList();
t.add("/folder1/sub-folder1");
t.add("/folder2/sub-folder2");
t.add("/folder1/sub-folder1/data");
I need to get output as /folder1/sub-folder1 which is mostly repeated path.
In python this can be achieved using the below function:
def getRepeatedPath(self, L):
""" Returns the highest repeated path/string in a provided list """
try:
pkgname = max(g(sorted(L)), key=lambda(x, v): (len(list(v)), -L.index(x)))[0]
return pkgname.replace("/", ".")
except:
return "UNKNOWN"
I am trying to work on equivalent lambda function in Java. I got struck and need some help in the lambda implementation.
public String mostRepeatedSubString(ArrayList<String> pathArray) {
Collections.sort(pathArray);
String mostRepeatedString = null;
Map<String,Integer> x = pathArray.stream.map(s->s.split("/")).collect(Collectors.toMap());
return mostRepeatedString;
}
Lots of tweaking, but I finally got it!
public static void main(String[] args) {
ArrayList<String> t = new ArrayList<String>();
t.add("folder1/sub-folder1");
t.add("folder2/sub-folder2");
t.add("folder1/sub-folder1/data");
System.out.println(mostRepeatedSubString(t));
}
public static String mostRepeatedSubString(List<String> pathArray) {
return pathArray
.stream()
// Split to lists of strings
.map(s -> Arrays.asList(s.split("/")))
// Group by first folder
.collect(Collectors.groupingBy(lst -> lst.get(0)))
// Find the key with the largest list value
.entrySet()
.stream()
.max((e1, e2) -> e1.getValue().size() - e2.getValue().size())
// Extract that largest list
.map(Entry::getValue)
.orElse(Arrays.asList())
// Intersect the lists in that list to find maximal matching
.stream()
.reduce(YourClassName::commonPrefix)
// Change back to a string
.map(lst -> String.join("/", lst))
.orElse("");
}
private static List<String> commonPrefix(List<String> lst1, List<String> lst2) {
int maxIndex = 0;
while(maxIndex < Math.min(lst1.size(), lst2.size())&& lst1.get(maxIndex).equals(lst2.get(maxIndex))) {
maxIndex++;
}
return lst1.subList(0, maxIndex);
}
Note that I had to remove the initial / from the paths, otherwise that character would have been used in the split, resulting in the first string in every path list being the empty string, which would always be the most common prefix. Shouldn't be too hard to do this in pre-processing though.

How can I consume for each elements of an optional list?

I have a List which can be null;
List<T> list; // may or may not null
I want to process for each element with a consumer.
So far, I do.
ofNullable(list)
.map(List::stream)
.ifPresent(stream -> stream.forEach(e -> {}));
or
ofNullable(eventDataList).ifPresent(v -> v.forEach(e -> {}));
Is there any easy or concise way to do this?
To avoid ugly null checking, use orElse(Collections.emptyList())
Optional.ofNullable(eventDataList)
.orElse(Collections.emptyList())
.forEach(e -> {});
With static imports, it's pretty concise:
ofNullable(eventDataList).orElse(emptyList()).forEach(e -> {});
Technically, if (list != null) { list.stream().forEach(e -> {...}); } is both shorter and more efficient in terms of CPU/memory usage than your variants.
Architecturally, if you have control over initialization of the list and its usage, it's often better to use either Collections.emptyList() instead of null (if the logic of your program allows) or make the list Optional from the very beginning. That would save you from necessity to make checks or create Optionals every time you want to use the list.
If you require to do something with every value in the list and say return a value then ifPresent will not work. Rather you can do something like below. In my example the optional list contains a user defined object Person which has a few attributes. I am iterating over the list and concatenating the values of a specific attribute and returning it.
public static class Person
{
String name;
int age;
public Person(final String name, final int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
public static void main(String[] args)
{
Person a = new Person("Alice", 1);
Person b = new Person("Bob", 2);
List<Person> personList = Lists.newArrayList(a, b);
String concatNames = Optional.of(personList).map(people -> people.stream().map(Person::getName).collect(Collectors.joining(" "))).orElse(null);
System.out.println("Names: "+concatNames);
}
I'm not sure that you can make it more concise. However, if you are frequently using the construct of looping over a nullable list and consuming each element, you could make a small class which does just that:
public class ListConsumer {
public static <H> Consumer<List<H>> of(Consumer<H> consumer) {
return hs -> hs.forEach(consumer);
}
}
You can then consume each element in a list as follows (e.g. print all Strings in list):
List<String> list = Arrays.asList("A", "B", "C");
Consumer<String> consumer = System.out::println;
Optional.ofNullable(list).ifPresent(ListConsumer.of(consumer));

java 8 stream groupingBy sum of composite variable

I have a class Something which contains an instance variable Anything.
class Anything {
private final int id;
private final int noThings;
public Anything(int id, int noThings) {
this.id = id;
this.noThings = noThings;
}
}
class Something {
private final int parentId;
private final List<Anything> anythings;
private int getParentId() {
return parentId;
}
private List<Anything> getAnythings() {
return anythings;
}
public Something(int parentId, List<Anything> anythings) {
this.parentId = parentId;
this.anythings = anythings;
}
}
Given a list of Somethings
List<Something> mySomethings = Arrays.asList(
new Something(123, Arrays.asList(new Anything(45, 65),
new Anything(568, 15),
new Anything(145, 27))),
new Something(547, Arrays.asList(new Anything(12, 123),
new Anything(678, 76),
new Anything(98, 81))),
new Something(685, Arrays.asList(new Anything(23, 57),
new Anything(324, 67),
new Anything(457, 87))));
I want to sort them such that the Something objects are sorted depending on the total descending sum of the (Anything object) noThings, and then by the descending value of the (Anything object) noThings
123 = 65+15+27 = 107(3rd)
547 = 123+76+81 = 280 (1st)
685 = 57+67+87 = 211 (2nd)
So that I end up with
List<Something> orderedSomethings = Arrays.asList(
new Something(547, Arrays.asList(new Anything(12, 123),
new Anything(98, 81),
new Anything(678, 76))),
new Something(685, Arrays.asList(new Anything(457, 87),
new Anything(324, 67),
new Anything(23, 57))),
new Something(123, Arrays.asList(new Anything(45, 65),
new Anything(145, 27),
new Anything(568, 15))));
I know that I can get the list of Anythings per parent Id
Map<Integer, List<Anythings>> anythings
= mySomethings.stream()
.collect(Collectors.toMap(p->p.getParentId(),
p->p.getAnythings()))
;
But after that I'm a bit stuck.
Unless I'm mistaken, you can not do both sorts in one go. But since they are independent of each other (the sum of the nothings in the Anythings in a Something is independent of their order), this does not matter much. Just sort one after the other.
To sort the Anytings inside the Somethings by their noThings:
mySomethings.stream().map(Something::getAnythings)
.forEach(as -> as.sort(Comparator.comparing(Anything::getNoThings)
.reversed()));
To sort the Somethings by the sum of the noThings of their Anythings:
mySomethings.sort(Comparator.comparing((Something s) -> s.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum())
.reversed());
Note that both those sorts will modify the respective lists in-place.
As pointed out by #Tagir, the second sort will calculate the sum of the Anythings again for each pair of Somethings that are compared in the sort. If the lists are long, this can be very wasteful. Instead, you could first calculate the sums in a map and then just look up the value.
Map<Something, Integer> sumsOfThings = mySomethings.stream()
.collect(Collectors.toMap(s -> s, s -> s.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum()));
mySomethings.sort(Comparator.comparing(sumsOfThings::get).reversed());
The problem of other solutions is that sums are not stored anywhere during sorting, thus when sorting large input, sums will be calculated for every row several times reducing the performance. An alternative solution is to create intermediate pairs of (something, sum), sort by sum, then extract something and forget about sum. Here's how it can be done with Stream API and SimpleImmutableEntry as pair class:
List<Something> orderedSomethings = mySomethings.stream()
.map(smth -> new AbstractMap.SimpleImmutableEntry<>(smth, smth
.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum()))
.sorted(Entry.<Something, Integer>comparingByValue().reversed())
.map(Entry::getKey)
.collect(Collectors.toList());
There's some syntactic sugar available in my free StreamEx library which makes the code a little bit cleaner:
List<Something> orderedSomethings = StreamEx.of(mySomethings)
.mapToEntry(smth -> smth
.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum())
.reverseSorted(Entry.comparingByValue())
.keys().toList();
As for sorting the Anything inside something: other solutions are ok.
In the end I added an extra method to the Something class.
public int getTotalNoThings() {
return anythings.stream().collect(Collectors.summingInt(Anything::getNoThings));
}
then I used this method to sort by total noThings (desc)
somethings = somethings.stream()
.sorted(Comparator.comparing(Something::getTotalNoThings).reversed())
.collect(Collectors.toList());
and then I used the code suggested above (thanks!) to sort by the Anything instance noThings
somethings .stream().map(Something::getAnythings)
.forEach(as -> as.sort(Comparator.comparing(Anything::getNoThings).reversed()));
Thanks again for help.

Categories

Resources