MongoDB $multiply operator PHP example? - php

I'm trying to multiple all "point" fields.
$out = $kuponmac->aggregate(
array('$match' => array('random'=>$random, 'ekleyen'=>$ekleyen)),
array('$group' => array(
'_id' => '$state',
'totalPop' => array('$multiply' => '$point')
)
),
array(
'$match' => array('totalPop' => array('$gte' => 0))
)
);
this is my code. I'm trying to use multiply but I couldn't use it correctly.
How can I make multiplication all "point" fields?
I'm looking http://docs.mongodb.org/manual/reference/aggregation/multiply/ but there's no detailed explanation or example.
could u help me?

It says on the page how it works:
Takes an array of one or more numbers and multiples them, returning the resulting product.
So you do:
array('$multiply' => array('$point', '$otherNumber'))
Since you cannot mulitply one number meaningfully.

$multiply is not a supported $group operator (unlike $sum), so you cannot used it in that context to compute the value for a grouped field. Instead, $multiply could be used in an expression, which may itself be included as an argument to a $group operator or as a field value in a projection.
I attempted to rewrite your pipeline as follows:
[
{ $group: { _id: "$state", points: { $push: "$point" }}},
{ $project: { product: { $multiply: "$points" }}}
]
The idea here is that we'd use $push to collect all point values into an array for each state's $group projection, and then compute the product, using that array as the argument to $multiply.
Unfortunately, this resulted in an error, as MongoDB treated the de-referenced points field as a single argument to be multiplied, instead of an array of numbers. This was recently reported as a bug in SERVER-10676. I'd encourage you to watch that issue for resolution.
Additionally, I opened SERVER-10682 to discuss adding a $product operator for $group, which would be ideal for your use case.

Related

Simplify PHP array with same items

