Java 8 list manipulation - java-8

I'm trying to do some basic map/filter operations on a list in a ListChangeListener.onChanged(Change<? extends Place>) method and I can get it working using the old-fashioned "iterate and do some ifs" way, but I wanted to try to write it using the stream() method from java 8. The commented part doesn't give the same result though, it fails to filter out the categories correctly (and yes, I have a working implementation of equals(Object) for Category
for (Place p : change.getAddedSubList()) {
if (!categories.contains(p.getCategory())) {
categories.add(p.getCategory());
}
}
// List<Category> addedCategories = change.getAddedSubList().stream()
//                      .map(Place::getCategory)
//                      .filter((c) -> { return !categories.contains(c); })
//   .collect(Collectors.toList());
// categories.addAll(addedCategories);

That's because in the first version, once you have added a category to the list, a subsequent occurrence of this category isn't added a second time: you have already added it to the list. The second version doesn't do the same thing. So you need to make sure categories are unique in the stream:
change.getAddedSubList().stream()
.map(Place::getCategory)
.distinct()
.filter(c -> !categories.contains(c))
.forEachOrdered(c -> categories.add(c));
Note that you also don't need to collect to a temporary list.

Duplicates in you stream may lead to duplicates in the categories list, when they are not contained in the categories list beforehand, since the filter method is applied for all items, before one of them is inserted.
One solution would be to insert a call to .distinct() in your Stream, another way to collect via Collectors.toSet().

Related

Adding a custom sorting to listing with an aggregate in shopware 6

I am trying to build a custom sorting for the product listings in shopware 6.
I want to include a foreign table (entity is: leasingPlanEntity), get the min of one of the fields of that table (period_price) and then order the search result by that value.
I have already built a Subscriber, and try it like that, what seems to work.
public static function getSubscribedEvents(): array
{
return [
//ProductListingCollectFilterEvent::class => 'addFilter'
ProductListingCriteriaEvent::class => ['addCriteria', 5000]
];
}
public function addCriteria(ProductListingCriteriaEvent $event): void
{
$criteria = $event->getCriteria();
$criteria->addAssociation('leasingPlan');
$criteria->addAggregation(new MinAggregation('min_period_price', 'leasingPlan.periodPrice'));
// Sortierung hinzufügen.
$availableSortings = $event->getCriteria()->getExtension('sortings') ?? new ProductSortingCollection();
$myCustomSorting = new ProductSortingEntity();
$myCustomSorting->setId(Uuid::randomHex());
$myCustomSorting->setActive(true);
$myCustomSorting->setTranslated(['label' => 'My Custom Sorting at runtime']);
$myCustomSorting->setKey('my-custom-runtime-sort');
$myCustomSorting->setPriority(5);
$myCustomSorting->setFields([
[
'field' => 'leasingPlan.periodPrice',
'order' => 'asc',
'priority' => 1,
'naturalSorting' => 0,
],
]);
$availableSortings->add($myCustomSorting);
$event->getCriteria()->addExtension('sortings', $availableSortings);
}
Is this already the right way to get the min(periodPrice)? Or is it taking just a random value out of the leasingPlan table to define the sort-order?
I didn't find a way, to define the min_period_price aggregate value in the $myCustomSorting->setFields Methods.
Update 1
Some days later, I asked a less complex question in the shopware community on slack:
Is it possible to use the DAL to define a subquery for an association in the product-listing?
It should generate something like:
FROM
JOIN (
SELECT ... FROM ... WHERE ... GROUP BY ... ORDER BY ...
) AS ...
The answer there was:
Don't think so
Update 2
I also did an in-deep anlysis of the DAL-Query-Builder, and it really seems to be not possible, to perform a subquery with the current version.
Update 3 - Different approach
A different approach might be, to define custom fields in the main entity. Every time a change is made on the main entity, the values of this custom fields should be recalculated.
It is a lot of overhead work, to realize this. Especially when the fields you are adding, are dependend on other data like the availability of a product in the store, for example.
So check, if it is worth the extra work. Would be better, to have a solution for building subqueries.
Unfortunately it seems that in your case there is no easy way to achieve this, if I understand the issue correctly.
Consider the following: for each product you can have multiple leasingPlan entities, and I assume that for a given context (like a specific sales channel or listing) that still holds. This means that you would have to sort the leasingPlan entities by price, then take the one with the lowest price, and then sort the products by their lowest-price leasingPlan's price.
There seems to be no other way to achieve that, and unfortunately for you, sorting is applied at the end, even if it is sort of a subquery.
So, for example, if you have the following snippet
$criteria = $event->getCriteria();
$criteria->addAssociation('leasingPlan');
$criteria->getAssociation('leasingPlan')
->addSorting(new FieldSorting('price', FieldSorting::ASCENDING))
->setLimit(1)
;
The actual price-sorting would be applied AFTER the leasingPlan entities are fetched - essentially the results would be sorted, meaning that you would not get the cheapest leasing plan per product, instead getting the first one.
You can only do something like that with filters, but in this case there is nothing to filter by - I assume you don't have one leasingPlan per SalesChannel or per language, so that you could limit that list to just one entry that could be used for sorting
That is not to mention that this could not be included in a ProductSortingEntity, but you could always work around that by plugging into the appropriate events and modifying the criteria during runtime
I see two ways to resolve your issue
Making another table which would store the cheapest leasingPlan per product and just using that as your association
Storing the information about the cheapest leasingPlans in e.g. cache and using that for filtering (caution: a mistake here would probably break the sorting, for example if you end up with too few or too many leasingPlans per product)
public function applyCustomSorting(ProductListingCriteriaEvent $event): void
{
// One leasingPlan per one product
$cheapestLeasingPlans = $this->myCustomService->getCheapestLeasingPlanIds();
$criteria = $event->getCriteria();
$criteria->addAssociation('leasingPlan');
$criteria->getAssociation('leasingPlan')
->addSorting(new FieldSorting('price', FieldSorting::ASCENDING))
->addFilter(new EqualsAnyFilter('id', $cheapestLeasingPlans))
;
}
And then you could sort by
$criteria->addSorting(new FieldSorting('leasingPlan.periodPrice', FieldSorting::ASCENDING));
There should be no need to add the association manually and to add the aggregation to the criteria, that should happen automatically behind the scenes if your custom sorting is selected in the storefront.
For more information refer to the official docs.

Why does my forget() method not remove object from collection?

I have a collection of "Tickets", using the random collection utility method I select one from the list. The "Tickets" collection should now remove (or forget) that randomly selected ticket so I can further process that collection. Using the forget method doesn't appear to do what is described in the documentation or (more likely I'm missing something).
Can someone spot whats wrong in my code?
$tickets = Tickets::all();
$total_winners = 5;
$selected_tickets = $tickets->random($total_winners);
$jackpot_winner = $selected_tickets->random();
$selected_tickets->forget($jackpot_winner->id); // this line should remove the $jackpot_winner
When I print the contents of $selected_tickets on lines 3 and lines 5, they have the exact same items, including the $jackpot_winner.
Forget function uses the collection key not the id from the model. To achieve what you want you may use this method:
$selected_tickets = $selected_tickets->except($jackpot_winner->id);
https://laravel.com/docs/8.x/collections#method-except

