This question already has answers here:
Merging and group two arrays containing objects based on one identifying column value
(4 answers)
Closed last month.
What I am originally trying to do is merge two objects
I have one object like this
{#23 ▼
+"Name": "Consultation"
+"ServiceId": "1024"
+"Price": ".00"
}
Another object
{#362 ▼
+"StartTime": "9:15AM"
+"ServiceId": "1024"
}
I am looking for a way to merge these two objects based on it's ServiceId, so I get a single object like this
{#1 ▼
+"Name": "Consultation"
+"ServiceId": "1024"
+"Price": ".00"
+"StartTime": "9:15AM"
}
Of course, if the ServiceId between the two objects do not match, it should not merge.
Any ideas on how to solve the error?
There seems to be two parts to your question. The first part is how to merge the output of two objects returned by json_decode. The second part is how to merge them only if the ServiceId match.
Part 1
json_decode produces, by default, objects of class \StdClass. If you want merge two \StdClass objects into a third \StdClass object, you need some judicious casting:
$a = json_decode('{"Name":"Consultation", "ServiceId":"1024", "Price":".00"}');
$b = json_decode('{"StartTime": "9:15AM", "ServiceId": "1024"}');
$c = (object)array_merge((array)$a, (array)$b);
var_dump($c);
Here, $a and $b are \StdClass objects. Cast them to array, merge, then cast them back to \StdClass.
That's a bit of a round-about way to go. So, you can benefit from working with these as arrays from the get-go. json_decode takes an optional second argument, which instructs it to return an array:
$a = json_decode('{"Name":"Consultation", "ServiceId":"1024", "Price":".00"}', true);
$b = json_decode('{"StartTime": "9:15AM", "ServiceId": "1024"}', true);
$c = array_merge($a, $b);
var_dump($c);
This works in arrays the whole time. If you later want $c to be a \StdClass object, you can cast it using $c = (object)$c; as was done in the first example.
See these live on 3v4l.org.
Part 2
Presumably, you're going to need some logic that iterates or otherwise pairs these objects together. You don't mention the source, but the logic will look something like this:
if ($a->ServiceId == $b->ServiceId) {
$c = (object)array_merge((array)$a, (array)$b)
}
If you have a list of objects, and you want to merge them all together, you can use the combinatoric array walk behavior of usort:
$merged = [];
usort($objects, function ($a, $b) use ($merged) {
$comp = strcmp($a->ServiceId, $b->ServiceId);
if (0 === $comp) {
$merged[] = (object)array_merge((array)$a, (array)$b)
}
return $comp;
});
var_dump($merged);
This iterates through your list of objects, comparing each. If the ServiceId match, then it merges the two objects and adds it to a list of merged. This will happen for as many objects that share ServiceId as you have in your list.
The idea here is too loop through 1 array and find the (assuming 1) element in the other array that match the current one:
foreach ($array1 as $key => $item) {
$other = array_filter($array2, function ($item2) use ($item) {
return $item2->ServiceId == $item->ServiceId;
});
if (!empty($other)) {
$item->StartTime = current($other)->SomethingElse;
}
}
Because you're dealing with objects merging all properties is a bit tricky.
Here's an example:
http://sandbox.onlinephpfunctions.com/code/15a9710dea77672c4ac4165044ad2c7aae4ae234
Related
I have a JSON array of data that I am trying to extract particular value/keys(?) from, and would like to add them into a new array.
The array looks like this:
{ "total':2000,
"achievements":[
{
"id":6,
"achievement":{},
"criteria":{
"id":2050,
"is_completed":false
},
"completed_timestamp":1224053510000
},
{
"id":8,
"achievement":{},
"criteria":{
"id":1289,
"is_completed":true
},
"completed_timestamp":0000000
}
]
}
I want to search for true in the is_completed, and then add the id from that array into a new array.
Basically, find the id's of all the key/array (sorry unsure of terminology) where is_completed is true.
I've tried something simple like finding trying to find the key of an ID, but struggling to get that to work. And also seen some of the multi-level for loop examples but can't get them to work for my data.
Example:
$key = array_search('1289', array_column($array, 'id'));
As pointed out in the comments, you could combine array_filter (to filter completed events) and array_column (to extract their IDs).
$completedAchievements = array_filter(
$array->achievements,
static function (\stdClass $achievement): bool {
return $achievement->criteria->is_completed === true;
}
);
$completedAchievementsIds = array_column($completedAchievements, 'id');
print_r($completedAchievementsIds); // Array([0] => 8)
Note: the code above supposes your JSON was decoded as an object. If it was decoded as an array, just replace -> syntax with the corresponding array index access.
Demo
I am using laravel, I am getting this error when trying to count the valuse of this array.
Error:array_count_values(): Can only count STRING and INTEGER values!
the function:
public function test()
{
$test = \DB::table('surveys')->where('company_id', '=', 1)->select('rd1')->get()->toArray();;
$c = array_count_values($test);
$val = array_search(max($c), $c);
return view ('companies.test', compact('val'));
}
here is a var_dump of $test:
array:4 [▼
0 => {#204 ▼
+"rd1": "option1"
}
1 => {#206 ▼
+"rd1": "option1"
}
2 => {#207 ▼
+"rd1": "option1"
}
3 => {#208 ▼
+"rd1": "option1"
}
]
Your array is 2d which array_count_values doesn't like. You can either perform a loop and count the values yourself, or use something like array_column to get an array of the value from just the one column. Example:
array_count_values(array_column($test, $columnName))
array_column will get an array of values from just the one column you specify, which in your case is a string and that returned array can be passed to array_count_values to get the result you want.
Of course though, as someone else commented, this grouping and counting can be done by the database much more efficiently (you would be passing back less data and the database can be optimized to return queries like this with indexes). Here is another question that covers this.
You could then turn that back into a similar array with just array_column like:
\DB::table('surveys')
->where('company_id', '=', 1)
->select('rd1', \DB::raw('count(*) as total')
->groupBy('rd1')
->get()
->toArray();;
array_column($test, 'total', 'rd1');
The built-in function array_count_values() can't calculate the
frequency if the elements are not strings or numbers. You can, however,
easily perform a loop to do so:
foreach ($array as $e)
$frequency[$e] =+ 1;
This would basically do the same as the built-in. Note that the elements
are used as keys for the final frequency array (and you can imagina that
if $e is not a valid key (string or number) it will fail). In your
case, you need to use as key a property of your element. For example, if
it is another array:
foreach ($test as $e)
$frequency[$e['survey_name']] += 1;
Or properties/methods of an object:
foreach ($test as $e)
$frequency[$e->myColumn()] += 1;
This question already has answers here:
Preserve key order (stable sort) when sorting with PHP's uasort
(6 answers)
Closed 5 months ago.
I'm using usort to sort an array of objects, but really I want this to act as a kind of "group by" function without disturbing the original relative order of the rows.
Say I have this:
MASTER_CODE, CONFIG_ITEM
foo1, opt_ray
foo2, opt_ray
foo1, opt_fah
foo2, opt_doe
From that data, an array of objects is constructed with an anonymous key. That is, each row is parsed as an object. The objects are collected into an array.
What I want to do is sort the array by the MASTER_CODE value, but without disturbing the order.
That is, the final order should be:
MASTER_CODE, CONFIG_ITEM
foo1, opt_ray
foo1, opt_fah
foo2, opt_ray
foo2, opt_doe
We don't add a sort order, because the data comes from an external source.
I can use usort to order by the master code, but it messes up the original relative order.
Any suggestions?
This is one option - it's not the most elegant solution. It will take the unique values from the first column of your array (the one you want to filter by), sort that, then loop it and add entries from your original array with the same first value.
// Get an unique array of values to use for sorting
$sorting = array_unique(array_column($a, 0));
sort($sorting);
$sorted = [];
foreach ($sorting as $sortValue) {
$sorted = array_merge(
$sorted,
array_filter(
$a,
function($row) use ($sortValue) {
// Find values that have the same first value as each sort value
return ($sortValue === $row[0]);
}
)
);
}
Example
Note: This will work on PHP 5.5. Since you also tagged PHP 5.3, you may need to replace the array_column function. Try something like this:
$sorting = array_unique(array_map(function($row) { return $row[0]; }, $a));
I have an array that consists of an undetermined number of arrays, recursively (n levels deep). Each array might contain a name key. I want to create a unique list of those values.
Example
Suppose the array is:
$bigArray = array(
'name'=>'one',
'something'=>array(
'name'=>'two',
'subthing'=>array('name'=>'three')
),
'anotherthing'=>array('name'=>'one')
);
The expected result would be:
$uniques = array('one', 'two', 'three') // All the 'name' keys values and without duplicates.
Here's a fiddle of my attempt.
My approach was using array_walk_recursive passing a $uniques array as reference, and allowing the function to update that value:
$uniques = array();
function singleOut($item, $key, &$uniques) {
if ($key == 'name' && !in_array($itm,$uniques,true) )
$uniques[] = $item;
}
array_walk_recursive($bigArray, 'singleOut', $uniques);
However, it's not working for me.
You could use also array_unique on this one too. Example:
$uniques = array();
array_walk_recursive($bigArray, function($val, $key) use (&$uniques){
if($key == 'name') {
$uniques[] = $val;
}
});
$uniques = array_unique($uniques); // unique values
Your fiddle was nearly spot on - the problem was, that the user parameter is given by-reference only within same levels of recursion. You need to use indirection with a reference:
$bigArray = array(
'name'=>'one',
'something'=>array(
'name'=>'two',
'subthing'=>array('name'=>'three')
),
'anotherthing'=>array('name'=>'one')
);
function singleOut($item, $key, $indirect) {
$uniques=&$indirect[0];
if ($key == 'name' && !in_array($item,$uniques,true) ) $uniques[] = $item;
}
$uniques = array();
$indirect = array(&$uniques);
array_walk_recursive($bigArray, 'singleOut', $indirect);
print_r($uniques);
Edit:
Fiddle is here
To avoid doing an in_array() check inside of array_walk_recursive(), you can store name values as keys in the output array. This will effectively eliminate duplicates by overwriting previous identical keys. When array_walk_recursive() is finished, you can use array_keys() to move the data from keys to values.
Code: (Demo)
$bigArray=[
'name'=>'one',
'something'=>[
'name'=>'two',
'subthing'=>['name'=>'three']
],
'anotherthing'=>['name'=>'one']
];
array_walk_recursive($bigArray,function($v,$k)use(&$uniques){
if($k==='name')
$uniques[$v]='';
});
var_export(array_keys($uniques));
Output:
array (
0 => 'one',
1 => 'two',
2 => 'three',
)
Because array_unique() can be slow in some cases, using array_keys() should generally perform faster. That said, if micro-optimization is a concern then you should do benchmark testing using your actual data and your specific environment and select the best method for your project.
As I mentioned in a comment under Ghost's answer, it is a good habit to make === strict comparisons on keys in your multi-dimensional array because if you are looking for a string, but encounter a 0 key, then PHP's type juggling "feature" will provide unexpected results.
Here is a page where I discuss and demonstrate this behavior: Type juggling while making loose comparison yields unwanted result
I'm new to the stackoverflow so feel free to delete this question if it's stupid:
The main purpose of doing this is to display a list of details on a page randomly everytime it refreshes, but the details are in different arrays, so i have 2 arrays which i have to randomize in the same way,
Example:
$Name[0]=John;
$Name[1]=Lucy;
$Name[2]=Mike;
$Age[0]=18;
$Age[1]=20;
$Age[2]=25;
after being randomize becomes :
$Name[2]=Mike;
$Name[0]=John;
$Name[1]=Lucy;
$Age[2]=25;
$Age[0]=18;
$Age[1]=20;
I tried using "->", For example:
$Array[0]->name = 'John';
$Array[0]->age = '18';
$Array[1]->name = 'Lucy';
$Array[1]->age = '20';
shuffle($Array);
but my teacher wasn't thrilled because the code was messy since there's alot of transfer (Details into the new array before the randomizing and new array back into details after randomizing). He wants me to do it only with the 2 arrays.
What you are doing, is sort of the right way of doing things. Look at it this way. What exactly are you trying to achieve? You want a set of data sorted randomly (ok, sorting is not the right word here, but you get it). Whenever you talk about any kind of reordering of data, we talk about changing the order that the elements appear in. Your element here is neither 'name', nor 'age'. Your element here is 'person'. So, your data list should be a list of 'person's, which you would reorder and not in two loosely-coupled arrays.
Anyways, if you absolutely MUST do it this way. This is what I suggest.
Let there be two arrays $A and $B. Let them both have N elements. What you do is, create an array $nums, which has elements from 0 to N-1 i.e.
$nums = array(0, 1, 2 ... N-1)
Now, shuffle the $sums array. So, let's say we have something like:
$num = (3, 4, 1, 2)
Ok, so we have created a mapping here,
$num[0] = 3
means that the 0th element is 3, which you can interpret as in the new array, the 0th element should be the 3rd element of the old array. To do this, run a simple loop:
for($i=0; $i<N; $i++) {
$A2[$i] = $A[$num[$i]];
$B2[$i] = $B[$num[$i]];
}
It is a slightly challenging task if you insist on this being an online solution (a solution that modifies the original data structure and not build a new one), but I'll leave that ask to you (think of swapping data elements according to the map we just created). Also, you could extend it easily to more than 2 arrays, or an array of arrays in case you're not sure how many arrays one has to deal with. Feel free to ask any question you might have...
You should use an associative array.
$people = array('John' => 18, 'Lucy' => 20, 'Mike' => 25);
return shuffle_assoc($people);
function shuffle_assoc( $array )
{
$keys = array_keys( $array );
shuffle( $keys );
return array_merge( array_flip( $keys ) , $array );
}