Seeing Differential of 2 Objects within JSONata - jsonata

I'm using JSONata, and I want to see if I can compare 2 objects to see the differences.
I can see there are functions to get a Boolean for it the 2 objects are different but I'm looking to actually get the differences between the 2 objects.
Any suggestions? Thanks

It would be nice to see the expected output for a pair of example objects to provide a proper solution, but speculatively you could do something like this to figure out what keys are not matching for 2 objects:
(
$object_a_keys := $keys(object_a);
$object_b_keys := $keys(object_b);
$object_a_keys_not_matching_object_b :=
$filter($object_a_keys, function($key) {
$not($key in $object_b_keys)
or $lookup(object_a, $key) != $lookup(object_b, $key)
});
$object_b_keys_not_matching_object_a :=
$filter($object_b_keys, function($key) {
$not($key in $object_a_keys)
or $lookup(object_a, $key) != $lookup(object_b, $key)
});
/* keys that don't match between the two objects */
$distinct(
$append(
$object_a_keys_not_matching_object_b,
$object_b_keys_not_matching_object_a
)
)
)
You can play with the solution here: https://stedi.link/3zEoNqC

Related

eXist-db collection sort

Following on from this question about navigating collections using pos:
In eXist 4.7 I have a collection in myapp/data/ which contains thousands of TEI XML documents. I use the following solution from Martin Honnen to get the document before and after a certain document
let $data := myapp/data
let $examples := $data/tei:TEI[#type="example"]
for $example at $pos in $examples
where $example/#xml:id = 'TC0005'
return (
$examples[$pos - 1],
$example
$examples[$pos + 1]
)
With this I would have expected $examples[$pos - 1] to produce document 'TC0004' and $examples[$pos + 1] to produce 'TC0006' (based on the sort order seen in eXide collection navigation view for example). They do not, producing the inverse instead.
Honnen and Michael Kay responded that
ordering of documents within a collection is very much processor-dependent
Applying an order by $example/#xml:id ascending clause did not change the result for the better.
So, the question is how can I impose an alpha-numeric order on $data?
Many thanks.
It seems at the XQuery level you can change let $examples := $data/tei:TEI[#type="example"] to
let $examples := sort($data/tei:TEI[#type="example"], (), function($e) { $e/#xml:id })
(assuming the XQuery/XPath 3.1 higher-order sort function is available) or to
let $examples := for $e in $data/tei:TEI[#type="example"] order by $e/#xml:id return $e
using the order by clause.
I don't know whether exist-db has some way to impose an order during the creation or during the selection of a collection.
Based on experience with older versions of eXist, the $pos value while going through a loop is not the sorted position order. It is the position while going through.
What you first want to do is create an ordered list, then get the three items from the list you're looking for.
let $data := myapp/data[tei:TEI/#type eq 'example']
let $examples := for $e in $data order by $e/#xml:id ascending return $e
let $pos := index-of($examples/#xml:id, 'TC0005')
return if (count($pos) eq 1) then (
if ($pos gt 1) then $examples[$pos - 1] else (),
$examples[$pos]
$examples[$pos + 1]
) else ()
A potential problem with this approach is that you'll have to sort all items every time. Creating a sorted cached list may alleviate this problem and would also allow for a much more efficient query, where you can use preceding-sibling and following-sibling from the query result.
Another potential solution, if the naming convention for the IDs is consistent, would be to query the before and after IDs.
The check to see if there is one item in $pos is to prevent cases where #xml:id is not unique (yes, that would be against the spec, but it happens in real world data) or no item exists. Keep in mind that index-of returns an array of indexes - 0 or more.

Combine 2 collections (keep the similar ones)

I've several collections, I want to keep only the elements that are present in each collection.
I went through the available methods, but I didn't find anything that would match.
$candidatesByConsultant = Consultant::find(request('consultant_id'))->candidates;
$candidatesByCreation = Candidate::whereBetween('created_at',[Carbon::parse(request('meeting_since')), Carbon::parse(request('meeting_to'))])->get();
Do you have any idea? :)
In order to have values that only present in both collection you must use intersect method:
$result = $candidatesByConsultant->intersect($candidatesByCreation);
The intersect method intersects the values of both collections. You can read it in Laravel's official documentation.
And in order to get have results that are not present in both collection you must use diff method:
$result = $candidatesByConsultant->diff($candidatesByCreation);
The diff method finds differences between collections. You can read it in Laravel's official documentation.
The intersect method may be suitable : https://laravel.com/docs/5.8/collections#method-intersect
Example taken from the documentation:
$collection = collect(['Desk', 'Sofa', 'Chair']);
$intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']);
$intersect->all();
// [0 => 'Desk', 2 => 'Chair']
However, especially if you are trying to intersect multiple collections of Eloquent models, it may not work since the equality between two models is defined by the Model::is() method. Check
https://laravel.com/docs/5.8/eloquent#comparing-models for more information about comparing two Eloquent models.
To handle this, I would do the following, assuming the primary key of your models is id:
$candidatesByConsultant = Consultant::find(request('consultant_id'))->candidates;
$candidatesByCreation = Candidate::whereBetween('created_at',[Carbon::parse(request('meeting_since')), Carbon::parse(request('meeting_to'))])->get();
$candidates = $candidatesByConsultant->merge($candidatesByCreation)->unique("id");
You may check the merge() and unique() documentations.
The built-in for this is $collection->intersect($other), but you can also achieve the desired result with a simple custom filter:
$left = collect([Model::find(1), Model::find(2), Model::find(3)]);
$right = collect([Model::find(1), Model::find(3), Model::find(5)]);
$result = $left->filter(function ($value, $key) use ($right) {
return $right->contains(function ($v, $k) use ($value) {
return $v->id === $value->id;
});
});
This will perform model comparison by id. It is not very performant though. Another approach would be to retrieve two arrays of ids, intersect them and filter the merged sets based on this list:
$left = collect([Model::find(1), Model::find(2), Model::find(3)]);
$right = collect([Model::find(1), Model::find(3), Model::find(5)]);
$merged = $left->merge($right);
$ids = array_intersect($left->pluck('id')->toArray(), $right->pluck('id')->toArray());
$result = $merged->filter(function ($value, $key) use ($ids) {
return in_array($value->id, $ids);
});

Merging multiple objects which uses same id

I'm trying to merge multiple objects (like Receipts, Reports, etc) with Collection->merge().
This is the code I used:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->merge($reports);
This is the result:
The above screenshot shows two elements, but the third element is missing because it has the same id (id: "1") as the first one. What I'm trying to achieve is to display all three of them as a collection.
EDIT:
I need the result to be objects (collection) because I also use the code on my view, where I check the class to determine what to display. Also, I use this function to sort the objects in the collection.
$collection->sort(function($a, $b)
{
$a = $a->created_at;
$b = $b->created_at;
if ($a === $b) {
return 0;
}
return ($a > $b) ? 1 : -1;
});
I know that this is an old question, but I will still provide the answer just in case someone comes here from the search like I did.
If you try to merge two different eloquent collections into one and some objects happen to have the same id, one will overwrite the other. I dunno why it does that and if that's a bug or a feature - more research needed. To fix this just use push() method instead or rethink your approach to the problem to avoid that.
Example of a problem:
$cars = Car::all();
$bikes = Bike::all();
$vehicles = $cars->merge($bikes);
// if there is a car and a bike with the same id, one will overwrite the other
A possible solution:
$collection = collect();
$cars = Car::all();
$bikes = Bike::all();
foreach ($cars as $car)
$collection->push($car);
foreach ($bikes as $bike)
$collection->push($bike);
Source: https://medium.com/#tadaspaplauskas/quick-tip-laravel-eloquent-collections-merge-gotcha-moment-e2a56fc95889
I know i'm bumping a 4 years old thread but i came across this and none of the answers were what i was looking for; so, like #Tadas, i'll leave my answer for people who will come across this. After Looking at the laravel 5.5 documentation thoroughly i found that concat was the go-to method.
So, in the OP's case the correct solution would be:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->concat($reports);
This way every element in the Report collection will be appended to every element in the Receipts collection, event if some fields are identical.
Eventually you could shuffle it to get a more visual appealing result for e.g. a view:
$collection->shuffle();
Another way to go about it is to convert one of your collections to a base collection with toBase() method. You can find it in Illuminate\Support\Collection
Method definition:
/**
* Get a base Support collection instance from this collection.
*
* #return \Illuminate\Support\Collection
*/
public function toBase()
{
return new self($this);
}
Usage:
$receipts = Receipt::all();
$reports = Report::all();
$collection = $receipts->toBase()->merge($reports);
You could put all collections in an array and use this. Depends on what you want to do with the collection.
$list = array();
$list = array_merge($list, Receipt::all()->toArray());
$list = array_merge($list, Report::all()->toArray());

xquery, preserve sort order

Is there any way to preserve sort order in xquery? My problem is that the data has to get passed to the MVC framework's get-response() function on the return, so I think it's automatically reverting to document order. I thought that doing the sort right in the first paramter of the subsequence() function would capture the first 'n' items after they are sorted, but it does not. I also tried having the $search-results parameter sorted before the call to subsequence(), but that did not work either. See the following code:
let $data :=
<figures count="{$count}"
mediatypes="{$mtypes}"
start="{$start}"
end="{$start+$myns:image-paging-default}"
page="{$page}"
increment="{$myns:image-paging-default}"
total-pages="{
if ($count lt $myns:image-paging-default) then
1
else
ceiling(($count + 1) div $myns:image-paging-default)
}"
{
subsequence(
( for $item in ($search-results)
order by $item//figure/#ftype descending
return $item),
$start,
$myns:image-paging-default)
}
</figures>
let $sidebar := xdmp:get-server-field('imagefacets')
return utils:get-response($req, ($data,$sidebar) )

LINQ / EF - Only return items based on a list of ID's

I have a list of type int, which contains ID's. For example it may contain 1,2,5,8,16 or 2,3,6,9,10,12 etc..
I then want to return all of my "Enquiries" based on the ID's stored in my list (called vehicles) and return them as a list, something like:
var enquiries = context.Enquiries.Where(x => x.EnquiryID == vehicles.Any()).ToList();
But obviously this doesn't work, is there something similar I can do?
You likely want to use Contains. Contains (in Linq2SQL or EF) will be transformed into a WHERE/IN clause.
enquiries = context.Enquiries
.Where( x => vehicles.Contains( x.EnquiryID ) )
.ToList();

Resources