I have this PHP array:
$this->user_list = array( 0 => 'Not paid',1 => 'Not paid', 2 => 'Not paid', 7 => 'Waiting, 15 => 'Waiting', 10 => 'Cancelled' );
How can I simplify this array as the id numbers are different, but some of them have same status?
I tried it like this:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
but it doesn't work as expected.
Basically I want to achieve this:
echo $this->user_list[15] should give me Waiting, echo $this->user_list[10] should give me Cancelled, etc. So this is working in my first array very well, I am just thinking about grouping duplicate names there.
As mentioned by other contributors, there is no native support in the PHP grammar for your intended use case. As clearly stated in the PHP: Arrays documentation:
An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments.
So basically each element in an array is a key => value pair, which means you cannot associate multiple keys to a single element.
This also explains why your first tentative didn't work:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
If you don't specify a key for an element, PHP uses a progressive index (0, 1, ...). So basically in the example above, the first zero is not actually a key, but a value, and PHP binds it to the key = 0. Maybe it could be easier for you to understand how it works if you print a var_dump or print_r of $this->user_list. You would get something similar to the following structure (NOTE: I have simplified the structure to make it more clear):
[
0 => [
0 => 0
1 => 1
2 => "Not paid"
],
1 => [
0 => 7,
15 => "Waiting"
],
10 => "Cancelled"
]
So how do we resolve this problem? Well... actually there is no need to contort the structure by swapping keys with values as other contributors seem to suggest. Changing the structure might simplify your "data entry" work but might also create big issues in other parts of the program because who knows, maybe accessing the invoice data by "ID" is simply more efficient than by "status" ... or something.
Since PHP does not provide such a feature out of the box, I believe a better solution would be to develop our own function; a good starting point could be the one in the example below.
function explode_array($config, $sep = ',') {
$res = [];
foreach($config as $configKey => $value) {
// split key values
$keys = explode($sep, $configKey);
foreach($keys as $key) {
$res[$key] = $value;
}
}
return $res;
}
$config = [
'0,1,2' => 'Not paid',
'7,15' => 'Waiting',
'10' => 'Cancelled'
];
$myArr = explode_array($config);
print_r($myArr);
The idea is quite simple: since we cannot use an array as key we leverage the next best data type, that is a CSV string. Please note there is no error handling in the above code, so the first thing you may want to do is adding some validation code to the explode_array (or however you wish to name it) function.
you should use like this. if id number is invoice id or something else and other value is there status about it.
$arr = array(
'Not paid' => [0,1,2] ,
'Waiting' => [5,6],
'Cancelled' =>[8]
);
foreach($arr as $key => $val){
foreach($val as $keys => $vals){
echo "invoiceid ".$vals ." status ".$key;
echo"<br>";
}
}
// for only one status you can use like this
foreach($arr['Not paid'] as $key => $val){
echo $val;
echo"<br>";
}
just try to run this and check output.
PHP has no built-in function or structure for handling cases like this. I'd use a simple array value-cloning function to map your duplicates. Simply have one instance of each status, then map the aliases, and then run a function that clones them in. As follows:
// Status list:
$ulist = [ 0 => 'Not paid', 7 => 'Waiting', 10 => 'Cancelled' ];
// Alternative IDs list, mapped to above source IDs:
$aliases = [ 0 => [1,2], 7 => [15] ];
// Function to clone array values:
function clone_values(array &$arr, array $aliases)
{
foreach($aliases as $src => $tgts) {
foreach($tgts as $tgt) {
$arr[$tgt] = $arr[$src];
}
}
ksort($arr); // If the order matters
}
// Let's clone:
clone_values($ulist, $aliases);
This results in the following array:
array(6) {
[0] · string(8) "Not paid"
[1] · string(8) "Not paid"
[2] · string(8) "Not paid"
[7] · string(7) "Waiting"
[10] · string(9) "Cancelled"
[15] · string(7) "Waiting"
}
....which can be accessed as you expect, here $ulist[2] => Not paid, etc. If the use case is as simple as illustrated in the OP, I'd personally just spell it out as is. There's no dramatic complexity to it. However, if you have dozens of aliases, mapping and cloning begins to make sense.
As said in the comments, you can't have multiple keys with one value. The best way is to use the keyword => [ number, number, number...] construction.
//set a result array
$result = [];
//loop the original array
foreach ( $this->user_list as $number => $keyword ){
//if the keyword doesn't exist in the result, create one
if(!isset ( $result [ $keyword ] ) ) $result[ $keyword ] = [];
//add the number to the keyword-array
$result[ $keyword ] [] = $number;
}

How to apply a filter to update element in array of arrays?

I am trying to create a PHP application that lets me uploading data to a MongoDB collection. For that I have "installed" the PHP driver with no problems.
However, I cannot find anyway -neither in the PHP guide- how could I update an element in an array of arrays.
Collection structure
As you may see, _unidades is an array of arrays. Each of those arrays contains an ID, a string and another array. In my case, which will be chosen depends on previous param -it has to match with element 1 of one of them.
Once I have selected that structure, I want to insert a new array into its array of arrays (position 2).
Regarding code, I tried the following:
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->update(
[
'_isbn' => (int) $_POST["isbn"],
'_topics' =>
[
'0' => (int) $_POST["topic"]
]
],
[
'$set' => [
'1' => array($_POST["sentence"], $_POST["question"])
]
]
);
$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 100);
$resultado = $manager->executeBulkWrite('libreee.faq', $bulk, $writeConcern);
However as you can see I am not capable to determine at least that it doesn't have to be an specific array (7th line).
Once said that I look forward to receiving your help.
Thanks a lot in advance.
Regards,
Ciconia.

How to find all documents where the value of a field is null or empty

I'm trying to write a simple Mongo query to return all documents where the value of a field is null (or an empty array, I'm not sure what you call it).
Here is my query;
db.getCollection('contact').find({'poco.name.email':false})
Here is a screenshot of my collection using RoboMongo;
Ultimately, I need to transfer this to some PHP. So far I have this working;
$conditions = array_merge($conditions, [
'owner.$id' => $this->getId(),
'poco.name.familyName' => "Smith",
//not sure what goes here.. something like
//'poco.emails' => null,
]);
return Model_Mongo_Contact::getContacts($conditions, $sort, $fields, $limit, $skip);
That's probably going to be harder to answer without more access to the methods.. Or maybe not I am very new to Mongo it might be really obvious.
I'm not PHP expert but I'll be doing in mongo shell as:
db.collection.find({
$and: [
{"poco.email":{$exists: true}},
{"poco.email":{$ne: []}}
]})
Query above will list all documents where poco.name.email is not missing and is not empty.
The basic concept when looking for an "empty" array or even "non-existent" is to use a property of "dot notation" and search where the 0 index of the array does not exist. This can be achieved with using the $exists operator:
$conditions = array_merge($conditions, [
'owner.$id' => $this->getId(),
'poco.name.familyName' => "Smith",
'poco.emails.0' => array( '$exists' => FALSE )
]);
That condition is true when either there is no property at all, or if it is something other than an array ( therefore no "0" field property ) or indeed an "empty" array ( since there is no content at the first index ).
This works best when the field is actually "indexed". So if in your "normal" structure you have "sub-documents" inside the array element with say a consistent field named "address" which is indexed as "poco.emails.address", then you are best of pointing to that specific indexed property to see if it $exists or not:
$conditions = array_merge($conditions, [
'owner.$id' => $this->getId(),
'poco.name.familyName' => "Smith",
'poco.emails.0.address' => array( '$exists' => FALSE )
]);
But if the array consists purely of "values" only and no "sub-documents", then simply testing for the 0 index position of the array as initially demonstrated will be enough.

