In Red, how do I search through a block for a string matching a pattern? - red

Given:
player-wins: [
"rock breaks scissors"
"paper covers rock"
"scissors cut paper"
]
I want a function which accepts two strings, each representing any of rock, paper or scissors and then returns the element that matches, ignoring the verb.
Example:
does-player-win "paper" "rock" should return "paper covers rock" by searching the block.
UPDATE I tried to change the structure to make it easy for find or select but apparently my structure is illegal:
player-wins: [
["rock" "scissors"] "breaks"
["paper" "rock"] "covers"
["scissors" "paper"] "cut"
]

I almost forgot the most simple solution
player-wins: [
"rock" "breaks" "scissors"
"paper" "covers"" rock"
"scissors" "cuts" "paper"
]
game: func [player1 player2] [
foreach [subject predicate object] player-wins [
all [
player1 = subject
player2 = object
return reduce [player1 predicate player2]
]
]
]
>> print game "rock" "scissors"
rock breaks scissors
>> print game "scissors" "paper"
scissors cut paper
But also your second structure fits
player-wins: [
["rock" "scissors"] "breaks"
["paper" "rock"] "covers"
["scissors" "paper"] "cut"
]
win: function [player1 player2] [
game: reduce [player1 player2]
winning: player-wins/(game)
print [player1 winning player2]
]
>> win "paper" "rock"
paper covers rock
or short
win: func [player1 player2] [
print [player1 select/only player-wins reduce [player1 player2] player2]
]
And an optimized version independent of the order covering all variations could be like this
player-wins: [
"rock" "breaks"
"scissors" "cuts"
"paper" "covers"
"rock" "is covered by"
"paper" "is cut by"
"scissors" "will be broken by" "rock"
]
game: func [player1 player2] [
parse player-wins [
collect [some [keep [player1 skip player2] | 2 skip] ]
]
]
>> print game "scissors" "rock"
scissors will be broken by rock
>> print game "rock" "scissors"
rock breaks scissors

Related

Strange problem when storing and querying GeoJson in MongoDB

