Java 8 Stream API - convert for loop over map & list iterator inside it - arraylist

In the below code, I am trying to calculate the total price of a basket, where basket is a HashMap containing the products as key and the quantity as value. Promotions are available as a list of Promotion.
I am looping over every map entry and for each of them iterating the promotions. If the promotion matches, I am taking the promotion price (promotion.computeDiscountedPrice()) and removing the promotion from the list (Because a promotion is applicable only to a product & product is unique in the list)
If there is no promotion, we execute block.
if (!offerApplied) { /* .... */ }
Can you please help me in doing this same operation using JAVA 8 stream api?
BigDecimal basketPrice = new BigDecimal("0.0");
Map<String, Integer> basket = buildBasket(input);
List<Promotion> promotions = getOffersApplicable(basket);
for (Map.Entry<String, Integer> entry : trolley.entrySet()) {
boolean offerApplied = false;
Iterator<Promotion> promotionIterator = promotions.iterator();
while (promotionIterator.hasNext()) {
Promotion promotion = promotionIterator.next();
if (entry.getKey().equalsIgnoreCase(offer.getProduct().getProductName())) {
basketPrice = basketPrice.add(promotion.computeDiscountedPrice());
offerApplied = true;
promotionIterator.remove();
break;
}
if (!offerApplied) {
basketPrice = basketPrice.add(Product.valueOf(entry.getKey()).getPrice()
.multiply(new BigDecimal(entry.getValue())));
}
}
return basketPrice;

The simplest and cleaner solution, with a better performance than having to iterate the entire promotions list, is to start by creating a map of promotions identified by the product id (in lower case or upper case [assuming no case collision occurs by the use of equalsIgnoreCase(..)]).
Map<String, Promotion> promotionByProduct = promotions.stream()
.collect(Collectors.toMap(prom -> prom.getProduct()
.getProductName().toLowerCase(), Function.identity()));
This will avoid the need to iterate over the entire array when searching for promotions, it also avoids deleting items from it, which in case of being an ArrayList would need to shift to left the remaining elements each time the remove is used.
BigDecimal basketPrice = basket.keySet().stream()
.map(name -> Optional.ofNullable(promotionByProduct.get(name.toLowerCase()))
.map(Promotion::computeDiscountedPrice) // promotion exists
.orElseGet(() -> Product.valueOf(name).getPrice()) // no promotion
.multiply(BigDecimal.valueOf(basket.get(name))))
.reduce(BigDecimal.ZERO, BigDecimal::add);
It iterates for each product name in the basket, then checks if a promotion exists, it uses the computeDiscountedPrice method, otherwise it looks the product with Product.valueOf(..) and gets the price, after that it mutiplies this value by the quantity of products in the basket and finally the results are reduced (all values of the basket are added) with the BigDecimal.add() method.
Important thing to note, is that in your code, you don't multiply by the quantity the result of promotion.computeDiscountedPrice() (this code above does), i'm not sure if that is a type in your code, or that's the way it should behave.
If case it is in fact the way it should behave (you don't want to multiply quantity by promotion.computeDiscountedPrice()) the code would be:
BigDecimal basketPrice = basket.keySet().stream()
.map(name -> Optional.ofNullable(promotionByProduct.get(name.toLowerCase()))
.map(Promotion::computeDiscountedPrice)
.orElseGet(() -> Product.valueOf(name).getPrice()
.multiply(BigDecimal.valueOf(basket.get(name)))))
.reduce(BigDecimal.ZERO, BigDecimal::add);
Here the only value multiplied by quantity would be the product price obtained with Product.valueOf(name).getPrice().
Finally another option, all in one line and not using the map (iterating over the promotions) using the first approach (multipling by quantity in the end):
BigDecimal basketPrice = basket.keySet().stream()
.map(name -> promotions.stream()
.filter(prom -> name.equalsIgnoreCase(prom.getProduct().getProductName()))
.findFirst().map(Promotion::computeDiscountedPrice) // promotion exists
.orElseGet(() -> Product.valueOf(name).getPrice()) // no promotion
.multiply(BigDecimal.valueOf(basket.get(name))))
.reduce(BigDecimal.ZERO, BigDecimal::add);

Related

Creating a shop/ item system in flutter with SQL, how to structure in-game/app items properly?

New to SQL & databases, creating an in-app shop that holds different items.
void _createTableItems(Batch batch) {
batch.execute('DROP TABLE IF EXISTS Items');
batch.execute('''CREATE TABLE boughtItems (
id INTEGER PRIMARY KEY,
price INTEGER
)''');
}
class Item {
Item({required this.id, required this.price, required this.title});
String title;
int id;
int price;
}
List<Item> availableProducts = [WeaponItem(id: 0, strength: 5), WeaponItem(id: 1, strength: 7), StableItem(id: 2, speed: 4), FoodItem(id: 3, rev: 7)]
I pretty much have the most basic strucutre possible right now.
When I need to get the products, all I do is search the availableProducts list of items for ID's in the database query.
Future<List<Item>> getBought() async {
await database;
List products = await _database!.query("Items");
List<Item> result = [];
for (var element in products) {
result.add(availableProducts.where((e) => e.id == element["id"]).first);
}
return result;
}
Is this an acceptable way to do this?
What I'm worried about is the mixing of item types.
I'm a bit lost since there's multiple things I could do. Should I create a different table holding all the properties of each individual items? Should I add a type string to the table to differentiate the different items?
I would create one table for example products. This table
has general information about the products(id, name, product_number, info) that all products have in common. If you want additional and different information for every product type, I would add a fk_type which references to a product_type(id, name) table that stores the different product types. Then I would create an additional table for every product type. Ex: product_weapon(id, size, ammunition, reload_time ), product_chair(id, high, depth, weight). But you can also work just with the simple product table and add general description fields that could store jsons, xmls, css files.

Optaplanner. School timetabling. Force first lession

I'm trying to add constraints to School timetabling example. For example: "all groups should have the first lesson".
I tried EasyScore and Streaming - no success. EasyScore cant finds a proper solution, shuffles lessons a lot. Streaming gave me an error: Undo for (Lesson(subj...)) does not exist
Code for Streaming:
from(Lesson::class.java)
.filter { it.timeslot != null }
.groupBy({ it.studentGroup }, { it.timeslot!!.day }, ConstraintCollectors.toList())
.filter { group, day, list ->
list.any { it.timeslot!!.number != 1 }
}
.penalize(
"Student must have first lesson",
HardSoftScore.ONE_HARD
) { group, day, list -> list.count { it.timeslot!!.number != 1 } },
Looks like I'm thinking the wrong direction.
https://github.com/Lewik/timetable
Any help will be greatly appreciated.
update: fixed == -> =!
As far as I understand it, I don't think you're enforcing what you intend to enforce. From what I make from your source code, you penalize every studentgroup's first lesson of the day.
What you should do to enforce the intended goal, is to penalize every studentgroup that does NOT have a timeslot with number == 1 but DOES have one (of the same day) where timeslot number != 1.
So something like :
join all Lesson.class instances with all Lesson.class instances where the first lesson's studentGroup equals the second lesson's studentGroup AND the first lesson's timeSlot's day equals the second lesson's timeSlot's day. You obtain a BiConstraintStream<Lesson, Lesson> this way...
from this, filter all Lesson.class instances where the first lesson's timeSlot's number is less than the second lesson's timeSlot number
then penalise the remaining where the first lesson's timeSlot number differs from 1. That equals penalising all of a studentGroup's days where they have some lesson that day without having any lesson that day during the first timeslot.
If I understood you correctly, that's what you wanted ?
I don't know the real source of the problem, but it's about hashCode. The exception was thrown because HashMap with Object key can't find by that Object.
Lesson class:
#Serializable
#NoArg
#PlanningEntity
data class Lesson(
val subject: String,
val teacher: String,
val studentGroup: String,
#PlanningVariable(valueRangeProviderRefs = ["timeslotRange"])
var timeslot: TimeSlot? = null,
#PlanningId
val id: String = UUID.randomUUID().toString(),
)
The implementation above will not work. It could be fixed if I remove data or add override fun hashCode() = Objects.hash(id). #PlanningId does not help here. Kotlin generates hashCode for data classes and seems it not working with optaplanner (or vise versa)
How about using .ifNotExists()?
First, convert student group from a String into a class and add #ProblemFactCollectionProperty List<StudentGroup> on your solution, then do
from(StudentGroup.class)
.ifNotExists(from(Lesson.class).filter(Lesson::isFirstTimeslot),
equals(this -> this, Lesson::getStudentGroup)
.penalize(...);

Spring JPA query for getting similar lists that are similar to a given list

I have a class named Product. Also it is linked to product database table. Database server is PostgreSQL. I am using Spring Boot and Hibernate.
public class Product{
#ToString.Exclude
#ElementCollection
#Nullable
private List<String> labels;
}
The Product class has labels field which represents the objects in the product photo. For example,
List<String> labels = ["apple", "table", "phone"]
My main purpose is that I want to write a SQL query that will get all products that has "similar labels".
What is "similar labels" for me?
For example, let labels1 represents the labels of the product1 and let labels2 represents the labels of the product2.
List<String> labels1 = ["apple", "table", "phone"]
List<String> labels2 = ["apple"]
There is only 1 item which is "apple" that is same in the two lists. So sameItemCounter is equal to 1.
The total distinct item list in two list is equal to,
distinctItemList = ["apple","table","phone"]
So, distinctItemCounter is equal to 3.
If 100*sameItemCounter/distincItemCounter is bigger than the 30 then , the two label lists are "similar labels".
So in this case, 100*sameItemCounter/distincItemCounter is equals to 33,3 which is bigger than the 30, then we can say labels1 and labels2 are "similar labels". So, relatively, we can say product1 and product2 are "similar products".
Although I can make this already with getting all products in the database than make all processes in Spring Boot(backend side), I want to make all this processes with a database query.
I want to an answer which is similar to this form:
public static final String GET_ALL_SIMILAR_PRODUCTS = "query??";
#Query(value = FIND_PROJECTS, nativeQuery = true)
public List<Product> getAllSimilarProducts(Product p); // need to return all "similar products" of p.
How can I do that?

play-slick scala many to many

I have an endpoint lets say /order/ where i can send json object(my order), which contains some products etc, so my problem is i have to first save the order and wait for the order id back from the db and then save my products with this new order id( we are talking many to many relation thats why theres another table)
Consider this controller method
def postOrder = Action(parse.json[OrderRest]) { req => {
Created(Json.toJson(manageOrderService.insertOrder(req.body)))
}
}
this is how my repo methods look like
def addOrder(order: Order) = db.run {
(orders returning orders) += order
}
how can i chain db.runs to first insert order, get order id and then insert my products with this order id i just got?
im thinking about putting some service between my controller and repo, and managing those actions there, but i have no idea where to start
You can use for to chain database operations. Here is an example of adding a table to a db by adding a header row to represent the table and then adding the data rows. In this case it is a simple table containing (age, value).
/** Add a new table to the database */
def addTable(name: String, table: Seq[(Int, Int)]) = {
val action = for {
key <- (Headers returning Headers.map(_.tableId)) += HeadersRow(0, name)
_ <- Values ++= table.map { case (age, value) => ValuesRow(key, age, value) }
} yield key
db.run(action.transactionally)
}
This is cut down from the working code, but it should give the idea of how to do what you want. The first for statement would generate the order id and then the second statement would add the order with that order id.
This is done transactionally so that the new order will not be created unless the order data is valid (in database terms).

How to limit subnodes from each nodes Neo4j Cypher

I am new to Neo4j,I have the following situation
In the above diagram represented a node with label user with sub-nodes having label shops. Each of these sub-nodes have sub-nodes with label items. Each node items has attribute size and the items node is in descending order by size attribute for each node shops as represented in the figure.
Question
I want to get two items node whose size is less than or equal to 17 from each shops .
How to do that? I tried, but its not working the way I need
Here is what I have tried
match (a:user{id:20000})-[:follows]-(b:shops)
with b
match (b)-[:next*]->(c:items)
where c.size<=17
return b
limit 2
Note- These shops node can have thousands of items nodes. So how to find the desired nodes without traversing all thousands of items nodes.
Please help , thanks in advance.
Right now Cypher does not handle this case well enough, I would probably do a java based unmanaged extension for this.
It would look like this:
public List<Node> findItems(Node shop, int size, int count) {
List<Node> results=new ArrayList<>(count);
Node item = shop.getSingleRelationship(OUTGOING, "next").getEndNode();
while (item.getProperty("size") > size && results.size() < count) {
if (item.getProperty("size") <= size) result.add(item);
item = item.getSingleRelationship(OUTGOING, "next").getEndNode();
}
return result;
}
List<Node> results=new ArrayList<>(count*10);
for (Relationship rel = user.getRelationships(OUTGOING,"follows")) {
Node shop = rel.getEndNode();
results.addAll(findItems(shop,size,count));
}
You can avoid having to traverse all items of each shop by grouping them according to size. In this approach, your graph looks like this
(:User)-[:follows]-(:Shop)-[:sells]-(:Category {size: 17})-[:next]-(:Item)
You could then find two items per shop using
match (a:User {id: 20000})-[:follows]-(s:Shop)-[:sells]-(c:Category)
where c.size <= 17
with *
match p = (c)-[:next*0..2]-()
with s, collect(tail(nodes(p))) AS allCatItems
return s, reduce(shopItems=allCatItems[..0], catItems in allCatItems | shopItems + catItems)[..2]
shopItems=allCatItems[..0] is a workaround for a type checking problem, this essentially initializes shopItems to be an empty Collection of nodes.