I have a multidimensional array that looks like this:
{
"groups": [
{
"__v": 0,
"_create_date": "2014-08-20T23:00:12.901Z",
"_id": "53f5287ca78473a969001827",
"_last_message_date": "2014-08-20T23:04:36.347Z",
"activity": 0,
"blocked_users": [],
"created_by": {
"_id": "53e84b0eba84943c6d0003f8",
"_last_modified": "2014-08-20T00:11:05.399Z",
"first_name": "Jegg",
"last_name": "V"
},
"curated": false,
"diversity": 0,
"featured": false,
"flagged": false,
"last_message": {
"text": "let's talk beo",
"created_by": {
"_id": "53e84b0eba84943c6d0003f8",
"first_name": "Jegg",
"last_name": "V"
},
"_id": "53f52984a78473a969001833",
"_create_date": "2014-08-20T23:04:36.347Z"
},
"member_count": 1,
"messages_count": 1,
"name": "Test",
"public": true,
"recency": 52182276.347,
"score": 52182276.347,
"tags": []
},
This structure repeats over 3000 times creating a very large multidimensional array. I think I can use array_chunk($array, 300) to break the array into smaller chunks. But I can't figure out how to access them exactly.
What I want to do is independently loop through the newly chunked arrays. So I'd like to end up with something like:
$array1 = {...}
$array2 = {...}
$array3 = {...}
$array4 = {...}
... and so on
THen I could loop through each of the newly created arrays, which are essentially smaller groups of the original array, but of 3000 arrays in one multidimensional array as I have in the first place, I end up with these smaller ones of 300 arrays each.
I hope this makes sense, I'm kinda out of my league. Help is always appreciated.
I think your array is in json format.
First decode it and then pass to array_chunk method.
array_chunk($input_array, 300));
then access them as $input_array[0][0], $input_array[0][1]....... $input_array[0][299], $input_array[1][0], $input_array[1][1].....
EDIT: oh, somehow I entirely misread the question. array_chunk is something worth looking into.
You could try using extract to fetch array values to the "global" variable namespace.
extract takes three arguments: the array you wish to extract, flags, and prefix if needed.
I'm not sure how non-associative arrays are extracted, but you could try
$full_array = array(
array( ... ),
array( ... ),
array( ... ),
array( ... ),
...
);
// EXTR_PREFIX_ALL prefixes all extracted keys with wanted prefix (the third param).
$extract_amount = extract( $full_array, EXTR_PREFIX_ALL, 'prefix' );
Now you should have the array extracted and available for use with variable names $prefix0, $prefix1, $prefix2 and so on.
I'm not sure how smart it is to extract an array with hundreds of available values.
Related
I have a multidimensional array that looks like this:
"fields" => {
"name": "John"
"lastname": "Doe"
}
"data" => {
"person": array:2 [
0 => {
"adress": "foo"
"phone": "bar"
},
1 => {
"adress": "foo1"
"phone": "bar1"
}
]
}
Now I want to check that in this multidimensional array the follow keys exist so my second array to compare looks like this:
[
'fields' => [
"name",
"lastname",
],
"data" => [
"person" => [
"adress",
"phone"
]
],
]
Now here's what I've tried to compare the keys of my second array with the first array:
$result = array_diff_key($firstArray, array_flip($secondArray));
But this gives me an
array_flip(): Can only flip STRING and INTEGER values!
So I need a way to recursively flip the array or maybe I am doing it a bit wrong
I have a multidimensional array that looks like this ...
This is not an array, whatever you've done it now is amorphous trash, not an array not an json object.
$result = array_diff_key($firstArray, array_flip($secondArray));
array_diff_key() finds differences - unique keys
array_flip() replaces keys with values in the array
So I need a way to recursively flip the array or maybe I am doing it a bit wrong
Don't flip arrays, stop searching differences if you want to find the same keys.
You need at least 2 arrays (or objects) to comparison. If you will have 2 (not nested) arrays use
array_intersect_key($array1,$array2);
For nested arrays... you have to get all the keys and then compare.
I have an array of objects, and want to update an attribute of one of the objects.
$objs = [
['value' => 2, 'key' => 'a'],
['value' => 3, 'key' => 'b'] ,
];
Let's say I want to set the 'value' of the object with 'key'=>'a' to 5.
Aside from iterating over the array searching for the key, is there any quicker/efficient way of doing this?
Thanks.
EDIT: There is debate as to why I can't use an associative array. It is because this array is obtained from a JSON value.
If my JSON object is this:
"obj": {
"a": {
"key": "a",
"value": 2
},
"b": {
"key": "b",
"value": 3
}
}
There is no guarantee that the order of the objects will be retained, which is required.
Hence I need an index in each object to be able to sort it using usort(). So my JSON needs to be:
"obj": {
"a": {
"key": "a",
"value": 2,
"index": 1
},
"b": {
"key": "b",
"value": 3,
"index": 2
}
}
But I cannot use usort() on an object, only on arrays. So my JSON needs to be
"obj": [
{
"key": "a",
"value": 2,
"index": 1
}, {
"key": "b",
"value": 3,
"index":2
}
]
Which brings us to the original question.
By using array_column(), you can pull all the values with the index key in the arrays. Then you can find the first occurrence of the value a by using array_search(). This will only return the first index where it finds a value. Then you can simply replace that value, as you now have the index of that value.
$keys = array_column($objs, 'key');
$index = array_search('a', $keys);
if ($index !== false) {
$objs[$index]['value'] = 5;
}
See this live demo.
http://php.net/array_search
http://php.net/array_column
You can make the array associative with array column. That way you can directly assign the value.
$objs = [ ['value'=>2, 'key'=>'a'], ['value'=>3, 'key'=>'b'] ];
$objs = array_column($objs, null, "key");
$objs['a']['value'] = 5;
https://3v4l.org/7tJl0
I want to recommend you reorginize your array lake that:
$objs = [
'a' => ['value'=>2, 'key'=>'a'],
'b' => ['value'=>3, 'key'=>'b']
];
And now
if( array_key_exists( 'a', $objs )) {
$objs ['a'] ['value'] = 5;
}
I had it like that initially. But I need for the objects to have an
index value in them, so I can run usort() on the main array. This is
because the array comes from JSON where the original order isn't
respected
Then create an index array:
// When fill `$objs` array
$objs = [];
$arrIndex = [];
$idx = 0;
foreach( $json as $item ) {
$arrIndex [ $item ['key']] = $idx;
$objs [$idx ++] = $item;
}
// And your task:
if( array_key_exists( 'a', $arrIndex )) {
$objs [ $arrIndex ['a']] ['value'] = 5;
}
Aside from iterating over the array searching for the key, is there
any quicker/efficient way of doing this?
You have to pay the price of iteration either way.
You can search your collection for the interesting object (takes linear time), or you form some kind of dictionary data structure, e.g. hash table (takes linear time) and then find the interesting object in constant time.
No free lunches here.
I have a PHP function that give me two seperate JSON arrays (array1 and array2). How can I merge them in an object with properties, like this:
{
"array1": [ {"type": "column", "valueField": ..., "descriptionField": ..., }]
"array2": [ {"type": "column", "valueField": ..., "descriptionField": ..., }]
}
Thanks in advance
This is just a simple example of how you can do it, you can improve it however you like, depending what kind of situation you need it for.
// Initialising arrays
$array1 = ['type' => 'column', 'valueField' => '.1.', 'descriptionField' => '.11.'];
$array2 = ['type' => 'column', 'valueField' => '.2.', 'descriptionField' => '.22.'];
// Turn them manually into jsons
$obj1 = json_encode($array1);
$obj2 = json_encode($array2);
// Merge the two jsonified arrays in a single array with whichever keys you prefer
$mix = ['array1' => $obj1, 'array2' => $obj2];
// Turn the merged "mix" array into json
$mix = json_encode($mix);
// Check the output
printf($mix);
/* Prints out:
{
"array1":"{"type":"column", "valueField":".1.", "descriptionField":".11."}",
"array2":"{"type":"column", "valueField":".2.", "descriptionField":".22."}"
}
*/
You can fiddle around with it in in this SANDBOX, have some fun with it.
I create a platform in PHP/MYsql and I am now migrating to mongo
My old query for mysql :
select sum(game_won) as game_won,count(id) as total,position
from games_player_stats
where position < 6 and position > 0 and user_id = :pa_id
group by position
order by total desc
The new json format looks like this:
{
"region" : "EUW",
"players" : [
{
"position" : 2,
"summoner_id" : 123456,
"game_won": 1
},
{
"position" : 1,
"summoner_id" : 123459,
"game_won": 0
},
{
"position" : 3,
"summoner_id" : 123458,
"game_won": 1
},
{
"position" : 4,
"summoner_id" : 123457,
"game_won": 0
}
]
}
Having multiple documents like this, I need to find howmany times summoner_id 123456 has had position 2 or any of the other positions 1-6 and howmany times did he win in that position
The Index needs to be queryable on region and summoner_id
Outcome would look like
{
"positions" :
[
{ "position" : 1,
"total" : 123,
"won" : 65
},
{ "position" : 2,
"total" : 37,
"won" : 10
}
]
}
Would I need to use Map/Reduce for this?
The best results for this are obtained by the aggregation framework for MongoDB. It differs from mapReduce in that all operations are performed using "natively coded operators" as opposed to the JavaScript evaluation that is used by mapReduce.
This means "faster", and significantly so. Not to mention there are also certain parts of what you are looking for in a result that actually favour the "multiple group" concept that is inherently available to a "pipeline" of operations, that would otherwise be a fairly ugly accumulator using mapReduce.
Aggregation Pipeline Formats
The best approach will differ depending on the MongoDB "server" version you have available.
Ideally with MongoDB 3.2 you use $filter to "pre-filter" the array content before processing with $unwind:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
// Filter the array content for matched conditions
{ "$project": {
"players": {
"$filter": {
"input": "$players",
"as": "player"
"cond": {
"$and": [
{ "$eq": [ "$$player.summoner_id", 123456 ] },
{ "$gte": [ "$$player.position", 1 ] },
{ "$lte": [ "$$player.position", 6 ] }
]
}
}
}
}},
// Unwind the array contents to de-normalize
{ "$unwind": "$players" },
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
db.collection.aggregate(pipeline);
For MongoDB 2.6.x releases, still "pre-filter" but using $map and $setDifference:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
// Filter the array content for matched conditions
{ "$project": {
"players": {
"$setDifference": [
{ "$map": {
"input": "$players",
"as": "player",
"in": {
"$cond": {
"if": {
"$and": [
{ "$eq": [ "$$player.summoner_id", 123456 ] },
{ "$gte": [ "$$player.position", 1 ] },
{ "$lte": [ "$$player.position", 6 ] }
]
},
"then": "$$player",
"else": false
}
}
}},
[false]
]
}
}},
// Unwind the array contents to de-normalize
{ "$unwind": "$players" },
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
And for earlier versions with the aggregation framework from MongoDB 2.2, "post filter" with $match "after" the $unwind:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
{ "$unwind": "$players" },
// Post filter the denormalized content
{ "$match": {
"players.summoner_id": 123456,
"players.position": { "$gte": 1, "$lte": 6 }
}},
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
Walkthrough
Matching the Document: This is primarily done using $elemMatch since you are looking for "multiple" conditions within the array elements. With a "single" condition on an array element it is fine to use "dot notation":
"players.summoner_id": 12345
But for anything more than "one" condition you need to use $elemMatch, otherwise all the statement is really asking is "does this match something within the array?", and that does not contain to "all" within the element. So even the $gte and $lte combination alone is actually "two" conditions, and therefore requires $elemMatch:
"players": {
"$elemMatch": {
"position": { "$gte": 1, "$lte": 6 }
}
}
Also noting here that from "1 to 6 inclusive" means "greater than or equal to" and vice versa for the "less than" condition.
-
"Pre-filtering": Noting here that the eventual goal is to "group" by an element within the array, being "position". This means that eventually you are going to need to $unwind the content to do that.
However, the $unwind pipeline operation is going to be quite costly, considering that it "takes apart" the array and creates a new document to process for each array member. Since you only want "some" of the members that actually match the conditions, it's desirable to "remove" any un-matched content from the array "before" you de-normalize this content.
MongoDB 3.2 has a good method for this with the $filter operator. It performs exactly as named by "filtering" the content of the array to only elements that match a particular set of conditions.
In an aggregation pipeline stage we use it's "logical variants" of the operators such as $gte and $lte. These return a true/false value depending on where the condition matched. Also within the array, these can actually be referred to using the member fields using "dot notation" to the alias argument in "as" which points to the current processed member.
The $and here is also another "logical operator" which does the same true/false response. So this means "all" the arguments in it's array of arguments must be met in order to return true. For the $filter itself, the true/false evaluated in "cond" determines whether to return the array element or not.
For MongoDB 2.6 which does not have the $filter operator, the same is represented with the combination of $map and $setDifference Simply put the $map looks at each element and applies an expression within "in". In this case we use $cond which as a "ternary" operator evaluates an 'if/then/else` form.
So here where the "if" returns true the expression in "then" is returned as the current array member. Where it is false, the expression in else returns, and in this case we are returning the value of false ( PHP False ).
Since all members are actually being returned by the result of $map we then emulate $filter by applying the $setDifference operator. This does a comparison to the members of the array and effectively "removes" any members where the element was returned as false from the result. So with distinct array members such as you have, the resulting "set" ( being a "set" of "unique" elements) just contains those elements where the condition was true and a non-false value was returned.
"Post" filtering: The alternate approach which is mandatory for server versions below MongoDB 2.6 is to "post" filter the array content. Since there are no operators in these versions that allow such actions on array content before $unwind, the simple process here to applying another $match to the content "after" the $unwind is processed:
{ "$match": {
"players.summoner_id": 123456,
"players.position": { "$gte": 1, "$lte": 6 }
}}
Here you use "dot notation" since each array element is now actually it's own document, and there is nothing else to compare to other than looking at the conditions on the specified path.
This is not ideal, since when you process $unwind all of the elements that actually don't match the conditions are still present. This ultimately means "more documents to process" and has the double cost of:
Had to create a new document for every member despite it not matching the conditions
Now you have to to apply the condition across every "document" emitted as a result of $unwind
This has a potentially huge impact on performance, and for that reason the modern MongoDB releases introduce ways to act on arrays without resorting to $unwind in order to process. You still need it for the remaining processing since you are "grouping" on a property contained within the array. But it is of course desirably to "get rid of un-matched elements first".
Remainging Grouping: Now the elements are filtered and de-normalized, it only remains to do the actual $group condition that will total things by the "position" within each element. This is a simple matter of providing the grouping key to "_id" and using the appropriate data accumulation.
In this case you have two constructs, being:
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
The basic { "$sum": 1 } is just "counting" the elements matched for each group and the { "$sum": "$players.won" } actually uses the "won" value to accumulate a total. This is pretty standard usage for the $sum accumulator.
Of course your output shows the content within an "array", so the following stages are really "optional" since the real work of actually "grouping" is already done. So you could actually just use the results in the form provided up to this first $group, and the remaining just puts everything into a single document response rather than "one document per 'position' value", which would be the return at this point.
The first note is output from $group is not ordered. So if you want a specific order of results ( i.e by position ascending ) then you must $sort after that $group stage. This will order the resulting documents of the pipeline as of the point where it is applied.
In your case you are actually asking for a sort on "total" anyway, so you would of course apply this with -1 meaning "descending" in this case. But whatever the case, you still should not presume that the output from $group is ordered in any way.
The "second" $group here is basically cosmetic in that this is what makes a "single document" response. Using null ( PHP NULL ) in the grouping key basically says "group everything" and will produce a single document in response. The $push accumulator here is what actually makes the "array" from the documents in the pipeline preceding this.
Wrap-Up
So that's the general process in accumulating data like this:
Match the documents required to the conditions, since after all it would be a waste to apply conditions later to every document when they don't even contain array elements that would match the conditions you eventually want.
Filter the array content and de-normalize. Ideally done as a "pre-filter" where possible. This gets the documents into a form for grouping, from there original array form.
Accumulate the content using appropriate operators for the task, either $sum or $avg or $push or any other available according to needs. Nothing also that depending on structure and conditions you can always use "more than one" $group pipeline stage.
PHP Translation
The initial example in PHP notation:
pipeline = array(
array(
'$match' => array(
'players' => array(
'$elemMatch' => array(
'summoner_id' => 123456,
'position' => array( '$gte' => 0, '$lte' => 6 )
)
)
)
),
array(
'$project' => array(
'$filter' => array(
'input' => '$players',
'as' => 'player',
'cond' => (
'$and' => array(
array( '$eq' => array( '$$player.summoner_id' => 123456 ) ),
array( '$gte' => array( '$$player.position' => 1 ) ),
array( '$lte' => array( '$$player.position' => 6 ) )
)
)
)
)
),
array( '$unwind' => '$players' ),
array(
'$group' => array(
'_id' => '$players.position',
'total' => array( '$sum' => 1 ),
'won' => array( '$sum' => '$players.won' )
)
),
array( '$sort' => array( 'total' => -1 ) ),
array(
'$group' => array(
'_id' => NULL,
'positions' => array(
'$push' => array(
'position' => '$_id',
'total' => '$total',
'won' => '$won'
)
)
)
)
)
$result = $collection->aggregate($pipeline);
When making data structures in PHP that you are comparing to JSON, it is is often useful to check your structure with something like:
echo json_encode($pipeline, JSON_PRETTY_PRINT)
Then you can see that what you are doing in PHP notation is the same as the JSON example you are following. It's a helpful tip so that you cannot really go wrong. If it looks different then you are not doing the "same" thing.
First this is not a duplicate questions. I've looked through some similar problem and most of the answer is what I am using right now.
Here is the problem set up,
on PHP side
$array = array('name' => 'a', 'data' => array('0'=>15,'0.25'=>'18','0.35'=>19,'1' =>20));
echo json_encode($array);
on the JS side
data = $.parseJSON(data); // data is the return from the php script
above
As you can see the $array['data'] is an associative array with numeric number as its key and sorted in order. While parsing into JSON, javascript altered the order of that array and sorted 0 and 1 as numeric key and put them to the head of the object.
I know this is standard behavior for certain browser such as chrome, and IE9.
I've read somewhere that people suggest stick with array strictly if I want to maintain the order of the array.
But my question is how do you feed back an array from PHP to javascript as an array instead of using json object? Or is there other solution to this kind of problem . Thanks for the input in advance.
Thanks for the input in advance
Use an array to maintain order, and then an object to create the map. There are two ways. I would suggest:
$array = array('name' => 'a', 'data' =>
array(
array('key' => 0, 'value' => 15),
array('key' => 0.25, 'value' => 18),
array('key' => 0.35, 'value' => 19),
array('key' => 1, 'value' => 20),
)
);
echo json_encode($array);
Which will give you the JSON:
{
"name": "a",
"data": [
{"key": 0, "value": 15},
{"key": 0.25, "value": 18},
{"key": 0.35, "value": 19},
{"key": 1, "value": 20}
]
}
Then you will have order but to look up a certain key will be more difficult. If you want that to be easy you can return a mapping object as well like this:
$array = array('name' => 'a', 'data' =>
array(
"0" => 15,
"0.25" => 18,
"0.35" => 19,
"1" => 20,
),
'order' => array("0", "0.25", "0.35", "1")
);
echo json_encode($array);
Which will give you:
{
"name": "a",
"data": {
"0": 15,
"0.25": 18,
"0.35": 19,
"1": 20
},
"order": ["0", "0.25", "0.35", "1"]
}
One of these two methods of returning your data should prove to be the most useful for your specific use case.
Actually, it's PHP that takes the "0" and "1" keys and makes them numeric keys. This has nothing to do with your JavaScript.
There isn't any real way to work around this, but ideally your code should not rely on such things as "what order the keys of an object are in". It may be a better idea, just from what I see here, to separate the data into an array of keys and an array of values, then zip them back together on the JS side.
I'd suggest another field for storing order.
$array = array('name' => 'a',
'data' => array('0'=>15,'0.25'=>'18','0.35'=>19,'1' =>20),
'order'=> '0,0.25,0.35,1'
);
echo json_encode($array);