Ordering Collection by multiple fields and condition in Mongoid - ruby-on-rails

I have 4 fields in a document which is name, online, like and score. I want to ordering by multiple fields and conditions collection of million documents with pagination.
Example some documents :
My user documents :
{ "_id": 1, "name": "A", "online": 1, "like": 10, "score": 1 },
{ "_id": 2, "name": "B", "online": 0, "like": 9, "score": 0 },
{ "_id": 3, "name": "C", "online": 0, "like": 8, "score": 1 },
{ "_id": 4, "name": "D", "online": 1, "like": 8, "score": 0 },
{ "_id": 5, "name": "E", "online": 1, "like": 7, "score": 1 },
{ "_id": 6, "name": "F", "online": 0, "like": 10, "score": 0 },
I will explain my point with the following example (example using an array).
Example in ruby language, I have an array structure looks like :
[["A", 1, 10, 1],
["B", 0, 9, 1],
["C", 0, 8, 1],
["D", 1, 8, 0],
["E", 1, 7, 1],
["F", 0, 10, 0]]
If online is 1 should be sort again by descending of like, but when online is 0 should be sort again by descending of score.
example sort :
list.sort{|a, b| a[1] == 1 ? ([-a[1], -a[2]] <=> [-b[1], -b[2]]) : ([-a[1], -a[3]] <=> [-b[1], -b[3]]) }
Result like this :
[["A", 1, 10, 1],
["D", 1, 8, 0],
["E", 1, 7, 1],
["B", 0, 9, 1],
["C", 0, 8, 1],
["F", 0, 10, 0]]
That is an array sort, but my problem is I have collection of mongodb and million documents, I can't use an array sort, because it will heavy load to database, should get all documents and convert to array (including sorting) and than paginate them, I think that's a bad idea.
I have try with order()/order_by() mongoid's optional method like :
User.
order_by([:online, :desc], [:like, :desc], [:score, :desc]).
hint(online: -1, like: -1, score: -1).
page(1).per(10)
But that query is only order by online and score, is there sort method in mongoid like an array sorting? or there is something like bubble sort in mongodb?
same problem here : Ruby on Rails: Concatenate results of Mongoid criterias and paging, merge method doesn't helped me because it can be replace the first criteria.

Using aggregation $cond
User.collection.aggregate([
{
"$project" => {
"id" => 1,
"name" => 1,
"online" => 1,
"like" => 1,
"score" => 1,
"sort" => {
"$cond" => {
"if" => {
"$eq" => ["$online", 1]
},
"then" => "$like",
"else" => "$score"
}
}
}
},
{
"$sort" => {
"online" => -1,
"sort" => -1,
"id" => 1
}
},
{
"$skip" => 0
{
"$limit" => 12
}
])
references :
https://docs.mongodb.com/manual/reference/operator/aggregation/cond/
https://www.oodlestechnologies.com/blogs/How-to-use-Conditional-Statements-for-sorting-data-in-MongoDB

Assuming fields score , like and online are numeric , based on the condition we can create an auxiliary field that is a sum of online and like or online and score. Following which we can sort on the new field and any other required field.
db.collection aggregate([ {"$addFields": {"refCount":{"$cond":{"if":{"$eq":["$online", 1]}, "then":{"$add": ["$online", "$like"]}, "else": {"$add": ["$online", "$score"]} } } }},{ "$sort": { "refCount": -1 ,"id":1} },{"$project":{"id": 1, "name": 1, "online": 1, "like": 1,"score":1}},{ "$limit": 10 } ])
The first stage of the pipeline build the auxiliary field refcount. This field is then used in $sort along with id . In the next stage we have a projection of required fields , followed by limit clause.
Instead of numeric , if these fields were strings, a padded concatenation of fields would be required.

Related

How to get the value of an array of selected keys from swift array of dictionaries and the complexity of it

Lets say I have got the below response. Two things I would like to know from the below operation.
1.) How to improve/optimise the below code using higher order functions in swift.
2.) Also would like to know the current complexity of the code and the complexity of any optimized code you may suggest.
In the below response I would like to check the values of only certain keys as described in keysToBeChecked and for that particular key I need to do an operation. Once the operation is done I want to add a new key(key6) to the response as shown below. The following operation works fine for me and that is what I intend to do. I am looking for the above 2 things mentioned
var response = [["key1": 1, "key2": 0, "name": "John", "key3": 1, "key4": 1, "place": "Newyork", "key5": 0],
["key1": 0, "key2": 1, "name": "Mike", "key3": 1, "key4": 0, "place": "California", "key5": 1],
["key1": 1, "key2": 0, "name": "John", "key3": 0, "key4": 1, "place": "Boston", "key5": 1]]
let keysToBeChecked = ["key1", "key2", "key3", "key4", "key5"]
for var item in response{
var dict = [String: String]()
for(key, value) in item{
if keysToBeChecked.contains(key){
dict[key] = "\(value)"
if dict[key] == "1"{
//perform required operations
output
}
}
}
item["key6"] = output
response.append(item)
}
print(response)//should print the below
My expected output is
response = [["key1": 1, "key2": 0, "name": "John", "key3": 1, "key4": 1, "place": "Newyork", "key5": 0, "key6": "output"],
["key1": 0, "key2": 1, "name": "Mike", "key3": 1, "key4": 0, "place": "California", "key5": 1, "key6": "output"],
["key1": 1, "key2": 0, "name": "John", "key3": 0, "key4": 1, "place": "Boston", "key5": 1, "key6": "output"]]
For higher order function, you can try,
let result = response.map { (dict) -> [String:Any] in
var filteredDict = [String:Any]()
dict.forEach({ (key, value) in
if keysToBeChecked.contains(key) {
filteredDict[key] = value
if (value as? Int) == 1 {
filteredDict["key6"] = "output"
}
}
})
return filteredDict
}