Asp.net Core 5.0 Linq Take(1).ElementAt(index)

I have a long Linq query and I'm trying to take one data in any index of that query.
My query is :
public IEnumerable<WebFairField> WebFairFieldForFair(Guid ID,int index)
{
return TradeTurkDBContext.WebFairField.Where(x => x.DataGuidID==ID)
.Include(x => x.Category)
.ThenInclude(x=>x.MainCategory).AsSplitQuery()
//
.Include(x=>x.FairSponsors)
.ThenInclude(x=>x.Company)
.ThenInclude(x=>x.FileRepos).AsSplitQuery()
//
.Include(x=>x.WebFairHalls.Take(1).ElementAt(index)) //Thats the point where i stuck*
.ThenInclude(x=>x.HallSeatingOrders)
.ThenInclude(x=>x.Company)
.ThenInclude(x=>x.FileRepos).AsSplitQuery()
//
.Include(x=>x.HallExpertComments).AsSplitQuery()
.Include(x=>x.Products).AsSplitQuery()
.Include(x=>x.FairSponsors).AsSplitQuery()
.AsNoTrackingWithIdentityResolution()
.ToList();
}
when I do that it gives me an error : Collection navigation access can be filtered by composing Where, OrderBy,ThenBy,Skip or Take operations.
I know I have to sort that data but I don't know how to do it. Can anyone show me how should I sort my data of that query ?
Thanks for any suggestion!!
The error
As you have mentioned, the line of
.Include(x=>x.WebFairHalls.Take(1).ElementAt(index)) //Thats the point where i stuck*
is causing the error. Basically you Take the first element and then try to call ElementAt. This is a problem technically, because you need to convert your collection to IEnumerable in order to be able to call ElementAt.
It is also a logical error, since if you take a single element, then it does not make sense to try and call ElementAt for it.
Skip
As Guru Strong pointed out, you can Skip, as Skip(index - 1).Take(1) which skips the first index - 1 elements and then takes the next one, which is the index'th element.
Sort
If you need to sort, call OrderBy. If you need several sorting criteria, then use ThenBy.

