Test mongodb map, reduce function in rails - ruby-on-rails

When I use mongoid mapreduce in rails, I pass the map and reduce function by string. for example
def group
Order.collection.map_reduce(map, reduce, :out => 'output_collection')
end
def map
"function()
{
var key = {game_id: this.game_id, year: this.date.getFullYear()};
emit(key, {count: 1, score: this.score});
}"
end
def reduce
"function()
{
var result = {sum: 0, total_score: 0, average: 0};
values.forEach(function(value)
{
result.sum += value.count;
result.total_score += value.score;
});
}"
end
Is it possible to test the map and reduce function in rails.
It is a simple example. But my project's function is more complicate, and I feel it is hard to maintainable.
Thanks for your help for any advice.

For a test of a function that takes place in the database, your testing methodology is:
Create a base set of data
Run the function
Compare the results to your expected
So, in this case, you'll want to make sure the sum/average/total_score match what you expect.

Related

How to `map` an `Iterable` partially in Dart?

Assuming I have a List as below:
final numbers = [1, 0, -1, 1, -3, 0];
I'd like to generate another list where 1 results in true and 0 results in false and skip the others. So, in other words, I'd like a method like map where I can also skip some elements. In terms of test, it would assert to:
expect(newList, equals([true, false, true, false]));
In this result, I'd like to skip -1 and -3 in the list.
How can I achieve this?
Environment
Dart 2.18.5
Use the .where() method to get a new iterable with only the elements satisfying a predicate, then use .map() to apply the transformation on the result.
final numbers = [1, 0, -1, 1, -3, 0];
final result = numbers.where((n) => n == 1 || n == 0).map((n) => n == 1);
print(result);
\\ (true, false, true, false)
The most direct version would be:
var result = numbers.expand<bool>((n) =>
n == 0
? const <bool>[false]
: n == 1
? const <bool>[true]
: const <bool>[]);
The expand method can do everything map and where can, plus more,
Not particularly efficient, but not all bad either.
Another approach is to use a sync* function:
var result = () sync* {
for (var number in numbers) {
if (number == 0) {
yield false;
} else if (number == 1) {
yield true;
}
}();
If you don't care about creating a list eagerly, that's a also the approach for a list literal:
var result = [for (var number in numbers)
if (number == 0) false else if (number == 1) true
];
Or:
const _map = {0: [false], 1: [true]};
var result = [for (var number in numbers} ...?_map[number]];
The options are endless. In practice, doing where and map is probably more readable.
Ariel's answer gave me a better idea. In the example of the question, what we'd like to filter out are two values, which are 1 and 0. The real problem I'm dealing with right now is more complicated than this one, it has a wide range of lookup list. I haven't specifically asked "What is a more programmatic way to do this?" or "What if the lookup list has more values?".
So, if the values I'm looking for are more than these two values, a List of lookup values and contains method would be better for my case.
final numbers = [1, 0, -1, 1, -3, 0];
final lookup = {1, 0};
final result = numbers.where((n) => lookup.contains(n)).map((n) => n == 1 ? true : false).toList();
print(result);
This has some drawbacks though, some of which that come to my mind are:
Performance is definitely worse than Ariel's answer. My solution uses contains, which will perform look-ups in lookup rather than a simple byte-comparison with n == 1. So, it isn't great with large lists.
hashCode must be overriden for non-primitives. It is easy to compare int with an int in this simple example, but if you have an instance, you need to override hashCode, otherwise Dart will compare the memory addresses rather than values.
Using another collection type for lookup other than Set might also have performance impact because Set is inherently O(1) while the others might depend on their standard library implementation, but it's safe to assume they're going to be worse than Set. (See Ariel's comment).
BTW, lookup does not have to be a Set. I've done it Set because lookup contains distinct values and the order is not important.
Iterable.map is a 1:1 mapping; if your input has n elements, then the output must have n elements too.
Using numbers.where(...).map(...) works, but alternatively:
Use Iterable.map first to map either to desired values or to an invalid sentinel value. If your sentinel value is null, then you can use Iterable.whereType to filter out them out:
var transformed = numbers.map((n) {
switch (n) {
case 0:
return false;
case 1:
return true;
default:
return null;
}
}).whereType<bool>();
You might prefer this if you want all of your logic in a single callback.
Use collection-for instead of Iterable.map and collection-if instead of Iterable.where:
var transformed = [
for (var n in numbers)
if (isValidValue(n))
transform(n),
];
where you define isValidValue and transform functions with your filtering and transformation logic respectively.

Building a map for parallel step from directory names [duplicate]

For example, the groovy File class has a nice iterator that will filter out just directories and not files:
void eachDir(Closure closure)
When I use eachDir, I have to use the verbose method of creating the collection first and appending to it:
def collection = []
dir1.eachDir { dir ->
collection << dir
}
Any way to get it back to the nice compact collect syntax?
I don't know of any "idiomatic" way of doing this, nice riddle! =D
You can try passing the eachDir, or any similar function, to a function that will collect its iterations:
def collectIterations(fn) {
def col = []
fn {
col << it
}
col
}
And now you can use it as:
def dir = new File('/path/to/some/dir')
def subDirs = collectIterations(dir.&eachDir)
def file = new File('/path/to/some/file')
def lines = collectIterations(file.&eachLine)
(that last example is equivalent to file.readLines())
And only for bonus points, you may define this function as a method in the Closure class:
Closure.metaClass.collectIterations = {->
def col = []
delegate.call {
col << it
}
col
}
def dir = new File('/path/to/some/dir')
def subDirs = dir.&eachDir.collectIterations()
def file = new File('/path/to/some/file')
def lines = file.&eachLine.collectIterations()
Update: On the other hand, you might also do:
def col = []
someDir.eachDir col.&add
Which I think is quite less convoluted, but it's not leveraging the collect method as you requested :)
Not for the specific example that you're talking about. File.eachDir is sort of a weird implementation IMO. It would have been nice if they implemented iterator() on File so that you could use the normal iterator methods on them rather than the custom built ones that just execute a closure.
The easiest way to get a clean one liner that does what you're looking for is to use listFiles instead combined with findAll:
dir1.listFiles().findAll { it.directory }
If you look at the implementation of eachDir, you'll see that it's doing this (and a whole lot more that you don't care about for this instance) under the covers.
For many similar situations, inject is the method that you'd be looking for to have a starting value that you change as you iterate through a collection:
def sum = [1, 2, 3, 4, 5].inject(0) { total, elem -> total + elem }
assert 15 == sum

Is there a way to apply multiple method by one liner in ruby?

There is an array like this:
a = [1,2,3,4]
I want to get the return values of size and sum like this.
size = a.size
sum = a.sum
Is there a way to get both values by a one-liner like this?
size, sum = a.some_method(&:size, &:sum)
In Ruby, you can do multiple assignments in one line:
size, sum = a.size, a.sum
It doesn't make it more readable, though.
You could do this:
a = [1,2,3,4]
methods = [:size, :max, :min, :first, :last]
methods.map { |m| a.send m }
#=> [4, 4, 1, 1, 4]
Another possible solution:
size, sum = a.size, a.reduce { |a,b| a = a + b }
Previous answers are correct, but if OP was actually concerned about walking the array multiple times, then array.size does not walk the array, it merely returns the length, thus there is no saving from a oneliner in that regard.
On the other hand, if size was just an example and the question is more about making multiple operations on an array in one go, then try something like this:
arr = [1,2,3,4,5,6]
product,sum = arr.inject([1,0]){|sums,el| [sums[0]*el, sums[1]+el]}
# => [720, 21]
That is, inject the array with multiple initial values for the results and then calculate new value for every element.

How to best count nested objects in Groovy

I have a Groovy class which looks a lot like this:
class A {
B[] requests = []
}
In code, I have an array of A and I'd like to know how many B are contained within it. What's the most groovyish way to make that calculation? I know how to do it in Java, but it looks far uglier than I suspect Groovy would like it to be. Here's what I've worked out so far:
list.each{ a -> count += a.requests.size() }
Is there a better way?
When coding this, I'd invoke the KISS principle regarding maintenance and clarity. Not everything should use shiny features.
Your version is reasonably 'Groovy', though you may want to consider using the implicit variable it (see doc):
def count = 0
list.each{ count += it.requests.size() }
Use spread operator (*)
class A {
B[] requests = []
}
class B{
String name
}
assert [
new A(requests : [new B(name: 'John'), new B(name: 'Jim'),
new B(name: 'Alex')]),
new A(requests : [new B(name: 'Test'), new B(name: 'Alex')]),
new A(requests : [new B(name: 'John')])
]*.requests*.size() == [3, 2, 1]

MongoDB - Mongoid map reduce basic operation

I have just started with MongoDB and mongoid.
The biggest problem I'm having is understanding the map/reduce functionality to be able to do some very basic grouping and such.
Lets say I have model like this:
class Person
include Mongoid::Document
field :age, type: Integer
field :name
field :sdate
end
That model would produce objects like these:
#<Person _id: 9xzy0, age: 22, name: "Lucas", sdate: "2013-10-07">
#<Person _id: 9xzy2, age: 32, name: "Paul", sdate: "2013-10-07">
#<Person _id: 9xzy3, age: 23, name: "Tom", sdate: "2013-10-08">
#<Person _id: 9xzy4, age: 11, name: "Joe", sdate: "2013-10-08">
Could someone show how to use mongoid map reduce to get a collection of those objects grouped by the sdate field? And to get the sum of ages of those that share the same sdate field?
I'm aware of this: http://mongoid.org/en/mongoid/docs/querying.html#map_reduce
But somehow it would help to see that applied to a real example. Where does that code go, in the model I guess, is a scope needed, etc.
I can make a simple search with mongoid, get the array and manually construct anything I need but I guess map reduce is the way here. And I imagine these js functions mentioned on the mongoid page are feeded to the DB that makes those operations internally. Coming from active record these new concepts are a bit strange.
I'm on Rails 4.0, Ruby 1.9.3, Mongoid 4.0.0, MongoDB 2.4.6 on Heroku (mongolab) though I have locally 2.0 that I should update.
Thanks.
Taking the examples from http://mongoid.org/en/mongoid/docs/querying.html#map_reduce and adapting them to your situation and adding comments to explain.
map = %Q{
function() {
emit(this.sdate, { age: this.age, name : this. name });
// here "this" is the record that map
// is going to be executed on
}
}
reduce = %Q{
function(key, values) {
// this will be executed for every group that
// has the same sdate value
var result = { avg_of_ages: 0 };
var sum = 0; // sum of all ages
var totalnum = 0 // total number of people
values.forEach(function(value) {
sum += value.age;
});
result.avg_of_ages = sum/total // finding the average
return result;
}
}
results = Person.map_reduce(map, reduce) //You can access this as an array of maps
first_average = results[0].avg_of_ages
results.each do |result|
// do whatever you want with result
end
Though i would suggest you use Aggregation and not map reduce for such a simple operation. The way to do this is as follows :
results = Person.collection.aggregate([{"$group" => { "_id" => {"sdate" => "$sdate"},
"avg_of_ages"=> {"$avg" : "$age"}}}])
and the result will be almost identical with map reduced and you would have written a lot less code.

Resources