Simpler way to get duplicates from a list in groovy - groovy

Given the list:
list: [
object1: {
id: 22,
name: "Tom"
},
object2: {
id: 12,
name: "Mary"
},
object3: {
id: 44,
name: "Tom"
}
]
Instead of using nested loops, is there any other one-liner that would get ONLY duplicated names from this list ? So the return list would be ["Tom"]

Another way would be to group them with a count, and find all the ones where the count is greater than one:
list.name.countBy { it }.findResults { it.value > 1 ? it.key : null }
Or indeed as #Daniel says:
list.values().name.countBy { it }.findResults { it.value > 1 ? it.key : null }
Depending on your structure...

Close to a oneliner is the below code. The idea is to create a list of unique named elements, and remove them from the original list.
def list = [
[ id: 22, name: "Tom"],
[ id: 12, name: "Mary"],
[ id: 44, name: "Tom"]
]
def unique = list.toUnique { a, b -> a.name <=> b.name }
list.removeAll(unique)
list*.name
removeAll returns booleon, so that complicates it if you need 1 line of code, the unique variable can be foldet into the method call.
Notice, .toUnique() returns a new collection .unique() removes from the collection called on.

Related

ArangoDB Retrieve parent object based on multiple filters on children properties

I have a Person object as follows
{
name: 'John Doe'
properties:
[
{
name: 'eyeColor',
value: 'brown'
},
{
name: 'age',
value: 25
},
{
name: 'interest',
value: 'reading'
},
{
name: 'interest',
value: 'diving'
}
]
}
Now I want to be able to filter my object based on multiple properties. In pseudocode:
Return all people for which
there exists any p.property such that
p.property.propertyname == 'interest'
AND p.property.propertyvalue == 'reading'
AND there exists any p.property such that
p.property.propertyname == 'age'
AND p.property.propertyvalue < 30
What is the most concise and extensible (I want to be able to apply N of these filters) of doing this without having too many intermediate results?

MongoDB Update (Aggregation Pipeline) using values from nested nested Arrays

