mongoDB - $lookup - code 4570: arguments to $lookup must be strings - string

I'm trying to write a Mongodb aggregation that do the following:
I have two collections, where there are two fields that apear in both collections, and I want to write a query that adds based on wether both of the fields are equal in both collections, a field that exists only in one of them.
Say that the first collection is A and the second is B. Say that these are the fields in both of them:
A: x, y, z, w.
B: x, y, u.
I want to add u to A, based on where x, y are both the same in A and B.
if this is a record in A: x=1, y=2, z=3, w=4 and B: x=1, y=2, u=6, I want:
A: x=1, y=2, z=3, w=4, u=6. (because x, y are equal in both collections).
I wrote the following:
db.A.aggregate([
{
"$lookup":{
"from":"B",
"let":{
"x":"$x",
"y":"$y"
},
"pipeline":[
{
"$match":{
"$expr":{
"$and":[
{
"$eq":[
"$x",
"$$x"
]
},
{
"$eq":[
"$y",
"$$y"
]
}
]
}
}
}
],
"as":"res"
}
}
])
I need to add a project part, but the problem is that i get the following error (means res is not working):
"message" : "arguments to $lookup must be strings, let: { x: '$x', y: '$y' } is type object"
and code 4570
Notes:
I use mongo version 4.4.5
x in my db is of type string and y is of type int32. I tried to convert y to string, this is not the problem.
If someone knows how to help I would appreciate that.

Related

In-place modification, insertion or removal in the same function for hash maps in Rust

Say I have a hash map m: HashMap<K, V>, a key k: K and a value v: V, and would like to do the following:
If m does not contain a value at index k, insert v at index k.
If m contains a value w at index k, apply a function fn combine(x: V, y: V) -> Option<V> to v and w, and:
If the result is None, remove the entry at index k from m.
If the result is Some(u), replace the value at index k by u.
Is there a way to do this "in-place", without calling functions that access, modify or remove the value at k multiple times?
I would also like to avoid copying data, so ideally one shouldn't need to clone v to feed the clones into insert and combine separately.
I could rewrite combine to use (mutable) references (or inline it), but the wish of not copying data still remains.
Digging deeper into the Entry documentation, I noticed that the variants of the Entry enum offer functions to modify, remove or insert entries in-place.
After taking std::collections::hash_map::Entry into scope, one could do the following:
match m.entry(k) {
Entry::Occupied(mut oe) => {
let w = oe.get_mut();
match combine(v, w) {
Some(u) => { *w = u; },
None => { oe.remove_entry(); },
}
},
Entry::Vacant(ve) => { ve.insert(v); },
}
(Here is a PoC in the Rust playground.)
This, however, requires combine to take a (mutable) reference as its second argument (which is fine in my case).
I managed to do it in one access, one write and one key-deletion in total in the worst case. The last key-deletion should not be necessary, but I'm not certain it can be done. I gave it my best so far. I hope this helps!
Okay, so I think we want to use the Entry API.
The full method list for Entry is here.
I think we'd do it in the following order:
If m contains a value w at index k: (two more steps)
Or insert v at index k.
This can be done by using .and_modify and then .or_insert. Something like this:
let map = // ... Initialize the map
// Do stuff to it
// ...
// Our important bit:
let mut delete_entry = false;
map.entry(k)
.and_modify(|w| { // If the entry exists, we modify it
let u = combine(v, w);
match u {
Some(y) => *w = y;
None => delete_entry = true;
}
}
)
.or_insert(v); // If it doesn't, we insert v
if delete_entry {
map.remove(k);
}
I don't think there's a way to do all three things without that last map.remove access, so this is my best attempt for now.

Sum of nested values in Alloy

