I have a collection of 10,000 objects that have two properties: a string and a string[]
class Product
{
String name;
String[] aliases;
}
Each object is roughly 64 bytes, so we're looking at under a megabyte in memory for the whole collection, which seems manageable for Android to search in memory.
I want an exact match to either the single string, or any string in the array, and to then return the name property of the object.
However, android has some restrictions on Lambdas and .stream() that exclude many of the popular search solutions without forcing users to have OS 7.0 or above, which rules out roughly one third of users. I'd like to avoid that.
Currently, the following code is working, but seems terrible considering some of options I've seen out there which take advantage of streams and lambdas.
public String isProductOrAlias(String lowertext) {
//products is instantiated in the parent class
//as a List<Product> loaded into memory from a local file
try {
for (Product p: products) {
if(lowertext.equals(p.name.toLowerCase()))
return p.name;
if(p.aliases != null) {
for (String s: p.aliases) {
if(lowertext.equals(s.toLowerCase()))
return p.name;
}
}
}
} catch (Exception ex) {
return "";
}
return "";
}
What's the best way to achieve the same goal, given the restrictions of the environment? Ideally, we'd like to keep minSDK to 23 (6.0) and above.
Canonical Kotlin, which would work on any version of Android regardless of Java Streams since it doesn't use them would be:
data class Product(val name: String, val aliases: Array<String>)
// written as an extension function, but doesn't have to be...
fun List<Product>.isProductOrAlias(text: String): String {
return this.firstOrNull { product ->
product.name.equals(text, ignoreCase = true) ||
product.aliases.any { alias ->
alias.equals(text, ignoreCase = true)
}
}?.name ?: ""
}
fun test() {
// assume somewhere this was created
val products = listOf<Product>(...)
println(products.isProductOrAlias("something")) // prints name of Product or ""
}
See Kotlin API reference for the standard library:
collection extension function firstOrNull()
array extension function any()
string extension function equals()
Kotlin Elvis operator ?:
Kotlin Safecalls ?.
Related
Say I have a piece of code that will try a few ways to find some value and, if unsuccessful, log all the ways that it tried.
Example:
public Optional<Integer> getFooOpt() {
Optional<Integer> fooOpt = Optional.empty();
List<String> needles = new ArrayList<>();
Optional<Integer> barOpt = getBarOpt();
if(barOpt.isPresent()) {
Integer bar = barOpt.get();
fooOpt = getFooOptByBar(bar);
if(!fooOpt.isPresent()) {
needles.add("bar " + bar);
}
}
if(!fooOpt.isPresent()) {
Optional<Integer> quxOpt = getQuxOpt();
if(quxOpt.isPresent()) {
Integer qux = quxOpt.get();
fooOpt = getFooOptByQux(qux);
if(!fooOpt.isPresent()) {
needles.add("qux " + qux);
}
}
}
if(!fooOpt.isPresent()) {
log.error("Not found by {}", needles);
}
return fooOpt;
}
Is there a way to restructure this code to avoid all the isPresent / get noise with Optionals here to make the code easier to read / follow?
If you're heavily using Optional, you can try nesting Optional#orElse.
Assuming you have several methods try{something}ToComputeValue that all return Optional<Integer>:
Optional<Integer> value = tryFooToComputeValue()
.orElse(tryBarToComputeValue()
.orElse(tryBazToComputeValue()
.orElseThrow(() -> throw new CannotComputeValue())));
That takes care of daisy chaining those. For logging, you could have each method log when it returns Optional.empty() (has failed to find the value). This doesn't fully match your desired behavior of logging only one statement with all methods. If you really need this, you can probably play with Optional#map or similar so that you add method names to needles when they fail to return a value.
You can simplify this by moving some of the logic into support methods. You don't care about the barOpt and quxOpt references here - all you care about is if they can get your that sweet, sweet fooOpt goodness. I think this is a candidate for OptionalInt rather than Optional. Without the error log message construction you have something like:
OptionalInt fooOpt = getFooOptByBarOpt();
if (fooOpt.isPresent()) {
return fooOpt;
}
fooOpt = getFooOptByQuxOpt();
if (fooOpt.isPresent()) {
return fooOpt;
}
return OptionalInt.empty();
Looking at the error logging there doesn't seem to be a reason to compile a List of failed attempts. You only log if you didn't find a value and if you didn't find a value you know exactly what you attempted.
if (!fooOpt.isPresent()) {
logger.error("Foo not found by bar or qux");
}
return fooOpt;
I couldn't wrap my head around writing the below condition using Java Streams. Let's assume that I have a list of elements from the periodic table. I've to write a method that returns a String by checking whether the list has Silicon or Radium or Both. If it has only Silicon, method has to return Silicon. If it has only Radium, method has to return Radium. If it has both, method has to return Both. If none of them are available, method returns "" (default value).
Currently, the code that I've written is below.
String resolve(List<Element> elements) {
AtomicReference<String> value = new AtomicReference<>("");
elements.stream()
.map(Element::getName)
.forEach(name -> {
if (name.equalsIgnoreCase("RADIUM")) {
if (value.get().equals("")) {
value.set("RADIUM");
} else {
value.set("BOTH");
}
} else if (name.equalsIgnoreCase("SILICON")) {
if (value.get().equals("")) {
value.set("SILICON");
} else {
value.set("BOTH");
}
}
});
return value.get();
}
I understand the code looks messier and looks more imperative than functional. But I don't know how to write it in a better manner using streams. I've also considered the possibility of going through the list couple of times to filter elements Silicon and Radium and finalizing based on that. But it doesn't seem efficient going through a list twice.
NOTE : I also understand that this could be written in an imperative manner rather than complicating with streams and atomic variables. I just want to know how to write the same logic using streams.
Please share your suggestions on better ways to achieve the same goal using Java Streams.
It could be done with Stream IPA in a single statement and without multiline lambdas, nested conditions and impure function that changes the state outside the lambda.
My approach is to introduce an enum which elements correspond to all possible outcomes with its constants EMPTY, SILICON, RADIUM, BOTH.
All the return values apart from empty string can be obtained by invoking the method name() derived from the java.lang.Enum. And only to caver the case with empty string, I've added getName() method.
Note that since Java 16 enums can be declared locally inside a method.
The logic of the stream pipeline is the following:
stream elements turns into a stream of string;
gets filtered and transformed into a stream of enum constants;
reduction is done on the enum members;
optional of enum turs into an optional of string.
Implementation can look like this:
public static String resolve(List<Element> elements) {
return elements.stream()
.map(Element::getName)
.map(String::toUpperCase)
.filter(str -> str.equals("SILICON") || str.equals("RADIUM"))
.map(Elements::valueOf)
.reduce((result, next) -> result == Elements.BOTH || result != next ? Elements.BOTH : next)
.map(Elements::getName)
.orElse("");
}
enum
enum Elements {EMPTY, SILICON, RADIUM, BOTH;
String getName() {
return this == EMPTY ? "" : name(); // note name() declared in the java.lang.Enum as final and can't be overridden
}
}
main
public static void main(String[] args) {
System.out.println(resolve(List.of(new Element("Silicon"), new Element("Lithium"))));
System.out.println(resolve(List.of(new Element("Silicon"), new Element("Radium"))));
System.out.println(resolve(List.of(new Element("Ferrum"), new Element("Oxygen"), new Element("Aurum")))
.isEmpty() + " - no target elements"); // output is an empty string
}
output
SILICON
BOTH
true - no target elements
Note:
Although with streams you can produce the result in O(n) time iterative approach might be better for this task. Think about it this way: if you have a list of 10.000 elements in the list and it starts with "SILICON" and "RADIUM". You could easily break the loop and return "BOTH".
Stateful operations in the streams has to be avoided according to the documentation, also to understand why javadoc warns against stateful streams you might take a look at this question. If you want to play around with AtomicReference it's totally fine, just keep in mind that this approach is not considered to be good practice.
I guess if I had implemented such a method with streams, the overall logic would be the same as above, but without utilizing an enum. Since only a single object is needed it's a reduction, so I'll apply reduce() on a stream of strings, extract the reduction logic with all the conditions to a separate method. Normally, lambdas have to be well-readable one-liners.
Collect the strings to a unique set. Then check containment in constant time.
Set<String> names = elements.stream().map(Element::getName).map(String::toLowerCase).collect(toSet());
boolean hasSilicon = names.contains("silicon");
boolean hasRadium = names.contains("radium");
String result = "";
if (hasSilicon && hasRadium) {
result = "BOTH";
} else if (hasSilicon) {
result = "SILICON";
} else if (hasRadium) {
result = "RADIUM";
}
return result;
i have used predicate in filter to for radium and silicon and using the resulted set i am printing the result.
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<Element> elementss = new ArrayList<>();
Set<String> stringSet = elementss.stream().map(e -> e.getName())
.filter(string -> (string.equals("Radium") || string.equals("Silicon")))
.collect(Collectors.toSet());
if(stringSet.size()==2){
System.out.println("both");
}else if(stringSet.size()==1){
System.out.println(stringSet);
}else{
System.out.println(" ");
}
}
}
You could save a few lines if you use regex, but I doubt if it is better than the other answers:
String resolve(List<Element> elements) {
String result = elements.stream()
.map(Element::getName)
.map(String::toUpperCase)
.filter(str -> str.matches("RADIUM|SILICON"))
.sorted()
.collect(Collectors.joining());
return result.matches("RADIUMSILICON") ? "BOTH" : result;
}
This is my groovy class
Asset {
ObjectState objectState = ObjectState.CURRENT
String description
#NotEmpty(message = "*Please provide a asset name")
#Length(min = 2, max = 50, message = "*Asset name must have characters between 2 and 50")
String assetName
#DBRef
Company company
}
I want to find those assets of a particular company which contains "test" in assetName and description
Now i implemented the business logic like this
#Override
Page<Asset> fetchAssetsBySearchStringAndObjectStateAndCompany(Company company, Pageable pageable, String searchQuery) {
ObjectState objectState = ObjectState.CURRENT
if (!pageable) {
pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "lastUpdated")
}
if (searchQuery) {
Page<Asset> assets = assetRepository.findAllByCompanyAndObjectState(company, pageable, objectState)
List<Asset> filteredAssets = []
assets.each {
if (it.assetName.contains(searchQuery) || it.description.contains(searchQuery)) {
filteredAssets.add(it)
}
}
return filteredAssets // i want this list in pagination object
} else {
return assetRepository.findAllByCompanyAndObjectState(company, pageable, objectState)
}
}
I find all the assets of a company -
Filter out the "test" string using groovy closure - assets.each { }
Now my filteredAssets contains required result but i want this in pagination object
Now my question is
1- Is this approach is efficient
2- How to convert filteredAssets in Page
I also tried to use mongo native query but i am unable to convert it to spring boot
#Query('''{
'company': ?0,
$or :
[
{'assetName' : { $regex: ?1, $options:'i' }},
{'description' : { $regex: ?1, $options:'i' }},
]
}
''')
Page<Asset> findAllByCompanyAndAssetNameOrDescription(Company company, String assetName, Pageable pageable)
I don't have a specific answer but my suggestion is that your first approach is not going to work at a higher level because you are filtering the results after the pagination has been performed by the initial query. So you will potentially end up with less than the desired page size (or even an empty result) even though there are valid results that could have been returned by the query.
In other words, to achieve this you really do need to use the second approach of constructing a native query that incorporates the filtering. To resolve why that is not working, you would need to post more information about the kind of errors you are seeing (or possibly put it as a separate question and close this one out).
EDIT: to answer the question more specifically - if you choose to persist with the approach, it looks to me like you can construct your own Page object by harnessing the Spring data PageImpl object which has a usable constructor from a list of elements. You can simply construct this object from your filtered list of elements - ie: instead of this:
...
return filteredAssets
Do this:
return new PageImpl(filteredAssets)
If you want to be more idiomatic with your groovy code I would also suggest to change the filtering operation to use findAll. In that case the code gets more compact:
return new PageImpl(assets.findAll { it.assetName.contains(searchQuery) })
Once again though I would caution that from looking at your problem I don't think it's going to have the result you actually want.
I have a java class with 3 boolean property like this
boolean isActive;
boolean isEnable;
boolean isNew;
every property is related to an enum (e.g. ACTIVE,ENABLE,NEW).
I want to have 2 lists of enum. One which has only the enums related to true property value and one for the false one.
just to be clear. using if-else statement I could have
Set<FlagEnum> flagSet = new HashSet<>();
Set<FlagEnum> falseFlagSet = new HashSet<>();
if (object.isActive()) {
flagSet.add(ACTIVE);
} else {
falseFlagSet.add(ACTIVE);
}
if (object.isEnable()) {
flagSet.add(ENABLE);
} else {
falseFlagSet.add(ENABLE);
}
if (object.isNew()) {
flagSet.add(NEW);
} else {
falseFlagSet.add(NEW);
}
is there a way to avoid all these if-else?
I tried with something like
Map<boolean, List<Pair<boolean, FlagEnum>>> res = Stream.of(
new Pair<>(object.isActive(), ACTIVE),
new Pair<>(object.isNew(), NEW),
new Pair<>(object.isEnable(), ENABLE))
.collect(Collectors.partitioningBy(Pair::getKey));
but the resulted structure is an additional complexity which I would like to avoid.
In my real case, I have more than 15 boolean properties...
You can simplify this in various ways. Which of them make sense, depends on your exact requirements.
You can derive the falseFlagSet trivially from the flagSet using EnumSet.complementOf after populating the flagSet:
EnumSet<FlagEnum> falseFlagSet = EnumSet.complementOf(flagSet);
This assumes that all FlagEnum values have corresponding flags. If that's not the case then you need to construct a EnumSet with all enums that have flags and subtract flagSet from that using removeAll.
#1 already removes the need for the else in your cascade, simplifying the code to
if (object.isActive()) {
flagSet.add(ACTIVE);
}
if (object.isEnable()) {
flagSet.add(ENABLE);
}
if (object.isNew()) {
flagSet.add(NEW);
}
If you have enough different flags, then you can create a mapping from getter method to FlagEnum value like this:
Map<Function<YourClass,Boolean>,FlagEnum> GETTERS = Map.of(
YourClass::isActive, FlagEnum.ACTIVE,
YourClass::isNew, FlagEnum.NEW,
YourClass::isEnable, FlagEnum.ENABLE);
Then you can use this to make the whole process data-driven:
EnumSet<FlagEnum> getFlagSet(YourClass yourObject) {
EnumSet<FlagEnum> result = EnumSet.noneOf(FlagEnum.class);
for (Map.Entry<Function<YourClass,Boolean>, FlagEnum> getter : GETTERS.entrySet()) {
if (getter.getKey().apply(yourObject)) {
result.add(getter.getValue());
}
}
return result;
}
If the number of flags is very big, then you could switch entirely to reflection and detect the flags and matching getters dynamically using string comparison, but I would not suggest that approach. If you need something like that then you probably should switch to a framework that supports that kind of feature and not implement it yourself.
That last two obviously only makes sense when the number of flags is big. If it's actually just 3 flags, then I wouldn't mind and just have 3 simple if statements.
As a slight tangent: GETTERS above should definitely be an immutable map (wrap it in Collections.unmodifiableMap or use something like Guava ImmutableMap) and it could be argued that the same applies to the return value of the getFlagSet method. I've left those out for succinctness.
You can use a private helper method for this.
private void addFlagSet(boolean condition, FlagEnum flagEnum,
Set<FlagEnum> flagSet, Set<FlagEnum> falseFlagSet) {
Set<FlagEnum> chosenFlagSet = condition ? flagSet: falseFlagSet;
chosenFlagSet.add(flagEnum);
}
Call it as:
addFlagSet(object.isActive(), FlagEnum.ACIVE, flagSet, falseFlagSet);
addFlagSet(object.isNew(), FlagEnum.NEW, flagSet, falseFlagSet);
addFlagSet(object.isEnable(), FlagEnum.ENABLE, flagSet, falseFlagSet);
You could probably use Reflection to get all methods, then check if a getReturnType() == boolean.class. Problem is the connection between the method's name and the enum. If every single one is named like the method without the 'is', you could use FlagEnum.valueOf() to retrieve the enum value from the method name and use it.
I think this could be the easiest and clearest way to do what I need
Map<Boolean, Set<FlagEnum>> flagMap = new HashMap<>();
flagMap.computeIfAbsent(object.isActive(), h -> new HashSet()).add(ACTIVE);
flagMap.computeIfAbsent(object.isEnabled(), h -> new HashSet()).add(ENABLE);
flagMap.computeIfAbsent(object.isNew(), h -> new HashSet()).add(NEW);
//to get TRUE set simply :
flagMap.get(true);
what do you think?
I have following method which is used for creating a order in the database, order has many items and, item has many bills. iPadPOSOrderDTO is the order which is going to base saved into the database.
so, the loop based code for creating order is the following
private void createNewOrder(IPadPOSOrderDTO iPadPOSOrderDTO) {
IPadPOSOrderV2 order = mapper.map(iPadPOSOrderDTO, IPadPOSOrderV2.class);
if(order.getOrderV2Bills()!=null && order.getOrderV2Bills().size()>0){
for(IPadPOSOrderV2Bill orderBill : order.getOrderV2Bills()){
orderBill.setOrder(order);
if(orderBill.getiPadPOSOrderV2BillItems()!=null && orderBill.getiPadPOSOrderV2BillItems().size()>0){
for(IPadPOSOrderV2BillItems orderBillItem : orderBill.getiPadPOSOrderV2BillItems()){
orderBillItem.setiPadPOSOrderV2Bill(orderBill);
orderBillItem.setOrderId(order.getOrderId());
}
}
}
}
sessionFactory.
getCurrentSession().save(order);
}
I wanted to refactor above code to use Java 8 streams API.
So, I did the following
private void createNewOrderV2(IPadPOSOrderDTO iPadPOSOrderDTO) {
IPadPOSOrderV2 order = mapper.map(iPadPOSOrderDTO, IPadPOSOrderV2.class);
if(order.getOrderV2Bills()!=null && order.getOrderV2Bills().size()>0){
order.getOrderV2Bills().stream().forEach(e -> { createBill(order,e);});
}
sessionFactory.
getCurrentSession().save(order);
}
private void createBill(IPadPOSOrderV2 ipadExistingOrderFromDatabase, IPadPOSOrderV2Bill iPadPOSOrderV2Bill) {
iPadPOSOrderV2Bill.setOrder(ipadExistingOrderFromDatabase);
if(iPadPOSOrderV2Bill.getiPadPOSOrderV2BillItems()!=null && iPadPOSOrderV2Bill.getiPadPOSOrderV2BillItems().size()>0){
iPadPOSOrderV2Bill.getiPadPOSOrderV2BillItems().stream().forEach(e -> createBillItem(ipadExistingOrderFromDatabase,iPadPOSOrderV2Bill,e));
}
}
private void createBillItem(IPadPOSOrderV2 ipadExistingOrderFromDatabase, IPadPOSOrderV2Bill iPadPOSOrderV2Bill, IPadPOSOrderV2BillItems iPadPOSOrderV2BillItem) {
iPadPOSOrderV2BillItem.setiPadPOSOrderV2Bill(iPadPOSOrderV2Bill);
iPadPOSOrderV2BillItem.setOrderId(ipadExistingOrderFromDatabase.getOrderId());
ipadExistingOrderFromDatabase.getOrderV2Bills().stream().forEach(e -> { createBill(ipadExistingOrderFromDatabase,e);});
}
could somebody share their experience and advice me if I am making the correct use of streams API for this refactoring.
Note that those size checks aren't really necessary. An empty list would result in an empty stream and thus nothing would get applied. The only benefit would be that you'd be able to avoid having to create the stream altogether but I highly doubt the performance difference would even be noticeble.
If you want to convert a potentially null collection to a stream you might want to use a small helper function:
public <T> Stream<T> collectionToStream(Collection<T> collection) {
return Optional.ofNullable(collection).map(Collection::stream).orElseGet(Stream::empty);
}
Using forEach() you could then do something like this:
private void createNewOrder(IPadPOSOrderDTO iPadPOSOrderDTO) {
IPadPOSOrderV2 order = mapper.map(iPadPOSOrderDTO, IPadPOSOrderV2.class);
collectionToStream(order.getOrderV2Bills()).forEach( orderBill -> {
orderBill.setOrder(order);
collectionToStream(orderBill.getiPadPOSOrderV2BillItems()).forEach(orderBillItem -> {
orderBillItem.setiPadPOSOrderV2Bill(orderBill);
orderBillItem.setOrderId(order.getOrderId());
}
}
}
}
sessionFactory.getCurrentSession().save(order);
}
Note that this isn't that different from your initial code and thus you should think about whether that conversion would make sense.
Converting your nested loops to a fully sequential stream would be harder and in the end not that different because you can't just flat map orderBill to a stream of orderBillItem. Doing that would not make orderBill available downstream so you'd have to call orderBillItem.setiPadPOSOrderV2Bill(orderBill); before returning the nested stream. That would end up in code very similar to the above and add no benefit because you're not using the returned stream.
Filter out the nulls ommiting the null checks
private void createNewOrderV2(IPadPOSOrderDTO iPadPOSOrderDTO) {
IPadPOSOrderV2 order = mapper.map(iPadPOSOrderDTO, IPadPOSOrderV2.class);
order.getOrderV2Bills().stream().filter(Objects::nonNull).forEach(e -> createBill(order, e));
sessionFactory.getCurrentSession().save(order);
}
private void createBill(IPadPOSOrderV2 ipadExistingOrderFromDatabase, IPadPOSOrderV2Bill iPadPOSOrderV2Bill) {
iPadPOSOrderV2Bill.setOrder(ipadExistingOrderFromDatabase);
iPadPOSOrderV2Bill.getiPadPOSOrderV2BillItems().stream().filter(Objects::nonNull).forEach(e -> {
e.setiPadPOSOrderV2Bill(iPadPOSOrderV2Bill);
e.setOrderId(ipadExistingOrderFromDatabase.getOrderId());
});
}
By the way your createBill() is called by the createBillItem and also the other way around, is this correct?