Returning array is faster than returning an element of the array

I just conducted this very interesting experiment and the results came out quite surprising.
The purpose of the test was to determine the best way, performance-wise, to get an element of an array. The reason is that I have a configuration class which holds settings in an associative mufti-dimensional array and I was not quite sure I was getting these values the best way possible.
Data (it is not really needed for the question, but I just decided to include it so you see it is quite a reasonable amount of data to run tests with)
$data = array( 'system' => [
'GUI' =>
array(
'enabled' => true,
'password' => '',
),
'Constants' => array(
'URL_QUERYSTRING' => true,
'ERRORS_TO_EXCEPTIONS' => true,
'DEBUG_MODE' => true,
),
'Benchmark' =>
array(
'enabled' => false,
),
'Translations' =>
array(
'db_connection' => 'Default',
'table_name' =>
array(
'languages' => 'languages',
'translations' => 'translations',
),
),
'Spam' =>
array(
'honeypot_names' =>
array(
0 => 'name1',
1 => 'name2',
2 => 'name3',
3 => 'name4',
4 => 'name5',
5 => 'name6',
),
),
'Response' =>
array(
'supported' =>
array(
0 => 'text/html',
1 => 'application/json',
),
),]
);
Methods
function plain($file, $setting, $key, $sub){
global $data;
return $data[$file][$setting][$key][$sub];
}
function loop(...$args){
global $data;
$value = $data[array_shift($args)];
foreach($args as $arg){
$value = $value[$arg];
}
return $value;
}
function arr(){
global $data;
return $data;
}
Parameters (when calling the functions)
loop('system', 'Translations', 'table_name', 'languages');
plain('system', 'Translations', 'table_name', 'languages');
arr()['system']['Translations']['table_name']['languages'];
Leaving aside any other possible flaws and focusing on performance only, I ran 50 tests with 10000 loops. Each function has been called 500000 times in total. The results are in average seconds per 10000 loops:
loop: 100% - 0.0381 sec. Returns: languages
plain: 38% - 0.0146 sec. Returns: languages
arr: 23% - 0.0088 sec. Returns: languages
I was expecting loop to be quite slow because there is logic inside, but looking at the results of the other two I was pretty surprised. I was expecting plain to be the fastest because I'm returning an element from the array and for the opposite reason arr to be the slowest because it returns the whole array.
Given the outcome of the experiment I have 2 questions.
Why is arr almost 2 times faster than plain?
Are there any other methods I have missed that can outperform arr?
I said this in the comment, but I decided it's pretty close to an answer. Your question basically boils down to why is 2+2; not faster than just plain 2;
Arrays are just objects stored in memory. To return an object from a function, you return a memory address (32 or 64 bit unsigned integer), which implies nothing more than pushing a single integer onto the stack.
In the case of returning an index of an array, that index really just represents an offset from the base address of the array, so everytime you see a square bracket-style array access, internally PHP (rather the internal C implementation of PHP) is converting the 'index' in the array into an integer that it adds to the memory address of the array to get the memory address of the stored value at that index.
So when you see this kind of code:
return $data[$file][$setting][$key][$sub];
That says:
Find me the address of $data. Then calculate the offset that the string stored in $file is (which involves looking up what $file is in memory). Then do the same for $setting, $key, and $sub. Finally, add all of those offsets together to get the address (in the case of objects) or the value (in the case of native data types) to push on to the stack as a return value.
It should be no surprise then that returning a simple array is quicker.
That's the way PHP works. You expect, that a copy of $data is returned here. It is not.
What you acutaly have, is a pointer (Something like an ID to a specific place in the memory), which reference the data.
What you return, is the reference to the data, not the data them self.
In the plain method, you search for the value first. This cost time. Take a look at this great article, which show, how arrays are working internal.
Sometimes Code say's more then words. What you assume is:
function arr(){
global $data;
//create a copy
$newData = $data;
//return reference to $newData
return $newData;
}
Also you should not use global, that is bad practise. You can give your array as a parameter.
//give a copy of the data, slow
function arr($data) {
return $data;
}
//give the reference, fast
function arr(&$data) {
return $data;
}

