Baby Kotlin dev here :)
Considering the following structure:
[
{ "color": ["red", "blue"] },
{ "color": ["red", "green"] },
{ "shape": ["square", "circle"] },
{ "shape": ["rectangle"] }
]
I'd like to obtain the following results where keys and their respective values are merged:
[
{ "color": ["red", "blue", "green"] },
{ "shape": ["square", "circle", "rectangle"] }
]
After some research I was thinking of something like this (doesn't work/compile), but I'm missing some pieces:
val colors1 = mapOf("color" to listOf("red", "blue"))
val colors2 = mapOf("color" to listOf("red", "green"))
val shapes1 = mapOf("color" to listOf("square", "circle"))
val shapes2 = mapOf("color" to listOf("rectangle"))
var mainList = mutableListOf(colors1, colors2, shapes1, shapes2)
mainList.reduce { acc, it -> (acc.asSequence() + it.asSequence())
.distinct()
.groupBy({it.key}, {it.value})
.mapValues { (_, values) -> values.flatten().distinct() }
Any help would be greatly appreciated.
Instead of merging the maps with reduce, you can use flatMap with the map entries. So it should work this way:
mainList
.flatMap { it.entries }
.groupBy({ it.key }, { it.value })
.mapValues { (_, values) -> values.flatten().toSet() }
(runnable sample)
Also, a little bit more efficient way to flatten the values in the last line is:
.mapValues { (_, values) -> values.flatMapTo(mutableSetOf()) { it } }
This removes the overhead of the intermediate collection that was created to store the values between flatten() and distinct()/toSet(), but still ensures that the items are unique because they are added to a mutableSet().
Sample with reassigning the mainList: (link)
Related
For example I have a class with below json format
[
{
"name": "a",
"detail": [
"1",
"2",
"3"
]
},
{
"name": "b",
"detail": [
"2",
"3",
"4"
]
}
]
how to change grouped it based on the detail?
[
{
"detail": "1",
"name": [
"a"
]
},
{
"detail": "2",
"name": [
"a",
"b"
]
},
{
"detail": "3",
"name": [
"a",
"b"
]
},
{
"detail": "4",
"name": [
"b"
]
}
]
below is my class structure
data class funName(
#field:JsonProperty("name")
val name: String = "",
#field:JsonProperty("detail")
val detail: Array<String> = arrayOf(""),
)
and my object is based on the array of funName
val data: Array<funName> = ...
i really have no idea how to do it.
val convert = data.groupBy { x -> x.detail } ??
Is this doable in kotlin/java?
Since the original data is grouped by name, you can think of the original data as a list of pairs
name detail
a 1
a 2
a 3
b 2
b 3
b 4
Mapping it to this format first would make it very easy to group by the second thing (detail) in the pair.
Since each funName corresponds to multiple pairs like this, you should use flatMap on data.
val result = data.flatMap { funName ->
funName.detail.map { funName.name to it }
}
.groupBy(keySelector = { (name, detail) -> detail }, valueTransform = { (name, detail) -> name })
// or more concisely, but less readable
// .groupBy({ it.second }) { it.first }
This will get you a Map<String, List<String>>.
If you want a List<Result>, where Result is something like
data class Result(
val detail: String = "",
val names: List<String> = listOf(),
)
You can add an additional map:
.map { (k, v) -> Result(k, v) }
In my mongodb collection documents are stored in the following format:
{ "_id" : ObjectId("62XXXXXX"), "res" : 12, ... }
{ "_id" : ObjectId("63XXXXXX"), "res" : 23, ... }
{ "_id" : ObjectId("64XXXXXX"), "res" : 78, ... }
...
I need to extract id's for the document for which the value of "res" is outlier (i.e. value < Q1 - 1.5 * IQR or value > Q3 + 1.5 * IQR (Q1, Q3 are percentiles)). I have done this using pandas functionality by retrieving all documents from the collection, which may become slow if the number of documents in collection become too big.
Is there a way to do this using mongodb aggregation pipeline (or just calculating percentiles)?
If I understand how you want to retrieve outliers, here's one way you might be able to do it.
db.collection.aggregate([
{ // partition res into quartiles
"$bucketAuto": {
"groupBy": "$res",
"buckets": 4
}
},
{ // get the max of each quartile
"$group": {
"_id": "$_id.max"
}
},
{ // sort the quartile maxs
"$sort": {
"_id": 1
}
},
{ // put sorted quartile maxs into array
"$group": {
"_id": null,
"maxs": {"$push": "$_id"}
}
},
{ // assign Q1 and Q3
"$project": {
"_id": 0,
"q1": {"$arrayElemAt": ["$maxs", 0]},
"q3": {"$arrayElemAt": ["$maxs", 2]}
}
},
{ // set IQR
"$set": {
"iqr": {
"$subtract": ["$q3", "$q1"]
}
}
},
{ // assign upper/lower outlier thresholds
"$project": {
"outlierThresholdLower": {
"$subtract": [
"$q1",
{"$multiply": ["$iqr", 1.5]}
]
},
"outlierThresholdUpper": {
"$add": [
"$q3",
{"$multiply": ["$iqr", 1.5]}
]
}
}
},
{ // get outlier _id's
"$lookup": {
"from": "collection",
"as": "outliers",
"let": {
"oTL": "$outlierThresholdLower",
"oTU": "$outlierThresholdUpper"
},
"pipeline": [
{
"$match": {
"$expr": {
"$or": [
{"$lt": ["$res", "$$oTL"]},
{"$gt": ["$res", "$$oTU"]}
]
}
}
},
{
"$project": {
"_id": 1
}
}
]
}
}
])
Try it on mongoplayground.net.
One more option based on #rickhg12hs's answer, is to use $setWindowFields:
db.collection.aggregate([
{$setWindowFields: {
sortBy: {res: 1},
output: {
totalCount: {$count: {}},
index: {$sum: 1, window: {documents: ["unbounded", "current"]}}
}
}
},
{$match: {
$expr: {$lte: [
{$abs: {$subtract: [
{$mod: [
{$multiply: [
{$add: ["$index", {$round: {$divide: ["$totalCount", 4]}}]}, 2]},
"$totalCount"
]}, 0]}
}, 1]}
}},
{$group: {_id: null, res: {$push: "$res"}}},
{$project: {_id: 0, q1: {$first: "$res"}, q3: {$last: "$res"},
iqr: {"$subtract": [{$last: "$res"}, {$first: "$res"}]}
}},
{$project: {
outlierThresholdLower: {$subtract: ["$q1", {$multiply: ["$iqr", 1.5]}]},
outlierThresholdUpper: {$add: ["$q3", {$multiply: ["$iqr", 1.5]}]}
}
},
{$lookup: {
from: "collection",
as: "outliers",
let: {oTL: "$outlierThresholdLower", oTU: "$outlierThresholdUpper"},
pipeline: [
{$match: {$expr: {$or: [{$lt: ["$res", "$$oTL"]}, {$gt: ["$res", "$$oTU"]}]}}},
{$project: {_id: 1}}
]
}
}
])
See how it works on the playground example
the mongodb data like this:
{
"_id": "123dsadasfa454sdsaw",
"hashmap": {
"uuid-12sadsadw5": {
"name": "bob"
},
"uuid-12sadsadwew5": {
"name": "alice"
}
},
"age": 10
}
"hashmap" like java HashMap, the key is uuid like "uuid-12sadsadwew5" and the value is object.
I want to get the data which the name in "hashmap" value is not null. And I use sql :
db.tabl1.find({"hashmap.values.name":{$ne:null}})
but cannot get the right result
You can use this aggregation query:
First use $objectToArray to create an array with values k and v. As we don't know the key (k) we can search by value (v).
Then $unwind array
And $match values where name is not null.
And then regroup and recreate the object using $arrayToObject.
db.collection.aggregate([
{
"$set": {
"hashmap": {
"$objectToArray": "$hashmap"
}
}
},
{
"$unwind": "$hashmap"
},
{
"$match": {
"hashmap.v.name": {
"$ne": null
}
}
},
{
"$group": {
"_id": "$_id",
"hashmap": {
"$push": "$hashmap"
}
}
},
{
"$set": {
"hashmap": {
"$arrayToObject": "$hashmap"
}
}
}
])
Example here
I have the following JSON response (reference name: "list") and
[
{
"key": "101",
"val": {
"portCall": {
"id": 12664978
},
"status": "in-port"
}
},
{
"key": "102",
"val": {
"portCall": {
"id": 12415798
},
"status": "in-port"
}
},
{
"key": "103",
"val": {
"status": "on-voyage",
"voyage": {
"id": "7kynv-7lq85"
}
}
},
{
"key": "104",
"val": {
"status": "on-voyage",
"voyage": {
"id": "7kynv-2385"
}
}
}
]
also, I have an array list of few key values, evImos = [101,102,104]
In that, I have to identify the first key in the "list" response that has status as "on-voyage". So, the result should be "104".
I have tried the following and I need some help to make it work. Any help would be appreciated.
* def function getFirst = function(evImos) { for (let num of evImos) { let results = list.filter(d => d["key"] === num && d["val"]["status"] === "on-voyage"); if(results.length === 1) { karate.log(num); return num; } } }
* list.forEach(getFirst(evImos))
I'll just give you one hint. This one line will convert the whole thing in to a form that is much easier for you to validate:
* def temp = {}
* list.forEach(x => temp[x.key] = x.val.status)
Which gives you:
{
"101": "in-port",
"102": "in-port",
"103": "on-voyage",
"104": "on-voyage"
}
Now you can do:
* def isOnVoyage = function(key){ return temp[key] == 'on-voyage' }
Also read this: https://stackoverflow.com/a/59162760/143475
Thanks, to #Peter.
Based on his hint, I just tweaked it a little bit to match my requirement and it worked for me.
Here is the working copy for anyone to refer in the future.
* def temp = {}
* list.forEach(x => temp[x.key] = x.val.status)
* def isOnVoyage = function(keys){ for (let key of keys) { if(temp[key] == 'on-voyage'){ karate.log(key); karate.set('num', key); break; }}}
* isOnVoyage(evImos)
I am using this code to access a Firebase Database, but I am cannot figure out how to read the sub array of strings. (the JSON structure is below)). The code I have returns the top level items, but not the list of strings. Would someone be able to assist with this issue?
Here is my function to read from the DB:
func sizes(userId: String = Auth.auth().currentUser!.uid, success: #escaping ([Sizes]) -> ()) {
let ref = Router.sizes.reference()
let query = ref.queryOrdered(byChild: "name") //userId)
query.observe(.value, with: { snapshot in
var array = [Sizes]()
for child in snapshot.children {
if let size = Mapper<Sizes>().map(JSON: (child as! DataSnapshot).value as! [String : AnyObject]) {
array.append(size)
}
}
success(array)
})
}
My Firebase JSON is as follows:
{
"-SzCat_001": {
"name": "Womans",
"sizeCategories": {
"name": "Pants",
"sizeDescriptor": [
"00",
"0",
"2",
"4",
"6",
"8",
"10",
"12",
"XL"
]
}
}
}
And this is what I get returned?
[0] = {
name = "Womans"
sizeCategories = 0 values {} }
I am trying to figure out how to read the sizeCategories list of strings as a subarray of sizes.
Here is my definition of sizes and sizeCategories:
struct Sizes: Mappable {
var name: String = ""
var sizeCategories = [SizeCategories]()
init() {
}
init?(map: Map) {
}
mutating func mapping(map: Map) {
name <- map["name"]
sizeCategories <- map["sizeCategories"]
}
}
struct SizeCategories: Mappable {
var name: String = ""
var sizeDescriptor = [String]()
init() {
}
init?(map: Map) {
}
mutating func mapping(map: Map) {
name <- map["name"]
sizeDescriptor <- map["sizeDescriptor"]
}
}
Thanks for any help!!!
You're jumping through a lot of hoops to read the data here. You could just let allMyData = snapshot.value as! [String: AnyObject] and know that each internal value is also a [String: AnyObject]. But if you really want to destructure into something more typed with this mapping technique, have a look at your sizes definition:
var sizeCategories = [SizeCategories]()
This says "sizeCategories is an array of SizeCategories type". But your data is not structured as an array, it is a dict:
"sizeCategories": {
"name": "Pants",
"sizeDescriptor": [
"00",
"0",
"2",
"4",
"6",
"8",
"10",
"12",
"XL"
]
}
You need to adjust your definition and mapping method here for this field.