I'm starting with definitions similar to these below
sig Sub { vals : set Int }
sig Top { subs : set Sub }
I'd like an expression that can produce the sum of all values contained inside something of type Top. Top when written as a nested set, would be something like {{3, 4}, {7}}. The result of the nested sum in this case should be 14.
This function of course just gives the number of elements in the outer set.
fun allsum[t: Top] : one Int { #t }
I believe I need to use the built-in sum function and a set comprehension, but Alloy syntax is still somewhat arcane to me.
For this, you need a nested sum expression:
fun allsum[t: Top] : Int {
sum s: t.subs | (sum v: s.vals | v)
}
The general format is:
sum e: <set> | <expression involving e>

sort strings according to the highest

can I combine below closures into one or do this in a more functional and elegant way in groovy. I am using the sortMethod in some other places( for testing purpose) too.
for eg : countAndMap should take
["a b c a a c" , "b b c"] and return[x1 : [a:3,c:2,b:1] , x2 : [b:2,c:1]]
def countAndMap(List<String> stringList) {
stringList.withIndex().collect { String s, Integer i -> [(num.call(i)): count.call(s)] }
}
Closure count = {sortMethod.call(it.split().countBy {it}) }
Closure sortMethod = { it.sort { x, y -> x.value <=> y.value } }
Closure num = { "x ${it + 1}".toString()}
there are no errors but I wonder if it's possible to do it in a more functional way
I am not sure what you mean with "more functional", but you could use a fold operation (called inject in groovy):
list = ["a b c a a c" , "b b c"]
def createSortedHistogram(String toCount) {
toCount
.split() // Create list of words
.inject([:]){ acc, word -> acc[word] = 1 + (acc[word] ?: 0);acc} // create histogram
.sort{-it.value} // sort histogram map by value desc
}
def countAndMap(List<String> list) {
list.withIndex().collectEntries{ sublist, i -> ["x ${i+1}": createSortedHistogram(sublist)] }
}
countAndMap(list)
I think the most interesting part is the inject method.
This solution uses the initial value [:] in order to use a map as result. In each iteration the inject operation either adds a new entry with value 1 to the map (if the word/key does not exist in the map) or increases the value of the word/key if it is already present in the map.
See the inject definition from Collections GroovyDoc.
public Object inject(Object initialValue, Closure closure) - Iterates through the given Collection, passing in the initial value to the 2-arg closure along with the first item. The result is passed back (injected) into the closure along with the second item. The new result is injected back into the closure along with the third item and so on until the entire collection has been used. Also known as foldLeft or reduce in functional parlance.

AQL: Dynamic query with nested array dates

I have been trying to get this dynamic query to work with dates as shown below in ArangoDB 3.1.
This works perfectly when I'm not trying to query dates, but returns an empty list as soon as I try to query with a date like below...
{
query:
'For c IN ##collectionName
FILTER ( c.#p0 == #v0 AND c.#p1 >= #v1 AND c.#p2 <= #v2 )
LIMIT #count RETURN c ',
bindVars: {
'#collectionName': 'Event',
p0: 'isPublished',
v0: true,
p1: 'dates[*].startsAt',
v1: '2018-06-01T04:00:00.000Z',
p2: 'dates[*].startsAt',
v2: '2018-07-01T03:59:59.999Z',
count: 9
}
}
Need some help getting past this
There are mistakes in your query, but they are actually not related to dates:
dates[*].startsAt is not a valid attribute path, but a shorthand expression for FOR date IN dates RETURN date.startsAt, which returns an array
The comparison operator >= does not work on arrays as you may think. null, true, false and every number and string are less than any array, see Type and Value Order. Your timestamp array will always be greater than any given timestamp string. What you probably want instead is an array comparison operator like ALL >=.
An expression dates[*].startsAt can not be used as bind parameter. With a document structure without array like { "date": { "startsAt": "..." } } it would be perfectly fine to bind ["date", "startsAt"] as p1 or p2. Note how the bind parameter value is an array of strings. "date.startsAt" on the other hand would describe the path for a top-level attribute
{ "date.startsAt": ... } and not a nested attribute startsAt of the top-level attribute date like { "date": { "startsAt": ... } }.
What your do with dates[*].startsAt is describing a top-level attribute like
{ "dates[*].startsAt": ... }, which does not exist. ["dates[*]", "startsAt"] does not work either. If you want to use the array expansion expression, then you have to write it like c.#p1a[*].#p1b in your query and use the bind parameters { "p1a": "dates", "p2a": "startsAt" }.
Query:
FOR c IN ##collectionName
FILTER c.#p0 == #v0
FILTER c.#p1a[*].#p1b ALL >= #v1
FILTER c.#p2a[*].#p2b ALL < #v2
LIMIT #count
RETURN c
bindVars:
{
"#collectionName": "Event",
"p0": "isPublished",
"v0": true,
"p1a": "dates",
"p1b": "startsAt",
"v1": "2018-06-01T04:00:00.000Z",
"p2a": "dates",
"p2b": "startsAt",
"v2": "2018-07-01T04:00:00.000Z",
"count": 9
}

TclOO: object equals

What is idiomatic pattern for TclOO object equals implementation?
Perhaps compare concatenated sorted lists of all properties?
Are there analogs to Scala case classes?
TclOO defines no equality system for you by design; as the objects are in general modifiable, there's no automatic notion that would apply other than object identity, and you can just compare the name of the object to get that (or the results of info object namespace $theObj, if you're being very paranoid; I think Tcl 8.7 will provide more options, but that's not yet accepted).
If you want to define an equality system such as you are proposing, you could do this:
oo::class create PropertyEquals {
method equals {other} {
try {
set myProps [my properties]
set otherProps [$other properties]
} on error {} {
# One object didn't support properties method
return 0
}
if {[lsort [dict keys $myProps]] ne [lsort [dict keys $otherProps]]} {
return 0
}
dict for {key val} $myProps {
if {[dict get $otherProps $key] ne $val} {
return 0
}
}
return 1
}
}
Then you just need to define a properties method on the class you might be comparing, and mix in the equals method from the above.
oo::class create Example {
mixin PropertyEquals
variable _x _y _z
constructor {x y z} {
set _x $x; set _y $y; set _z $z
}
method properties {} {
dict create x $_x y $_y z $_z
}
}
set a [Example new 1 2 3]
set b [Example new 2 3 4]
set c [Example new 1 2 3]
puts [$a equals $b],[$b equals $c],[$c equals $a]; # 0,0,1
Note that Tcl doesn't provide complex collection classes like some other languages (because it has array-like and mapping-like open values) and so doesn't need an object equality (or content hashing) framework to support that.

Resources