I've been trying to write a recursive function that would reorder an array of objects based on the order provided by another array (simple, numeric array).
I want to use this sorting function to sort an array of objects by a 'template' array that would hold only one property of each object present in the array to sort, e.g.
$template = ['A', 'B', 'C']
Array to sort:
$myArray = [
new Element('B'),
new Element('C'),
new Element('A'),
]
class Element
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
}
I was not successful. Perhaps you might have an idea about how to apprach this task?
I don't see how recursion would help you with that task. This is how you can use the built in sort functions:
usort($myArray, function(Element $a, Element $b) use ($template) {
return array_search($a->name, $template) - array_search($b->name, $template);
});
usort sorts by given comparison callback
I added the Element type hint to the callback because the sort function will only work with arrays of Element objects
array_search returns the key for the given name property value within the $template array. If the value does not exist in the array, it will be placed at the beginning, because the result false is coerced to 0.
I also managed to do the sorting using recursion - here it is:
function orderRecursively($template, $myArray, &$ordered)
{
foreach($myArray as $k => $v) {
if ($myArray[$k]->name == $template[0]) {
$ordered[] = $myArray[$k];
array_splice($template, 0, 1);
}
}
if (!empty($template)) orderRecursively($template, $myArray, $ordered);
}
$ordered = [];
order($template, $myArray, $ordered);
$ordered would then hold the sorted array of objects.
Still, I find #fschmengler's answer more elegant.
Related
I have a JSON object I've turned into an array, this needs to be stored into my database, the trouble is my table has field called id, however in the array it's called group_id.
What's the most efficient way of traversing my multi-dimensional array converting all the group_id keys to id so I can pass it to my SQL storage function? I want to function to work with an array or an array or arrays.
I had planned to make a function:
$db->remap($array, [ 'group_id' => 'id' ]; then I can pass a list of all the fields I want remapping.
array_walk_recursive doesn't help because it won't let you modify the keys, I looked at a recursive function call, but struggled to keep track of the array.
I managed to get it working, including here to help others, if there is a better way please let me know, thanks.
/**
* Recursively remap array keys
* #param array $array Array to search
* #param array $mapping Key value pairs of the keys to map [ 'fromKey' => 'toKey' ]
* #return array Returned remapped array
*/
public function remapArray(array $array, array $mapping) {
$newArray = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
// This is also an array, resursively call
$newArray[$key] = $this->remapArray($value, $mapping);
} else {
if (! empty($mapping[$key])) {
// Match, replace key
$newArray[$mapping[$key]] = $value;
} elseif (! in_array($key, $mapping)) {
// Only store if this key doesn't match the target
$newArray[$key] = $value;
}
}
}
return $newArray;
}
I have this function in my old php5 code that will accept a variable number of parameters and perform sorting based on the parameters:
function array_alternate_multisort(){
$arguments = func_get_args();
$arrays = $arguments[0];
for ($c = (count($arguments)-1); $c > 0; $c--)
{
if (in_array($arguments[$c], array(SORT_ASC , SORT_DESC)))
{
continue;
}
$compare = create_function('$a,$b','return strcasecmp($a["'.$arguments[$c].'"], $b["'.$arguments[$c].'"]);');
usort($arrays, $compare);
if ($arguments[$c+1] == SORT_DESC)
{
$arrays = array_reverse($arrays);
}
}
return $arrays ;
}
I call it like this:
$alliances = array_alternate_multisort($alliances, "output", SORT_DESC, "score", SORT_DESC);
How can I replace this with a function without calling create_function()?
You can use an anonymous function instead:
$compare = function ($a, $b) use ($arguments, $c) {
return strcasecmp($a[$arguments[$c]], $b[$arguments[$c]]);
};
Untested but should be close enough
The use keyword allows you to inherit variables from the parent scope inside your function.
First of all, I'll argue that if you have the ability to hardcode this into your script:
$alliances = array_alternate_multisort($alliances, "output", SORT_DESC, "score", SORT_DESC);
then you can just as easily completely scrap your custom function and just write this:
Code: (Demo)
array_multisort(...[
array_column($alliances, 'output'),
SORT_DESC,
array_column($alliances, 'score'),
SORT_DESC,
&$alliances
]);
This will do EVERYTHING that your custom function will do and more WITHOUT introducing ANY custom function.
This is a very concise, totally native, and instantly readable technique. Using this means that:
you will not confine your script to only SORT_ASC and SORT_DESC; there are other useful sorting flags to enjoy for specific scenarios.
you can opt to omit the sorting direction parameters if you wish to use SORT_ASC (the default sorting flag).
you modify the input array by reference like other native sorting functions.
Now anything beyond the above is going to introduce unnecessary convolution. To keep this hypothetical (which, again, I don't endorse) demonstration simple I'll insist that the sorting direction flags are required like in your original snippet.
Code: (Demo)
function array_alternate_multisort($array, ...$args) {
foreach ($args as $i => $arg) {
$sortParams[] = $i & 1 ? $arg : array_column($array, $arg);
}
$sortParams[] = &$array;
array_multisort(...$sortParams);
return $array;
}
& 1 is a bitwise odd check. If the index is odd, push the constant into $sortParams otherwise push the column data into $sortParams.
This answer belongs to a family of similar answers that use the splat operator to unpack parameters into a array_multisort() call.
array_multisort and dynamic variable options
Sort array of associative arrays on multiple columns using specified sorting rules
I am using Parse.com APIs, though I wouldnt say this is a Parse question.
I have an array of ParseObjects called $groups and an array of ParseObjects called $inputs.
I already have both of these, so I do not want make any new queries.
All of the input objects are children of one of the group objects. I'd like to get an array of all of the $inputs that belong to each $group.
One way I could do this would be:
$groups= // array of ParseObjects
$inputs= // array of ParseObjects
foreach ($groups as $group)
{
$inputsInGroup=array();
foreach ($inputs as $input)
{
if($input->get('parent')==$group)
{
array_push($inputsInGroup,$input);
}
}
//here I can use $inputsInGroup which will contain all elements that have this group as their parent
}
That would work, but seams really inefficient as it has to search the whole list of inputs for each group, including any that it already determined belonged to a previous group.
Is there a way to supply a function to array_search () or sort() to return objects in the array that pass this check $input->get('parent')==$group?
I know you can do this with jQuery's sort() seems like PHP likely has a similar method.
You had an unneeded nested loop there - that's why
$groups; // array of ParseObjects
$inputs; // array of ParseObjects
$ibg; // inputs by group
foreach($inputs as $input) {
var $group = $input->get('parent')->getObjectId();
if(!isset($ibg[$group])){
$ibg[$group] = array();
}
$ibg[$group][] = $input;
}
What you're asking for is to apply a filter, with a custom callback, to an array, which can be done with array_filter. However, the change is only paradigmatic in nature — in that the asymptotic complexity won't change much.
array_filter($groups, function ($group) use ($inputs) {
foreach($inputs as $input) {
if ($input->get('parent') == $group) {
return true;
}
}
});
Both are still O(n * k) in time complexity, but one uses a more functional, as opposed to procedural, approach.
I have many arrays, each holding instances of Product. Need to get unique products only. From PHP documentation of array_diff:
array array_diff ( array $array1 , array $array2 [, array $... ] )
Two elements are considered equal if and only if (string) $elem1 ===
(string) $elem2. In words: when the string representation is the same.
Does this mean I'm forced to implement toString() in my instances? Is there any function to compute the difference providing a custom callback?
I didn't tested this code, but I guess it will not work because there is no toString() function in Product:
$categories = array();
// ...
// Unique products from all categories, compared against ===
$uniqueProducts = array();
// Compute unique products
foreach($categories as $category) {
$uniqueProducts += array_diff($category->getProducts(), $uniqueProducts)
}
return $uniqueProducts;
Does this mean I'm forced to implement toString() in my instances?
According to the typecast section in the PHP docs manual - you do not need a toString() function. Basically typecasting (string) is the same as if you simply did var_dump($uniqueProducts)
All that the array_diff is doing is typecasting your array.
One option is to make your own "array_diff" function
function my_array_diff($arraya, $arrayb)
{
foreach ($arraya as $keya => $valuea)
{
// Put your own 'test' here - but for example this uses in_array()
if (in_array($valuea, $arrayb))
{
unset($arraya[$keya]);
}
}
return $arraya;
}
If the product lists are short you could implement your own checking, based on values of your choice?
$uniqueProducts = array();
foreach($productsOne as $productOne) {
foreach($uniqueProducts as $alreadyListed) {
if($productOne->getName() != $alreadyListes->getName()) { // You can customise this line to make more specific
break;
}
$uniqueProducts = $productOne; // If product hasn't already been added to the array then it's unique.
}
}
// Repeat foreaches for second product array, or combine them before teh foreach using $productOne = $productOne+$productTwo.
As stated in the title, How do you perform an array_unshift() on a arrayObject, array_push() is obtained by doing an arrayObject->append() but what about unshift ?
Edit: What I've forgot to mention is that i also need in this particular case to preserve existing keys.
The API of ArrayObject does not have any function to accomplish this directly. You have several other options:
Manually move each element by 1, and set your new value at index 0 (only if you have a numerically index ArrayObject).
$tmp = NULL;
for ($i = 0; $arrayObject->offsetExists($i + 1); $i++) {
$tmp = $arrayObject->offsetGet($i + 1);
$arrayObject->offsetSet($i + 1, $arrayObject->offsetGet($i));
}
$arrayObject->offsetSet($i, $tmp);
$arrayObject->offsetSet(0, $new_value);
Write a class deriving from ArrayObject and add a function prepend (implementation could be the one below).
Extract an array, call array_unshift() and create a new ArrayObject with the modified array:
$array = $arrayObject->getArrayCopy();
array_unshift($array, $new_value);
$arrayObject->exchangeArray($array);
There is no such functionality in ArrayObject, but you can subclass it to add whatever you need. Try this:
class ExtendedArrayObject extends ArrayObject {
public function prepend($value) {
$array = (array)$this;
array_unshift($array, $value);
$this->exchangeArray($array);
}
}
function prefix($value) {
return $this->exchangeArray(array_merge([$value], $this->getArrayCopy()));
}
#Dmitry, #soulmerge your answers was good regarding the initial question but was missing the requirement in my edit, but they point me in the right direction to achieve what i was expected here's the solution we came with here at work: (work for php>=5.1)
public function unshift($value){
$tmp = $this->getArrayCopy();
$tmp = array($value) + $tmp;
$this->exchangeArray($tmp);
return $this;
}
these exemple is not exactly the same as the final solution we needed as for our particular arrayObject. We use a given key in array values as key for the values (think of using database rowId as index for each value in the collection). let's call this value "key" here's what the actual array struct looks like:
array(
key1 => array(key=>key1,val1=>val1,val2=>val2...),
key2 => array(key=>key2,val1=>val1_2,val2=>val2_2...),
...
);
so our solution looks more something like this:
public function unshift($value){
$tmp = $this->getArrayCopy();
$tmp = array($value['key'],$value) + $tmp;
$this->exchangeArray($tmp);
return $this;
}
thank's for your answers, if you find a way that work in php5.0 too i'm still interested.
this works for me. with array_merge()
$old_arr[]=array(
'val1'=>'sample1',
'val2'=>'1'
);
$new_arr[] = array(
'val1'=>'sample2',
'val2'=>'2'
);
array_merge($new_arr,$old_arr);