sorted map in cypher

When creating a map from collections via apoc's apoc.map.fromLists (or plain cypher for all I care), is there any way to respect/keep the sorting? Or is it just "It's a map, sorting doesn't make sense anyway".
specifically, when executing
with [{name: "z", a: 1}, {name: "b", b: 1}, {name: "b2", b: 2}] as qs
return apoc.map.fromLists([q in qs | q.name], qs)
I get
{
"b2": {
"name": "b2",
"b": 2
},
"z": {
"name": "z",
"a": 1
},
"b": {
"name": "b",
"b": 1
}
}
where indeed I would like to have
{
"z": {
"name": "z",
"a": 1
},
"b": {
"name": "b",
"b": 1
},
"b2": {
"name": "b2",
"b": 2
}
}
Sorry, maps in Cypher are unsorted and unsortable structures, with regard to map keys.
There is apoc.maps.sortedProperties(), however this doesn't return a map, but a list of key/value pairs, where those pairs are sorted alphabetically.

Get specific json data rails

I have this json data (actual data is a lot longer, that's why I need only 2)
[
{
"id": 1,
"user_id": 1,
"event_id": 1,
"creator_id": 1,
"event_name": "Poker",
"cruise_ship_name": "Royal Cruise",
},
{
"id": 2,
"user_id": 1,
"event_id": 2,
"creator_id": 1,
"event_name": "Ballroom",
"cruise_ship_name": "Celebrity Infinity",
},
{
"id": 3,
"user_id": 1,
"event_id": 3,
"creator_id": 1,
"event_name": "Tennis",
"cruise_ship_name": "Mediterranean",
}
]
I want to combine all data and get only specific fields (event_name and cruise_ship_name)
So in my final json format
it will be:
[
{
"event_name": "Mexican Fiesta",
"cruise_ship_name": "Celebrity Infinity",
}
]
I have been looking at this example:
#object.to_json {:include => [ :assocation_a, :assocation_b ]}
but not sure what :association_a and :association_b are.
Suppose you have an array of hashes:
events = [
{
"id": 1,
"user_id": 1,
"event_id": 1,
"creator_id": 1,
"event_name": "Poker",
"cruise_ship_name": "Royal Cruise",
},
...
]
You can iterate through each value in your hash, only keeping values of interest:
events.each do |event_hash|
event_hash.keep_if { |key, _| [:event_name, :cruise_ship_name].include?(key) }
end
puts events
The to_json method accept parameters which allow you include specific attributes:
#object.to_json(only: [:event_name, :cruise_ship_name])
The include: :assocation_a option to object, allowing the object association in the assocation_a model to be converted to JSON as well.

Return Search results metadata with model -- rails

I have the following search results (json) that I need to tie with a model (I expanded the first one to make it more readable:
{"products_matched":
{ "89": //<-- this is a product ID
{ "lines": {
"0": {
"meta_count": 6,
"metas": {
"0": [0, 4], "1": [0, 4], "2": [0, 1], "3": [1, 2], "4": [2, 3], "5": [3, 4]
}
},
"1": {
"meta_count": 5,
"metas": {
"0": [0, 4], "1": [0, 4], "2": [0, 1], "3": [1, 2], "4": [2, 3]
}
}
},
"product_score": 0.0
} ,
"82": {"lines": {"0": {"meta_count": 2, "metas": {"0": [0, 4], "1": [0, 4]}}}, "product_score": 0.55},
"60": {"lines": {"0": {"meta_count": 3, "metas": {"0": [0, 4], "1": [0, 4], "2": [3, 4]}}}, "product_score": 0.0},
"10": {"lines": {"0": {"meta_count": 2, "metas": {"0": [0, 4], "1": [0, 4]}}}, "product_score": 0.0}}
}
In rails, how can I return both the model object and the associated meta data (lines) attached to id?
What I have so far:
product_ids = results["products_matched"].keys # => ["89", "82", "60", "10"]
products = Product.where(id: product_ids) # => #<ActiveRecord::Relation [#<Product id: 82, name: ...],[...] ...>
I could try to collect / map them together under one instance variable, or I could send them separately, but that leaves the view to marry the two.
Simple solution, and I don't know if it is the best, but I just added an attr_accessor to the Product model.
attr_accessor :result
Then I loop through the products:
product_ids = results["products_matched"].keys # => ["89", "82", "60", "10"]
#products = Product.where(id: product_ids)
#products.each do |p|
p.result = results["parsers_matched"][p.id.to_s]
end

Sort array of arrays by array value into hash in ruby

I have an array of arrays
[ [1,23,true], [2,33,false],[3,44,true] ]
I'm trying to sort the array based on the [2] value of each inner array, so the outcome would be
true_values: [ [1,23,true],[3,44,true] ]
false_values: [ [2,33,false] ]
I can select the array objects but am looking for a succinct way of sorting and outputting a hash.
x = [ [1,23,true], [2,33,false],[3,44,true] ]
Since group_by keeps the order you can sort before you group by:
p x.sort_by{|x|x[1]}.group_by(&:last) #=> {true=>[[1, 23, true], [3, 44, true]], false=>[[2, 33, false]]}
and if you do not need to keep the true/false value in the hash:
p x.sort_by{|x|x[1]}.group_by(&:pop) #=> {true=>[[1, 23], [3, 44]], false=>[[2, 33]]}
Here I'm using Enumerable#group_by to group it by it true or false first, then sorting that hash by the contents of the second element of the array found in v. Lastly we call .to_h or to hash to get the format you outlined above.
[10] pry(main)> arr
=> [[1, 23, true], [2, 33, false], [3, 44, true]]
[11] pry(main)> arr.group_by { |x| x[2] }.sort_by { |_, v| v.map { |i| i[1] } }.to_h
=> {true=>[[1, 23, true], [3, 44, true]], false=>[[2, 33, false]]}
Here's one way:
arr = [[1, 44, true], [2, 33, false], [3, 23, true]]
arr.sort_by { |*_,tf| (tf && 0) || 1 }.chunk { |*_,tf| tf }.to_h
#=> {true=>[[1, 44, true], [3, 23, true]], false=>[[2, 33, false]]}
If you want ties broken by, say, arr[i][1], then:
arr.sort_by { |_,e,tf| [(tf && 0) || 1, e] }.chunk { |*_,tf| tf }.to_h
#=> {true=>[[3, 23, true], [1, 44, true]], false=>[[2, 33, false]]}
Notice that I've changed arr given in the question to make the second point.

Resources