Multi-staged sorting of array based on subarray values using comparison functions

I am in a tricky situation where i need to sort an array by values that lie within its subarray, but i need the result to be staged. In other words, sorting should be done by one OR more priorities.
The problem is that the entire sorting process is configurable by the user, so hardcoding anything is not an option. I need to stay flexible, but i limit the options by providing predefined sort functions.
Let's get into it:
In this example we will sort a list of print formats. We will use only two possible properties.
The user configures the sorting process in an INI file:
sort_priority="special_deal:desc,ratio:asc"
Description:
// special_deal -> This is a binary flag - if set to 1 the print format is a special deal and should therefore be presented first
// ratio -> This is the ratio of the given print format (i.e. 0.75 (that's a 3:4 format) or 1 (that's a 1:1 format))
In the code, the configuration is broken apart:
$toSort=array(<OUR ARRAY WE WANT TO SORT>);
$sortKeys=explode(',', 'special_deal:desc,ratio:asc');
// we then iterate through the defined keys
foreach($sortKeys as $sortKey){
// we put together the name of the predefined sort function
if(strstr($sortKey, ':')) {
list($skey,$sdir)=explode(':', $sortKey);
$methodName='sort_by_'.$skey.'_'.$sdir;
} else $methodName='sort_by_'.$sortKey.'_asc';
// so $methodName can (for instance) be: sort_by_special_deal_asc
// or: sort_by_ratio_desc
// if the sort function is available, we apply it
if(is_callable($methodName))
usort($toSort, $methodName);
}
And our sort functions look like this:
function sort_by_special_deal_asc($a, $b){
return ($a['specialDeal']!=$b['specialDeal']);
}
function sort_by_special_deal_desc($a, $b){
return ($a['specialDeal']==$b['specialDeal']);
}
function sort_by_ratio_asc($a, $b){
if($a==$b) return 0;
return $a['ratio']<$b['ratio'] ? -1 : 1;
}
function sort_by_ratio_desc($a, $b){
if($a==$b) return 0;
return $a['ratio']>$b['ratio'] ? -1 : 1;
}
On the the problem at hand ...
The above solution works fine, but only for the last applied sort function. So when we iterate through the sort functions that are to be applied, each call to usort() will cause a reordering of all the elements in the array. The problem is, we want the sorting to be staged (or stacked), so in this given example that would mean, practically:
1.) Sort all entries so that the ones that are a special deal come first
2.) Then sort all entries by their ratio
Here is an example on how the data can look like:
$formats=array(
array(
'format' => '30x40',
'ratio' => 0.75
),
array(
'format' => '60x90',
'ratio' => 0.667
),
array(
'format' => '50x50',
'ratio' => 1
),
array(
'format' => '60x80',
'ratio' => 0.75,
'specialDeal' => 1
)
);
And the desired result, given the above sorting feature, should be:
$formats=array(
array(
'format' => '60x80',
'ratio' => 0.75,
'specialDeal' => 1
),
array(
'format' => '60x90',
'ratio' => 0.667
),
array(
'format' => '30x40',
'ratio' => 0.75
),
array(
'format' => '50x50',
'ratio' => 1
),
);
I hope this explains the issue properly.
Can anybody point me in the right direction here? How can i achieve this, dynamically, at best by using usort() ?
Thank You!
EDIT: Please note - my comparison functions (see above) were faulty. There were two problems:
1.) Returning boolean was wrong - returning -1, 0 or 1 was the way to go.
2.) Comparison of $a and $b as complete arrays/objects was not correct - the right is to compare the specific values within those arrays (the ones the function is supposed to compare).
See the accepted answer and the according comments section for details.
Build an array like this by parsing the user's sort preferences:
$sortMethods = array('sort_by_ratio_desc', 'sort_by_special_deal_asc');
Then sort using a comparison like this:
usort($array, function ($a, $b) use ($sortMethods) {
foreach ($sortMethods as $method) {
$result = $method($a, $b);
if ($result != 0) {
break;
}
}
return $result;
});
Check out the comments for uasort in the php.net manual - http://php.net/manual/en/function.uasort.php
Specifically the one with dynamic callbacks posted by dholmes.

Categories