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);
Related
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 have a class called Collection which stores objects of same type.
Collection implements array interfaces: Iterator, ArrayAccess, SeekableIterator, and Countable.
I'd like to pass a Collection object as the array argument to the array_map function. But this fails with the error
PHP Warning: array_map(): Argument #2 should be an array
Can I achieve this by implementing other/more interfaces, so that Collection objects are seen as arrays?
The array_map() function doesn't support a Traversable as its array argument, so you would have to perform a conversion step:
array_map($fn, iterator_to_array($myCollection));
Besides iterating over the collection twice, it also yield an array that will not be used afterwards.
Another way is to write your own map function:
function map(callable $fn)
{
$result = array();
foreach ($this as $item) {
$result[] = $fn($item);
}
return $result;
}
Update
Judging by your use-case it seems that you're not even interested in the result of the map operation; therefore it makes more sense to use iterator_apply().
iterator_apply($myCollection, function($obj) {
$obj->method1();
$obj->method2();
return true;
});
array_map wants, as the name suggests, arrays. It's not called iterator_map after all. ;)
Apart from iterator_to_array(), which produces a potentially large temporary array, there's no trick to make iterable objects work with array_map.
The Functional PHP library has a map implementation which works on any iterable collection.
If you're not interested in creating a new array that is a function mapped over the original array, you could just use a foreach loop (because you implement Iterator).
foreach($item in $myCollection) {
$item->method1();
$item->method2();
}
if you actually want to use map, then I think you'll have to implement your own. I would suggest making it a method on Collection, eg:
$mutatedCollection = $myCollection->map(function($item) {
/* do some stuff to $item */
return $item;
});
I would ask yourself if you really want to use map or do you really just mean foreach
I came up with the following solution:
//lets say you have this iterator
$iterator = new ArrayIterator(array(1, 2, 3));
//and want to append the callback output to the following variable
$out = [];
//use iterator to apply the callback to every element of the iterator
iterator_apply(
$iterator,
function($iterator, &$out) {
$current = $iterator->current();
$out[] = $current*2;
return true;
},
array($iterator, &$out) //arguments for the callback
);
print_r($out);
This way, you can generate an array without iterating twice as you would to with the approach like:
$iterator = new ArrayIterator(array(1,2,3));
$array = iterator_to_array($iterator); //first iteration
$output = array_map(function() {}, $array); //second iteration
Good luck!
I just stumbled upon this question and I managed to cast the collection to an array to make it work:
array_map($cb, (array) $collection);
disclaimer For the original question this might not be a suitable option but I found the question while looking to solve a problem which I solved with this solution. I would recommend using a custom iterator map where possible/viable.
another option is to do something like this:
foreach($collection as &$item) {
$item = $cb($item);
}
which will mutate the underlying collection.
EDIT:
It has been pointed out that casting to an array can have unwanted side effects. It would be better to add a method to your collection to return the array from the iterator, and traverse that, or otherwise add a map method which accepts a callback and run a loop on the underlying iterator.
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.
I have a method which takes a generator plus some additional parameters and returns a new generator:
function merge(\Generator $carry, array $additional)
{
foreach ( $carry as $item ) {
yield $item;
}
foreach ( $additional as $item ) {
yield $item;
}
}
The usual use case for this function is similar to this:
function source()
{
for ( $i = 0; $i < 3; $i++ ) {
yield $i;
}
}
foreach ( merge(source(), [4, 5]) as $item ) {
var_dump($item);
}
But the problem is that sometimes I need to pass empty source to the merge method. Ideally I would like to be able to do something like this:
merge(\Generator::getEmpty(), [4, 5]);
Which is exactly how I would do in C# (there is a IEnumerable<T>.Empty property). But I don't see any kind of empty generator in the manual.
I've managed to work around this (for now) by using this function:
function sourceEmpty()
{
if ( false ) {
yield;
}
}
And this works. The code:
foreach ( merge(sourceEmpty(), [4, 5]) as $item ) {
var_dump($item);
}
correctly outputs:
int(4)
int(5)
But this is obviously not an ideal solution. What would be the proper way of passing an empty generator to the merge method?
Bit late, but needed an empty generator myself, and realized creating one is actually quite easy...
function empty_generator(): Generator
{
yield from [];
}
Don't know if that's better than using the EmptyIterator, but this way you get exactly the same type as non-empty generators at least.
Just for completeness, perhaps the least verbose answer so far:
function generator() {
return; yield;
}
I just wondered about the same question and remembered an early description in the docs (which should be in at least semantically until today) that a generator function is any function with the yield keyword.
Now when the function returns before it yields, the generator should be empty.
And so it is.
Example on 3v4l.org: https://3v4l.org/iqaIY
I've found the solution:
Since \Generator extends \Iterator I can just change the method signature to this:
function merge(\Iterator $carry, array $additional)
{
// ...
This is input covariance thus it would break backward compatibility, but only if someone did extend the merge method. Any invocations will still work.
Now I can invoke the method with PHP's native EmptyIterator:
merge(new \EmptyIterator, [4, 5]);
And the usual generator also works:
merge(source(), [4, 5])
As explained in the official docs, you can create an in-line Generator instance, by using yield in an expression:
$empty = (yield);
That should work, but when I tried using that, I got a fatal error (yield expression can only be used in a function). Using null didn't help either:
$empty = (yield null); //error
So I guess you're stuck with the sourceEmpty function... it was the only thing I found that works... note that it will create a null value in the array you're iterating.
All the code was tested on PHP 5.5.9, BTW
The best fix I can come up with (seeing as compatibility is an issue) would be to make both arguments optional:
function merge(\Generator $carry = null, array $additional = array())
{
if ($carry)
foreach ($carry as $item)
yield $item;
foreach ($additional as $item)
yield $item;
}
foreach(merge(null, [1,2]) as $item)
var_dump($item);
This way, existing code won't brake, and instead of constructing an empty generator, passing null will work just fine, too.
I want to write a static method in a class to generically sort an array of objects.
I am thinking of somthing along the lines of:
class GenUtils {
const ASCENDING = 1;
const DESCENDING = 2;
protected static alphaSort($value1, $value2, $sort_type=self::DESCENDING){
$retval = strcasecmp($value1, $value2);
return ($sort_type == self::DESCENDING) ? $retval : (-1*$retval);
}
protected static numericSort($value1, $value2, $sort_type=self::DESCENDING){
return $value1 < $value2;
}
// Assumption: array is non-empty and homogeneous
public doSort(array& $object_array, $method_name, $sort_type=self::DESCENDING) {
if(!empty($object_array) && method_exists($object_array[0],$method_name)) {
$element = $object_array[0];
$value = $element->$method_name();
if(is_string($value)){
//do string sort (possibly using usort)
}
elseif(is_number($value)){
//do numeric sort (possibly using usort)
}
}
}
}
This is just a quick brain dump -perharps someone can fill in the missing pieces, or suggest a better way of doing this?
[Edit]
Just to clarify, the objects to be sorted (in the array), have methods which return either a string (e.g. getName()) or a numeric value (e.g. getId())
A typical usecase code snippet would therefore be somethimng like this:
GenUtils::doSort($objects,'getName'); // This will do an alphabetic DESC sort using the getName() method
GenUtils::doSort($objects, 'getId', GenUtils::ASCENDING); // This will do a numeric ASC sort using the getId() method
The use cases (numeric and string) in your example are already built in to PHP - Check out the sort function. I would use the built-in function unless I had more specific needs.
Use usort and define your own comparison function to work with your objects.