I'm trying to update a field (totalPrice) in my document(s) based on a value in a nested, nested array (addOns > get matching array from x number of arrays > get int in matching array[always at pos 2]).
Here's an example of a document with 2 arrays nested in addOns:
{
"_id": ObjectID('.....'),
"addOns": [
["appleId", "Apples", 2],
["bananaID", "Bananas", 1]
],
"totalPrice": 5.7
}
Let's say the price of bananas increased by 20cents, so I need to look for all documents that have bananas in its addOns, and then increase the totalPrice of these documents depending on the number of bananas bought. I plan on using a updateMany() query using an aggregation pipeline that should roughly look like the example below. The ??? should be the number of bananas bought, but I'm not sure how to go about retrieving that value. So far I've thought of using $unwind, $filter, and $arrayElemAt, but not sure how to use them together. Would appreciate any help!
db.collection('orders').updateMany(
{ $elemMatch: { $elemMatch: { $in: ['bananaID'] } } },
[
{ $set: {'totalPrice': { $add: ['$totalPrice', { $multiply: [{$toDecimal: '0.2'}, ???] } ] } } }
]
I'm not exactly sure whats my mongo version is, but I do know that I can use the aggregation pipeline because I have other updateMany() calls that also use the pipeline without any issues, so it should be (version >= 4.2).
**Edit: Thought of this for the ???, the filter step seems to work somewhat as the documents are getting updated, but the value is null instead of the updated price. Not sure why
Filter the '$addOns' array to return the matching nested array.
For the condition, use $eq to check if the element at [0] ('$$this.0') matches the string 'bananaID', and if it does return this nested array.
Get the array returned from step 1 using $arrayElemAt and position [0].
Use $arrayElemAt again on the array from step 3 with positon [2] as it is the index of the quantity element
{ $arrayElemAt: [{ $arrayElemAt: [ { $filter: { input: "$addOns", as:'this', cond: { $eq: ['$$this.0', 'bananaID'] } } } , 0 ] }, 2 ] }
Managed to solve it myself - though only use this if you don't mind the risk of updateMany() as stated by Joe in the question's comments. It's very similar to what I originally shared in **Edit, except you cant use $$this.0 to access elements in the array.
Insert this into where the ??? is and it'll work, below this code block is the explanation:
{ $arrayElemAt: [{ $arrayElemAt: [ { $filter: { input: "$addOns", as:'this', cond: { $eq: [{$arrayElemAt:['$$this', 0]}, 'bananaID'] } } } , 0 ] }, 2 ] }
Our array looks like this: [["appleId", "Apples", 2], ["bananaID", "Bananas", 1]]
Use $filter to return a subset of our array that only contains the elements that matches our condition - in this case we want the array for bananas. $$this represents each element in input, and we check whether the first element of $$this matches 'bananaID'.
{ $filter: { input: "$addOns", as:'this', cond: { $eq: [{$arrayElemAt:['$$this', 0]}, 'bananaID'] } } }
// how the result from $filter should look like
// [["bananaID", "Bananas", 1]]
Because the nested banana array will always be the first element, we use $arrayElemAt on the result from step 2 to retrieve the element at position 0.
// How it looks like after step 3: ["bananaID", "Bananas", 1]
The last step is to use $arrayElemAt again, except this time we want to retrieve the element at position 2 (quantity of bananas bought)
This is how the final updateMany() query looks like after steps 1-4 are evaluated.
db.collection('orders').updateMany(
{ $elemMatch: { $elemMatch: { $in: ['bananaID'] } } },
[
{ $set: {'totalPrice': { $add: ['$totalPrice', { $multiply: [{$toDecimal: '0.2'}, 1] } ] } } }
], *callback function code*)
// notice how the ??? is now replaced by the quantity of bananas which is 1

Mongodb: Count specific objects in collections

I want to count specific objects if present in collections like:
{
id: 1,
obj1: {...},
obj2: {...}
},{
id: 2,
obj2: {...}
},{
id: 3,
obj1: {...},
obj3: {...}
}
In above example i need the sum of count of objects(i.e. obj1, obj2, obj3). And query should return 5 in above scenario.
You can try the below $group aggregation.
$cond with $ifNull expression to check for existence of field, output 1 when present 0 when absent.
inner $sum to count values in each document with outer $sum to sum the values across collection.
db.col.aggregate([
{
"$group":{
"_id":null,
"count":{
"$sum":{
"$sum":[
{"$cond":[{"$ifNull":["$obj1",false]},1,0]},
{"$cond":[{"$ifNull":["$obj2",false]},1,0]},
{"$cond":[{"$ifNull":["$obj3",false]},1,0]}
]
}
}
}
}
])

AQL filter by array of IDs

If I need to filter by array of IDs, how would I do that using bindings?
Documentation does not provide any hints on that.
for c in commit
filter c.hash in ['b0a3', '9f0eb', 'f037a0']
return c
Updating the answer to deal with bindings reference that I missed.
LET commit = [
{ name: "111", hash: "b0a3" },
{ name: "222", hash: "9f0eb" },
{ name: "333", hash: "asdf" },
{ name: "444", hash: "qwer" },
{ name: "555", hash: "f037a0" }
]
FOR c IN commit
FILTER c.hash IN #hashes
RETURN c
The key is that when you send the bind param #hashes, it needs to be an array, not a string that contains an array.
If you use the AQL Query tool via the ArangoDB Admin Tool, make sure you click the "JSON" button in the top right to ensure the parameter hashes has the value
["b0a3", "9f0eb", "f037a0"] and not
"['b0a3', '9f0eb', 'f037a0']"
If you want to send a string as a parameter such as "b0a3","9f0eb","f037a0", so { "hashes": "\"b0a3\",\"9f0eb\",\"f037a0\"" } as bind parameter, then you can split the string into an array like this:
LET commit = [
{ name: "111", hash: "b0a3" },
{ name: "222", hash: "9f0eb" },
{ name: "333", hash: "asdf" },
{ name: "444", hash: "qwer" },
{ name: "555", hash: "f037a0" }
]
FOR c IN commit
FILTER c.hash IN REMOVE_VALUE(SPLIT(#hashes, ['","', '"']), "")
RETURN c
This example will take the string #hashes and then SPLIT the contents using "," and " as delimiters. This converts the input variable into an array and then the query works as expected. It will also hit an index on the hash attribute.
The delimiters are enclosed with single quote marks to avoid escaping, which would also be possible but less readable: ["\",\"", "\""]
Note that "," is listed first as delimiter, so that the result of the SPLIT is
[ "", "9f0eb", "b0a3", "f037a0" ] instead of
[ "", ",", "9f0eb", "b0a3", "f037a0" ].
The empty string element caused by the first double quote mark in the bind parameter value, which would make the query return commit records with an empty string as hash, can be eliminated with REMOVE_VALUE.
The recommended way is to pass ["b0a3", "9f0eb", "f037a0"] as array however, as shown at the beginning.
like this:
with person FOR id in ["person/4201061993070840084011","person/1001230840198901011999","person/4201008406196506156918"]
FOR v,e,p In 1..1 ANY id
relation_samefamily,stay,relation_internetbar,relation_flight,relation_train
OPTIONS {
bfs:true
}
FILTER (p.edges[*]._from ALL IN ["person/42010619930708400840084011","person/10012310840989084001011999","person/4201060840196506156918"] and p.edges[*]._to ALL IN ["person/4201061993070808404011","person/1001231908408901011999","person/4200840106196506156918"])
RETURN {v,e}

CouchDB group view to keep string key and value

I have a view with documents in the form of {key:[year,month,day,string],value:int}:
{
rows:[
{
key: [
2016,
4,
30,
"String1"
],
value: 20
},
{
key: [
2016,
4,
30,
"String2"
],
value: 7
},
{
key: [
2016,
4,
30,
"String3"
],
value: 13
},{
key: [
2016,
5,
1,
"String1"
],
value: 10
},
{
key: [
2016,
5,
1,
"String4"
],
value: 12
},{
key: [
2016,
5,
2,
"String1"
],
value: 3
},
]}
From this I use startkey and endkey to get a range of values by date. My issue is then grouping the documents I get returned by the key string, and summing the value int. The rest of the key may or may not be present it does not matter. So far with group levels I have only been able to sum values per date key.
When rendered in a table I get something like:
What I want is:
So I ended up reducing in my controller with javascript like:
$scope.reduceMap = function (rows) {
var reducedMap = {};
var sortableArray = [];
for (var i = 0; i < rows.length; i++) {
var key = rows[i].key[3];
if (!reducedMap.hasOwnProperty(key)) {
reducedMap[key] = {key: key, value: rows[i].value};
} else {
reducedMap[key] = {key: key, value: rows[i].value + reducedMap[key].value};
}
}
for (var k in reducedMap) {
sortableArray.push(reducedMap[k]);
}
return sortableArray;
};
Since I asked for a CouchDB answer, I will leave this here but not accept it.
If you emit view's key as: string, year, month, day and use a built in reduce function _sum, then the following URL example gives you the desired result:
http://localhost:5984/text/_design/search/_view/by_text?startkey=["",2016,1,1]&endkey=[{},2016,1,1]&group_level=1
Your date search criteria is specified as normal, but the first part of the key is basically any string. Then grouping level 1 and reducing using sum gives you the count of string occurrences withing date range grouped by string.

Resources