dart - Sort a list of futures

So I have a set of options that each contain an int value representing their ordinal.
These options are stored in a remote database with each option being a record.
As such when I fetch them from the db I end up with a list of future:
e.g. List<Future<Option>>
I need to be able to sort these Options.
The following dart pad shows a simplified view of what I'm trying to achieve:
https://dartpad.dartlang.org/a5175401516dbb9242a0edec4c89fef6
The Options MUST be futures.
My original solution was to copy the Options into a list, 'complete' them, then sort the list.
This however caused other problems and as such I need to do an 'insitu' sort on the original list.
You cannot sort the futures before they have completed, and even then, you need to extract the values first.
If you need to have a list of futures afterwards, this is what I would do:
List<Future<T>> sortFutures<T>(List<Future<T>> input, [int compare(T a, T b)]) {
var completers = [for (var i = 0; i < input.length; i++) Completer<T>()];
Future.wait(input).then((values) {
values.sort(compare);
for (int i = 0; i < values.length; i++) {
completers[i].complete(values[i]);
}
});
return [for (var c in completers) c.future];
}
This does not return the original futures because you don't know the ordering at the time you have to return them. It does return futures which complete with the same value.
If any of the futures completes with an error, then this blows up. You'll need more error handling if that is possible.
Gentlfolk,
thanks for the help.
julemand101 suggestion of using Future.wait() lead me to the answer.
It also helped me better understand the problem.
I've done a new gist that more accurately shows what I was attempting to do.
Essentilly when we do a db request over the network we get an entity back.
The problem is that the entity will often have references to other entities.
This can end in a whole tree of entities needing to be returned.
Often you don't need any of these entities.
So the solution we went for is to only return the database 'id' of each child entity (only the immediate children).
We then store those id's in a class RefId (see below).
The RefId is essentially a future that has the entities id and knows how to fetch the entity from the db.
When we actually need to access a child entity we force the RefId to complete (i.e. retrieve the entity across the network boundary).
We have a whole caching scheme to keep this performant as well as the ability to force the fetching of child elements, as part of the parent request, where we know up front they will be needed.
The options in my example are essentially menu items that need to be sorted.
But of course I can't sort them until they have been retrieved.
So a re-written example and answer:
https://dartpad.dartlang.org/369e71bb173ba3c19d28f6d6fec2072a
Here is the actual IdRef class we use:
https://dartpad.dartlang.org/ba892873a94d9f6f3924436e9fcd1b42
It now has a static resolveList method to help with this type of problem.
Thanks for your assistance.

datamapper: get object order by relationships fields

I use Codeigniter with datamapper orm and have a problem
this are my models:
mailing -> has many row
row -> has many cell
cell -> has many version
version has one created and one updated field.
I want to get the last 10 mailings order by last version changes created or updated..
I thought to do it like this:
$versions = new Version();
now get last 10 versions order by created or updated
and distinct by mailing_id and now get all mailings to show...
like this: ?
foreach ($version as $v)
{
$v->mailing->get();
}
thx for helping
Yes, you can call ->get() on every related model inside a loop but this would generate a n+1 query scenario and be slow if you are looping over lots of version rows.
You can use the include_related to get full Mailing instances loaded with data when you query Versions in one step (with a join behind a curtain) like this:
$versions = new Version;
$versions->order_by(...)->limit(...); // add your ordering and limiting as before
$versions->include_related('mailing', null, true, true);
// include related mailings, with of their fields and create instances, see
$versions->get();
foreach ($versions as $version) {
// now the $version->mailing is a Mailing instance loaded with the related data
print $version->mailing->id
}

Resources