I want to store GeoJson data for an area using MongoDB. The data comes from an official website. Each area is represented as MultiPolygon. In the end, I want to find all areas that contain a lng/lat pairs using a $intersect like that:
db.areas.find({
"location.geometry": {
"$geoIntersects": {
"$geometry": {
"type": "Point",
"coordinates": [
<lng>,
<lat>
]
}
}
}
}
In principle, it seems to work just fine. However, I've encountered problems with some areas seemingly with respect to the set of polygons of a MultiPolygon. I could boil down my problem to an individual case:
An area (being a GeoJson MultiPolygon) has six polygons, say [A, B, C, D, E, F]. Also the point <lng>,<lat> I query for lies within polygon A. Now the query above only works if the area does not contain the polygons D and F (A has to be included always, of course) -- that is, I get the expected search result. Otherwise, the query is empty (but no error). In short
What works: [A], [A,B], [A,B,C], [A,B,C,E], [A,C], ... (any combination with A and without D & F)
What doesn't work: [A,D], [A,B,F], ... (any combination that contains D or F)
What is the problem with polygons D and F? Are they not allowed to overlap with other polygons in the MultiPolygon? Are they maybe too small? I've tried the GeoJson definition but couldn't see any issues. Could it be because the GeoJson support of MongoDB.
You are correct that without any special considerations, you can insert a Polygonor MultiPolygon into MongoDB that has deformed GeoJSON structure. This is because unless you specifically create a geo index on the field, MongoDB doesn't know it is GeoJSON at all. The geo engine will silently not match a target intersect geometry, much as it would if you pointed it at a simple scalar field like {"name":"buzz"}.
If you add an index thusly:
db.geo.createIndex({loc:"2dsphere"})
Then this will activate the geo-aware machinery and if you try to insert or update a deformed GeoJSON shape, it will produce an error (scroll to see the Loop not closed part):
{
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 16755,
"errmsg" : "Can't extract geo keys: { _id: 0.0, loc: { type: \"MultiPolygon\", coordinates: [ [ [ [ -83.0, 40.0 ], [ -83.0, 41.0 ], [ -82.0, 41.0 ], [ -82.0, 40.0 ], [ -83.0, 40.0 ] ] ], [ [ [ -93.0, 40.0 ], [ -93.0, 41.0 ], [ -92.0, 41.0 ], [ -92.0, 40.0 ], [ -93.0, 40.0 ] ] ], [ [ [ -73.0, 49.0 ], [ -72.0, 41.0 ], [ -72.0, 40.0 ], [ -73.0, 40.0 ], [ -73.0, 41.0 ] ] ] ] } } Loop is not closed: [ [ -73.0, 49.0 ], [ -72.0, 41.0 ], [ -72.0, 40.0 ], [ -73.0, 40.0 ], [ -73.0, 41.0 ] ]"
}
}
In other words, the geo index becomes the guard at the door and ensures that all shapes written are GeoJSON compliant. This is also why it is very useful to ensure the index is created before inserts and updates because trying to create a geo index on many docs with potentially 100s or 1000s of deformed shapes will lead to much tedious work trying to isolate and fix the bad shapes one at a time.
After some more digging, I figured out that the polygons causing the issues contained duplicate coordinates (apart from the first and last coordinate). Online GeoJson validator didn't raise an error, but it seems that MongoDB doesn't handle it.
After removing all duplicates, everything works fine -- at least I hope that removing duplicates alter the shape of the polygons too much (but that's not overly crucial for my case). It's just a bit unfortunate that MongoDB doesn't raise an error but simply returns an empty result.

Need to change one element from a document. - MongoDB

I'm trying to follow the documentation on mongoDB to change one element from an array, inside an array, inside an array.
This is my element
secciones: [
{
_id: ObjectId("5c6ee5d5e12e25dc1ab99b3e"),
subSeccion: [
{
_id: ObjectId("5c6ee5d5e12e25dc1ab99b3f"),
subSeccionItems: [
"The witch jumps over the sleepy dog",
"The witch jumps over the sleepy dog",
"The dog eats"
"The cat scratches"
]
}
]
}
]
The problem is that with this command it changes both occurrences of `The witch jumps over the sleepy dog, and I need just one to be changed:
db.pautas.updateOne({_id:ObjectId("5c6ee5d5e12e25dc1ab99b37")}, {$set: {"criterios.secciones.$[id].subSeccion.$[id2].subSeccionItems.$[enun]": "The witch jumps over the sleepy dog"}}, { arrayFilters: [ { "id._id": ObjectId("5c6ee5d5e12e25dc1ab99b3e")}, {"id2._id": ObjectId("5c6ee5d5e12e25dc1ab99b3f")}, {"enun": "The elephant smells"}]})
Same command unformatted:
db.pautas.updateOne({_id:ObjectId("5c6ee5d5e12e25dc1ab99b37")}, {$set: {"criterios.secciones.$[id].subSeccion.$[id2].subSeccionItems.$[enun]": "The witch jumps of the sleepy dog"}}, { arrayFilters: [ { "id._id": ObjectId("5c6ee5d5e12e25dc1ab99b3e")}, {"id2._id": ObjectId("5c6ee5d5e12e25dc1ab99b3f")}, {"enun": "The elephant smells"}]})
Im using this documentation: https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/
The command works, but changes both elements, I need just one to change.
Regards.
UPDATE
Now with the answer from #willis I tried this query and got the same result. I saved some characters but didn't get the result I need, which is to change just one occurrence of the item in question.
db.pautas.updateOne({_id:ObjectId("5c6ee5d5e12e25dc1ab99b37")}, {$set: {"criterios.secciones.$[].subSeccion.$[].subSeccionItems.$[enun]": "The witch jumps of the sleepy dog"}}, { arrayFilters: [ {"enun": "The elephant smells"}]})
Unformatted:
db.pautas.updateOne({_id:ObjectId("5c6ee5d5e12e25dc1ab99b37")}, {$set: {"criterios.secciones.$[].subSeccion.$[].subSeccionItems.$[enun]": "The witch jumps of the sleepy dog"}}, { arrayFilters: [ {"enun": "The elephant smells"}]})
I'm still stuck here, please help.
I believe the positional operator $ should do what you want:
the positional $ operator acts as a placeholder for the first element that matches the query document
> db.test_coll.insert({arr: [{nested: ["hello", "hello", "goodbye"]}]})
> db.test_coll.update({"arr.nested": "hello"}, {$set: {"arr.$[].nested.$": "updated"}});
> db.test_coll.find({})
{ "_id" : ObjectId("5c7344d94b434de8affa5b73"), "arr" : [ { "nested" : [ "updated", "hello", "goodbye" ] } ] }

Mongo query array.length within an array

I tried to use length on an array that is within another array and got an error:
db.quotes.find( { $where: "this.booksWithQuote.authors.length>0" } )
// fails with this.booksWithQuote.authors is undefined
I'm basing this syntax on this example MongoDB – find all documents where an array / list size is greater than N that works:
db.domain.find( {booksWithQuote: {$exists:true}, $where:'this.booksWithQuote.length>0'} )
// Above works
So I'm wondering if this is possible, how can I find all documents that have array1.array2 where array2 length is greater than zero.
I've tried the following but it fails with a syntax error:
db.quotes.find( {
"booksWithQuote": {$exists: true} },
"booksWithQuote.authors": {$exists: true} },
$where: "booksWithQuote.authors.length>0" } )
// fails with this.booksWithQuote.authors is undefined
It's worth pointing out that if I knew the author of a book, the nested array with array searching worked. Pretty cool!
db.quotes.find( { "booksWithQuote.authors" : "Sam Fisher" } )
// Returns all quotes that have a book that has the author Sam Fisher
But in my case I'm just trying to find all of the quotes that have more than one author on any given book.
To follow along, consider this example.
I have a collection of quotes, and each quote has a list of books where the quote was used, and each book has an array of authors.
Below is some sample data so you can understand the structure of the data. It shows one quote with no books, another quote with a book but no authors, and a third quote with books and several authors.
[
{
quoteText: "Love to code",
booksWithQuote: [ ]
},
{
quoteText: "Another Quotesake",
booksWithQuote: [
{
title: "Where is elmo",
authors: [ ]
}
]
},
{
quoteText: "For goodness sake",
booksWithQuote: [
{
title: "The search for Elmo",
authors: [
"John Smith",
"Sam Fisher",
"Jim Wiggins"
]
},
{
title: "Finding Elmo",
authors: [ "Sam Fisher" ]
},
{
title: "Mercy me",
authors: [ ]
}
]
}
]
So to reiterate, How can I find all documents that have an array within another array where the 2nd array has one or more elements?
You can use $exists operator and refer to first element of an array using dot notation:
db.col.find({ "booksWithQuote.authors.0": { $exists: true } })
MongoDB documentation: https://www.mongodb.com/docs/manual/reference/operator/query/expr/
you can use $expr to achieve this
$expr:{$gte: [{$size: "$selectedArray"}, size]}

Find items that have at least one item in array and doesn't have other than array

I'm trying to make a complex mongo query and after hours of trial and only error I'm here to ask for help.
Giving the following documents:
{
// Item 1
name: 'Tranças Nordestinas',
author: 'Daiane Rosa',
ingredients: [
'wheatFlour',
'sugar',
'butter',
'bakingPowder',
'eggs'
]
}
{
// Item 2
name: 'Brigadeiro de Churros',
author: 'Cristine Russel',
ingredients: [
'sugar',
'chocolate'
]
}
{
// Item 3
name: 'Chá Mate Gelado',
author: 'Victor Onofre',
ingredients: [
'sugar',
'water'
]
}
{
// Item 4
name: 'Macarrão Verde ao Molho',
author: 'Victor Onofre',
ingredients: [
'greenPasta',
'tomatoSauce',
'butter'
]
}
If I query the array [ 'sugar', 'water' ], only the third item shoud be returned.
If I query the array [ 'water', 'chocolate', 'wheatFlour', 'sugar', 'butter', 'bakingPowder', 'eggs' ], items 1, 2 and 3 should be returned.
And If I query the array [ 'sugar' ], no item should be returned.
Couldn't find a way to do this kind of query.
Don't know if mongodb is the right choise to do this query, would mysql perform better in this situation?
Thanks.
You can try $redact with $setIsSubset for your query.
$setSubset to compare the ingredients arrays with input array and return true when the ingredients array is a subset of the input array, which includes equals too and $redact will use the result from above comparison; true value to keep and false value to remove the document.
db.collection.aggregate(
[{
$redact: {
$cond: {
if: {
$setIsSubset: ["$ingredients", ['sugar']]
},
then: "$$KEEP",
else: "$$PRUNE"
}
}
}]
);

count hasMany relation in mongodb/mongoid

enter code hereI"m currently working on a rails app running mongodb via mongoid. Let's say I have two collections , posts and comments, and they're linked by a HABTM relation, I'd like to select the posts that have the most comments, and I think the way to do it is to count the comments for each post and order by comments.count DESC. I don't know how to count the number of comments for each posts, add a column to the posts results like count(comments) AS comments_count in SQL, and order by this "pseudo-field".
Any thoughts on this ?
Thanks
EDIT, looking at the others answers related to this, I've tried:
db.posts.aggregate([
{ $group: {
_id: { post_id: '$post_id', comment_id: '$comment_id' }
}},
{ $group: {
_id: '$_id.post_id',
comments_count: { $sum: 1 }
}}]
, function(err, result){
console.log(result);
}
);
I'm getting
{
"result" : [{
"_id" : null,
"datasets_count" : 1
}],
"ok" : 1
}
The aggregation framework in MongoDB has operators for grouping, summing, and sorting.
The following is a working example of how to group, count, and sort posts by the number of comments.
Hope that this is what you want and that it helps.
models/post.rb
class Post
include Mongoid::Document
has_many :comments
field :text, type: String
end
models/comment.rb
class Comment
include Mongoid::Document
belongs_to :post
field :text, type: String
end
test/unit/post_test.rb
require 'test_helper'
require 'pp'
class PostTest < ActiveSupport::TestCase
def setup
Post.delete_all
Comment.delete_all
end
test "posts ordered by comment count" do
[ [ "Now is the time for all good men to come to the aid of their country.",
[ "Tally ho!" ] ],
[ "Twas brillig, and the slithy toves did gyre and gimble in the wabe.",
[ "Off with their heads!",
"The time has come, the walrus said.",
"Curiouser and curiouser." ] ],
[ "The quick brown fox jumped over the lazy dog.",
[ "The typewriter is mightier than the sword.",
"Dachshund is a badger dog." ] ]
].each do |post, comments|
post = Post.create(text: post)
comments.each{|comment| post.comments << Comment.create(text: comment)}
end
pipeline = [
{ '$group' => {
'_id' => '$post_id',
'count' => { '$sum' => 1 }
}
},
{ '$sort' => { 'count' => -1}
}
]
result = Comment.collection.aggregate(pipeline).to_a.collect do |post_id_count|
Post.find(post_id_count['_id'])
end
puts
pp result
end
end
rake test
Running tests:
[1/1] PostTest#test_posts_ordered_by_comment_count
[#<Post _id: 52717e7f7f11ba52d8000003, text: "Twas brillig, and the slithy toves did gyre and gimble in the wabe.">,
#<Post _id: 52717e7f7f11ba52d8000007, text: "The quick brown fox jumped over the lazy dog.">,
#<Post _id: 52717e7f7f11ba52d8000001, text: "Now is the time for all good men to come to the aid of their country.">]
Finished tests in 0.050494s, 19.8043 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips