Lambda expressions and nested arrays - java

I'm trying to come up with some nice lambda expressions to build "desiredResult" from "customers" ArrayList. I implemented it in an old ugly way "for" loop. I know there should be nice one-liners, but I can't think of any method - nested arrays come into my way.
Iterable<List<?>> params;
Customer customer1 = new Customer("John", "Nowhere");
Customer customer2 = new Customer("Alma", "Somewhere");
Customer customer3 = new Customer("Nemo", "Here");
Collection<Customer> customers = new ArrayList<>();
customers.add(customer1);
customers.add(customer2);
customers.add(customer3);
Collection<List<?>> desiredResult = new ArrayList<>();
for (Customer customer : customers) {
List<Object> list = new ArrayList<>();
list.add(customer.getName());
list.add(customer.getAddress());
list.add("VIP");
desiredResult.add(list);
}
params = desiredResult;

I'd just use Arrays.asList for creating the inner lists, which makes the problem much simpler:
Collection<List<?>> desiredResult =
customers.stream()
.map(c -> Arrays.asList(c.getName(), c.getAddress(), "VIP"))
.collect(Collectors.toList());
If you absolutely must have an ArrayList, just wrap the Arrays.asList call with it.

Here is a suggestion:
Collection<List<?>> desiredResult = customers.stream()
.map(MyClass::customerToList)
.collect(toList());
I have extracted the list building into a separate method for better readability - the corresponding method would look like this:
private static List<Object> customerToList(Customer c) {
List<Object> list = new ArrayList<>();
list.add(c.getName());
list.add(c.getAddress());
list.add("VIP");
return list;
}

Related

The cleanest way to create a list in a functional way from a list of lists?

I have the following code:
List<DataObject> dataObjectList = new ArrayList<>();
for (Company company : companyRepository.findAll()) {
for (Employee employee : company.employees) {
DataOjbect dataObject = new dataObject();
dataObject.setCompanyName(company.getName());
dataObject.setEmployeeName(employee.getName());
dataObjectList.add(dataObject);
}
}
What is the cleanest way to write this code in a functional style in java?
Note that companyRepository.findAll() returns an Iterator, so you can't simply create a stream out of it.
To create a Stream from Iterator, you need to create Iterable first and then pass its Spliterator using Iterable::spliterator method into StreamSupport::stream.
Stream<?> stream = StreamSupport.stream(iterable.spliterator(), false);
The Iterable can be created from Iterator through a lambda expression as long as Iterable has only one abstract method, therefore the interface is qualified for such expression.
Iterable<Company> iterable = () -> companyRepository.findAll();
Stream<Company> stream = StreamSupport.stream(iterable.spliterator(), false);
Now, the things get easy: Use the advantage of flatMap to flatten the nested list structure (a list of companies where each company has a list of employees. You need to create each DataObject right inside the flatMap method as long as its instantiation relies on the company.getName() parameter:
List<DataObject> dataObjectList = StreamSupport.stream(iterable.spliterator(), false)
.flatMap(company -> company.getEmployees().stream()
.map(employee -> {
DataObject dataObject = new DataObject();
dataObject.setCompanyName(company.getName());
dataObject.setEmployeeName(employee.getName());
return dataObject;
}))
.collect(Collectors.toList());
... and less verbose if you use a constructor ...
List<DataObject> dataObjectList = StreamSupport.stream(iterable.spliterator(), false)
.flatMap(company -> company.getEmployees().stream()
.map(employee -> new DataObject(company.getName(), employee.getName())))
.collect(Collectors.toList());
Imo, there is no real benefit to using streams to do this. I would stick with what you have. You could make it more concise by creating a constructor in your DataObject class. Then you could do the following:
List<DataObject> dataObjectList = new ArrayList<>();
for (Company company : companyRepository.findAll()) {
for (Employee employee : company.employees) {
dataObjectList.add(new DataObject(company.getName(), employee.getName()));
}
}

Combine two Maps to a List of objects using Java8

Given 2 Maps and an object
Map<Integer, List<String>> fruits = new HashMap<>();
fruits.put(1, Arrays.asList("apple", "banana"));
fruits.put(2, Arrays.asList("orange", "kiwi"));
Map<Integer, List<String>> veggies= new HashMap<>();
veggies.put(1, Arrays.asList("tomato", "potato"));
veggies.put(2, Arrays.asList("onion"));
Class Food
{
private id;
private List<String> fruitsList;
private List<String> veggiesList;
//getters and setters
}
I am trying to combine the given 2 maps to a single list containing Food object(List).
//Used for explanation purpose
Food food1 = new Food();
food1.setId(1);
food1.setFruitsList(Arrays.asList("apple", "banana"));
food1.setVeggiesList(Arrays.asList("tomato", "potato"));
//Used for explanation purpose
Food food2 = new Food();
food2.setId(2);
food2.setFruitsList(Arrays.asList("orange", "kiwi"));
food2.setVeggiesList(Arrays.asList("onion"));
//Wanted this list of food
List<Food> foodList = new ArrayList();
foodList.add(food1);
foodList.add(food2);
I need to get a List.
Can we achieve that using Java8 streams?
Any solutions would be appreciated.
You can do something like this:
List<Food> foodList = fruits.keySet().stream()
.concat(veggies.keySet().stream())
.distinct()
.map(id -> {
Food food = new Food();
food.setId(id);
food.setFruitsList(fruits.getOrDefault(id, new ArrayList<>()));
food.setVeggiesList(veggies.getOrDefault(id, new ArrayList<>()));
return food ;
})
.collect(Collectors.toList());
If your Food class has a constructor taking the three parameters, it gets just a little more concise:
List<Food> foodList = fruits.keySet().stream()
.concat(veggies.keySet().stream())
.distinct()
.map(id -> new Food(
id,
fruits.getOrDefault(id, new ArrayList<>()),
veggies.getOrDefault(id, new ArrayList<>())
)
.collect(Collectors.toList());
If you know the ids are the same in both maps, you can skip the .concat() and .distinct() steps (which are quite expensive), and just use get() instead of getOrDefault().
You can also (in any case) do
Set<Integer> allKeys = new HashSet<>(fruits.keySet());
allKeys.addAll(veggies.keySet());
List<Food> foodList = allKeys.stream()
.map(/* as before */)
.collect(Collectors.toList());
which is not quite a "pure stream" a solution, but is probably more efficient.
You can merge the two keys streams and use a distinct method to get only unique keys.
List<Food> foods = Stream.concat(fruits.keySet().stream() , veggies.keySet().stream())
.distinct()
.map(e -> new Food(e, fruits.get(e), veggies.get(e)))
.collect(Collectors.toList());

Java collect to list but specify pre-defined first two elements order

I have a List<Person> objects. From it I want to get a list of all id's, and I always want the id "abc" and "bob" to come as the 0th and 1st index of the list if available. Is there a way to do this with java streams?
class Person {
private String id;
}
List<Person> allPeople = ...
List<String> allIds = allPeople.stream().map(Person::id).collect(Collectors.toList());
My approach is:
Set<String> allIds = allPeople.stream().map(Person::id).collect(Collectors.Set());
List<String> orderedIds = new ArrayList<>();
if(allIds.contains("abc")) {
orderedIds.add("abc");
}
if(allIds.contains("bob")) {
orderedIds.add("bob");
}
//Iterate through the set and all add all entries which are not bob and abc in the list.
It seems like you need more of a PriorityQueue rather than a List here, so may be something like this:
PriorityQueue<String> pq = list.stream()
.map(Person::getId)
.distinct()
.collect(Collectors.toCollection(() -> new PriorityQueue<>(
Comparator.comparing(x -> !"abc".equals(x))
.thenComparing(x -> !"bob".equals(x)))));
If you still need a List though, just drain that pq into one:
List<String> result = new ArrayList<>();
while (!pq.isEmpty()) {
result.add(pq.poll());
}
I assume that each id occurs only once in the list. With this I would choose a simple straightforward solution:
List<Person> allPeople = ...;
List<String> allIds = allPeople.stream().map(Person::id).collect(toCollection(ArrayList::new));
boolean foundBob = allIds.remove("bob");
if (foundBob) allIds.add(0, "bob");
boolean foundAbc = allIds.remove("abc");
if (foundAbc) allIds.add(0, "abc");
Note that "bob" and "abc" are moved to the head of the list in reverse order. So "abc" is first in the end.
You can make a small utility method for moving an element:
static void moveToHead(List<String> list, String elem) {
boolean found = list.remove(elem);
if (found) list.add(0, elem);
}
With this your code is even simpler and easier to understand:
List<Person> allPeople = ...;
List<String> allIds = allPeople.stream().map(Person::id).collect(toCollection(ArrayList::new));
moveToHead(allIds, "bob");
moveToHead(allIds, "abc");
if you want to perform this in a "fully" stream pipeline you could do:
allPeople.stream()
.map(Person::id)
.distinct()
.collect(collectingAndThen(partitioningBy(s -> "abc".equals(s) || "bob".equals(s)),
map -> Stream.concat(map.get(true).stream(), map.get(false).stream())));
.collect(toList());
if you always want "abc" in front of "bob" then change
map.get(true).stream()
to
map.get(true).stream()
.sorted(Comparator.comparing((String s) -> !s.equals("abc")))
Another solution you could do is:
Set<String> allIds = allPeople.stream().map(Person::id).collect(toSet());
List<String> orderedIds = Stream.concat(allIds.stream()
.filter(s -> "abc".equals(s) || "bob".equals(s))
.sorted(Comparator.comparing((String s) -> !s.equals("abc"))),
allIds.stream().filter(s -> !"abc".equals(s) && !"bob".equals(s)))
.collect(toList());
which is pretty much doing the same thing as the above partitioningBy but just in a different approach.
Finaly, you might be surprised but your approach actually seems good, so you may want to complete it with:
Set<String> allIds = allPeople.stream().map(Person::id).collect(toSet());
List<String> orderedIds = new ArrayList<>();
if(allIds.contains("abc"))
orderedIds.add("abc");
if(allIds.contains("bob"))
orderedIds.add("bob");
orderedIds.addAll(allIds.stream().filter(s -> !"abc".equals(s) && ! "bob".equals(s)).collect(toList()));
Inspired by Stuart Marks there is an even simpler solution:
List<String> allIds = allPeople.stream()
.map(Person::getId)
.distinct()
.sorted(comparing(x -> !"abc".equals(x)).thenComparing(x -> !"bob".equals(x)))
.collect(Collectors.toList());

Lambda expression to add objects from one list to another type of list

There is a List<MyObject> and it's objects are required to create object that will be added to another List with different elements : List<OtherObject>.
This is how I am doing,
List<MyObject> myList = returnsList();
List<OtherObj> emptyList = new ArrayList();
for(MyObject obj: myList) {
OtherObj oo = new OtherObj();
oo.setUserName(obj.getName());
oo.setUserAge(obj.getMaxAge());
emptyList.add(oo);
}
I'm looking for a lamdba expression to do the exact same thing.
If you define constructor OtherObj(String name, Integer maxAge) you can do it this java8 style:
myList.stream()
.map(obj -> new OtherObj(obj.getName(), obj.getMaxAge()))
.collect(Collectors.toList());
This will map all objects in list myList to OtherObj and collect it to new List containing these objects.
You can create a constructor in OtherObject which uses MyObject attributes,
public OtherObject(MyObject myObj) {
this.username = myObj.getName();
this.userAge = myObj.getAge();
}
and you can do following to create OtherObjects from MyObjects,
myObjs.stream().map(OtherObject::new).collect(Collectors.toList());
I see that this is quite old post. However, this is my take on this based on the previous answers. The only modification in my answer is usage of .collect(ArrayList::new, ArrayList::add,ArrayList:addAll).
Sample code :
List<OtherObj> emptyList = myList.stream()
.map(obj -> {
OtherObj oo = new OtherObj();
oo.setUserName(obj.getName());
oo.setUserAge(obj.getMaxAge());
return oo; })
.collect(ArrayList::new, ArrayList::add,ArrayList::addAll);

Create multi array in Java

This might be a very basic question but I'm not used to work with Java and I would like to create an array / list like this:
6546:{
"Ram":{
24M,
4M,
64M,
...
},
"Cpu":{
2%,
4%,
6%,
...
},
...
}
I've been trying it with LinkedList and so on but end up creating lists of lists and it starts looking very ugly.
This is a very common array in JSON, PHP or even Javascript, what would be the best way to create it by using Java?
You want a List<List<Integer>> or an int[][].
List<List<Integer>> list = new ArrayList<>();
list.add(new ArrayList<>());
list.get(0).add(24);
But perhaps you just want to use something like Gson and store this as JSON.
Or create a class like:
class Data {
private final List<Integer> ram = new ArrayList<>();
private final List<Integer> cpu = new ArrayList<>();
}
Or if you want to avoid creating classes? (Which you shouldn't)
Map<String, List<Integer>> map = new HashMap<>();
map.put("cpu", new ArrayList<>());
map.put("ram", new ArrayList<>());
Array of array you can define like - String[][].
It might be done in that way.
int[][] twoDimTable = new int[size][size];
String[][] twoDimTable = new String[size][size];
or
List<List<Integer> list = new ArrayList<>(); //or
List<List<String> list = new ArrayList<>();
HashMap<Integer, HashMap<String, List<Object>>> looks good.
This looks more like a key/value indexed structure.
One way (of many) to do something equivalent in Java:
Map<Integer, Map<String, String[]>> myData = new Hashtable<Integer, Map<String, String[]>>();
Map<String, String[]> entries = new Hashtable<String, String[]>();
entries.put("Ram", new String[] {"24M", "4M"}); // etc.
entries.put("Cpu", new String[] {"2%", "4%"}); // etc.
myData.put(6546, entries);
This would create an equivalent data structure, and you could index into it in a familiar fashion:
myData.get(6546).get("Ram")[0];
Although that would be VERY bad form, as you should always check for nulls before using the results of .get(x), such as:
Map<String, String[]> gotEntry = myData.get(6546);
if (gotEntry != null) {
String[] dataPoints = gotEntry.get("Ram");
if (dataPoints != null && dataPoints.length > 0) {
String data = dataPoints[0];
}
}
And so on. Hope this helps!
One other more interesting option is to use something like described here where you can define your data as a JSON string, and convert it into Object types later using un/marshalling.

Categories