PHP Sorting String Array With Substring As Identifier - php

I want to sort an array of strings where the substring I want to use as sorting identifier is in the middle of the string in question. I've already tried to do that using a very crude technique and I was wondering if there was a better way to do this.
Here is what I've come up with so far:
<?php
$tracks = array(
"Arcane Library_Track2.mp3",
"Deeper Ocean_Track4.mp3"
"Neon Illumination_Track3.mp3",
"Sound Of Cars_Track1.mp3",
);
$tracksreversed = array();
$sortedtracks = array();
foreach( $tracks as $t) {
array_push( $tracksreversed, strrev($t) );
}
sort($tracksreversed);
foreach( $tracksreversed as $t ) {
array_push( $sortedtracks, strrev($t) );
}
$tracks = $sortedtracks;
?>
This method works but there are two issues. For one it uses two loops and the second problem is that this method will fail as soon as we include more file types like ogg or wav.
Is there a way to easily sort this string according to the _Track1, _Track2, _Track3 indentifiers? Maybe some regex sorting could be done here or perhaps there is some other method I could do which makes this more compact?

Maybe the usort function will be useful. Like:
$getIdx = function(string $name) {
preg_match('/_Track(?<idx>\d+)/', $name, $matches);
return $matches['idx'];
};
usort($tracks, function ($a, $b) use ($getIdx) {
return $getIdx($a) <=> $getIdx($b);
});
Seems to work https://3v4l.org/j65rd

Related

Order alphabetically SimpleXML array by attribute

I need a bit of help ordering a SimpleXML array in an alphabetical order using one of it's attribute ($url). I have the following code that generate a page:
<?php
$xml = simplexml_load_file($xml_url);
$ids = array();
foreach($xml->list as $id) {
$ids[] = $id;
}
//It works but randomize instead of sorting alphabetically
usort($ids, function ($a, $b) {
return strnatcmp($a['url'], $b['url']);
});
foreach ($ids as $id) {
PopulatePage($id);
}
function PopulatePage($id) {
$url = $id->url;
$img = $id->img;
//OTHER CODES TO GENERATE THE PAGE
}?>
QUESTION RESOLVED!
There is no conversion needed, you already have an array which you can sort, you just need to understand how usort callbacks work. Each of the items in your $ids array is a SimpleXMLElement object, and each time your callback is run, it will be given two of those objects to compare. Those objects will be exactly the same as in the existing PopulatePage function, so accessing the URL needs to happen exactly as it does there ($url = $id->url;) not using a different notation ($id['url']).
To make it more explicit, let's write a named function with lots of clear variable names:
function CompareTwoIds(SimpleXMLElement $left_id, SimpleXMLElement $right_id): int {
$left_url = $left_id->url;
$right_url = $right_id->url;
return strncatcmp($left_url, $right_url);
}
Now you can test calling that function manually, and use it as the callback when you're happy:
usort($ids, 'CompareTwoIds');
Once you're comfortable with the principles, you may decide to skip the extra verbosity and just write this, which is completely equivalent:
usort($ids, fn($a,$b) => strncatcmp($a->url, $b->url));

Fatal error: Using $this when not in object context using array_walk

I have made a class that should replace data at the hand of an array.
I insert
$data = array(
'bike',
'car',
'pc'
);
And it should translate the array to :
$data = array(
'[#bike]',
'[#car]',
'[#pc]'
);
I have written the following piece of code.
array_walk($PlaceHolders , function(&$value, $key) { $value = $this->Seperators['plcstart'].$value.$this->Seperators['plcend']; });
Which I am using in a class context.
The two sperators have the following value:
$this->Seperators['plcstart'] = '[#';
$this->Seperators['plcend'] = ']';
The problem is. At localhost it works superb!!
Now when I upload the system to a unix environment it stops working!
I have no clue why this is the result.
Would anyone here know what I am doing wrong?
I would recommend you to use array_map() when you want to modify array elements using a callback.
You could end with something like this
class TransformerClass {
private $Seperators = array(
'plcstart' => '[#',
'plcend' => ']'
);
public function transform(array $data) {
return array_map(
function($text) {
return $this->Seperators['plcstart'].$text.$this->Seperators['plcend'];
},
$data
);
}
}
Example:
$transformer = new TransformerClass();
$items = array(
'bike',
'car',
'pc'
);
$result = $transformer->transform($items);
And $result will contain your desired resulting data.
The most bizare thing is... This morning I opened the same page again.
And it worked like the offline environment :-S....
This is quite frustrating though!!
However I would like to make a few small notes:
///######## LOADING ALL PLACEHOLDERS
$PlaceHolders = array_keys($this->TplObjects);
///######## SEPERATOR START
$SeperatorStart = $this->Seperators['plcstart'];
$SeperatorEnd = $this->Seperators['plcend'];
///######## ADDING THE START AND END TAGS TO EACH ARRAY VALUE
array_walk($PlaceHolders , function(&$value, $key) { $value =
$SeperatorStart.$value.$SeperatorEnd; });
This is what I thought to be a solution but it is NOT!!
Why?? Because the way it was worked:
///######## LOADING ALL PLACEHOLDERS
$PlaceHolders = array_keys($this->TplObjects);
///######## ADDING THE START AND END TAGS TO EACH ARRAY VALUE
array_walk($PlaceHolders , function(&$value, $key) { $value = $this->Seperators['plcstart'].$value.$this->Seperators['plcend']; });
Because it got it's data directly out of the class.
By using function I placed the variables into it's own individual scope, hence the variable $SeperatorStart and $SeperatorEnd do not exist in this scope.
I could ofcourse import these two into that function. But I do not know how to do this with array_walk. I have not used this function this often hence I know only a few basic manners to use this function.
The second option as opted for above by #Falc works great! And is the method I was looking for. Hence thanks a million!!

sorting 2d array with individual comparator using phps array_multisort

I got a 2d-array containing a "column" on which this whole array is to be sorted. As I learned here, it is quite straightforward using array_multisort. My problem is, that this to be sorted over column contains values that needs to be compared in an unusual way. So I thought of something like this:
function main(){
$toBeSorted = array(array(...), ..., array(...));
$sortColumnIndex = n;
$sort_column = array();
//Code copied from provided link
foreach ($toBeSorted as $row)
$sort_column []= $row[$sortColumnIndex];
array_multisort($this->comparator(),$sort_column, $toBeSorted);
}
function comparator(a,b){
return 1;
}
As you can see, I want to pass my comparator to that sort-function. I probably think to much in a non-php way.
There is the usort function, which sorts by using a callback.
Otherwise you could have a look at the array_walk and array_walk_recursive functions, which iterate over an array and apply a function to every member.
I solved it by transforming my sorting space in one, that array_multisort can handle:
...
$sortColumn = array();
foreach($toBeSorted as $value){
$sortColumn[] = $this->transform($value);
}
array_multisort($sortColumn, $toBeSorted);
...
my transformation function simply does everything I imagined the callback would do.
function transform($value){
//i.e. return real part of complex number
//or parse strings or any kind of strange datatypes
}

Split a string to form multidimensional array keys?

I'm creating JSON encoded data from PHP arrays that can be two or three levels deep, that look something like this:
[grandParent] => Array (
[parent] => Array (
[child] => myValue
)
)
The method I have, which is simply to create the nested array manually in the code requires me to use my 'setOption' function (which handles the encoding later) by typing out some horrible nested arrays, however:
$option = setOption("grandParent",array("parent"=>array("child"=>"myValue")));
I wanted to be able to get the same result by using similar notation to javascript in this instance, because I'm going to be setting many options in many pages and the above just isn't very readable, especially when the nested arrays contain multiple keys - whereas being able to do this would make much more sense:
$option = setOption("grandParent.parent.child","myValue");
Can anyone suggest a way to be able to create the multidimensional array by splitting the string on the '.' so that I can json_encode() it into a nested object?
(the setOption function purpose is to collect all of the options together into one large, nested PHP array before encoding them all in one go later, so that's where the solution would go)
EDIT: I realise I could do this in the code:
$options['grandparent']['parent']['child'] = "myValue1";
$options['grandparent']['parent']['child2'] = "myValue2";
$options['grandparent']['parent']['child3'] = "myValue3";
Which may be simpler; but a suggestion would still rock (as i'm using it as part of a wider object, so its $obj->setOption(key,value);
This ought to populate the sub-arrays for you if they haven't already been created and set keys accordingly (codepad here):
function set_opt(&$array_ptr, $key, $value) {
$keys = explode('.', $key);
// extract the last key
$last_key = array_pop($keys);
// walk/build the array to the specified key
while ($arr_key = array_shift($keys)) {
if (!array_key_exists($arr_key, $array_ptr)) {
$array_ptr[$arr_key] = array();
}
$array_ptr = &$array_ptr[$arr_key];
}
// set the final key
$array_ptr[$last_key] = $value;
}
Call it like so:
$opt_array = array();
$key = 'grandParent.parent.child';
set_opt($opt_array, $key, 'foobar');
print_r($opt_array);
In keeping with your edits, you'll probably want to adapt this to use an array within your class...but hopefully this provides a place to start!
What about $option = setOption("grandParent", { parent:{ child:"myValue" } });?
Doing $options['grandparent']['parent']['child'] will produce an error if $options['grandparent']['parent'] was not set before.
The OO version of the accepted answer (http://codepad.org/t7KdNMwV)
$object = new myClass();
$object->setOption("mySetting.mySettingsChild.mySettingsGrandChild","foobar");
echo "<pre>".print_r($object->options,true)."</pre>";
class myClass {
function __construct() {
$this->setOption("grandparent.parent.child","someDefault");
}
function _setOption(&$array_ptr, $key, $value) {
$keys = explode('.', $key);
$last_key = array_pop($keys);
while ($arr_key = array_shift($keys)) {
if (!array_key_exists($arr_key, $array_ptr)) {
$array_ptr[$arr_key] = array();
}
$array_ptr = &$array_ptr[$arr_key];
}
$array_ptr[$last_key] = $value;
}
function setOption($key,$value) {
if (!isset($this->options)) {
$this->options = array();
}
$this->_setOption($this->options, $key, $value);
return true;
}
}
#rjz solution helped me out, tho i needed to create a array from set of keys stored in array but when it came to numerical indexes, it didnt work. For those who need to create a nested array from set of array indexes stores in array as here:
$keys = array(
'variable_data',
'0',
'var_type'
);
You'll find the solution here: Php array from set of keys

Sorting an array of SimpleXML objects

I've read what I've found on Stackoverflow and am still unclear on this.
I have an array of SimpleXML objects something like this:
array(2) {
[0]=>
object(SimpleXMLElement)#2 (2) {
["name"]=>
string(15) "Andrew"
["age"]=>
string(2) "21"
}
[1]=>
object(SimpleXMLElement)#3 (2) {
["name"]=>
string(12) "Beth"
["age"]=>
string(2) "56"
}
}
And I want to be able to sort by whatever column, ascending or descending. Something like:
sort($data, 'name', 'asc');
Where I can pass in the above array of objects and sort by the value of whichever key I like.
For reference, a similar .NET solution would be along these lines:
XmlSortOrder order = XmlSortOrder.Ascending;
if ( sortDirection == "asc" ) {
order = XmlSortOrder.Ascending;
}
expression.AddSort( columnSortingOn + "/text()", order,
XmlCaseOrder.UpperFirst, "en-us", XmlDataType.Text );
I've seen people say
"Use usort"
Followed by a basic example from the PHP manual but this doesn't really explain it. At least not to me. I've also seen people suggest using an external library like SimpleDOM but I want to avoid using something external for this (seemingly, though I cannot yet solve it) little thing.
Any help is appreciated, Thanks!
I guess the people suggesting to use SimpleDOM would be me. :)
I've written SimpleDOM::sort() exactly for that situation, because in order to sort SimpleXMLElements by an arbitration expression (or arbitrary expressions) you need to use array_multisort() which is boring and won't teach you anything useful.
Here's the short version of how it works: first you create a proxy array of key=>value pairs corresponding to each SimpleXMLElement and the value with which they'll be sorted. In your example, if you want to sort them by <age/>, the array would be array(21, 56). Then you call array_multisort() with the "proxy array" as first argument, followed by any number of sorting modifiers such as SORT_DESC or SORT_NUMERIC then finally the array you want to sort, which will be passed by reference.
You will end up with something like that:
$nodes = array(
new SimpleXMLElement('<person><name>Andrew</name><age>21</age></person>'),
new SimpleXMLElement('<person><name>Beth</name><age>56</age></person>')
);
function xsort(&$nodes, $child_name, $order = SORT_ASC)
{
$sort_proxy = array();
foreach ($nodes as $k => $node)
{
$sort_proxy[$k] = (string) $node->$child_name;
}
array_multisort($sort_proxy, $order, $nodes);
}
xsort($nodes, 'name', SORT_ASC);
print_r($nodes);
xsort($nodes, 'age', SORT_DESC);
print_r($nodes);
But really, instead of burdening yourself with more code you'll have to maintain and possibly ending up rewriting array_multisort() in userspace, you should leverage existing solutions. There's nothing interesting in such a sorting algorithm/routine, your time would be better spent on something that doesn't already exist.
The usort function allows to you tell PHP
Hey, you! Sort this array I'm giving you with this function I wrote.
It has nothing specifically to do with SimpleXML. It's a generic function for sorting PHP built-in array data collection.
You need to write a function, instance method, or static method to sort the array. The second argument to usort accepts a PHP Callback, which is a pseudo-type that lets you specify which function, instance method, or static method.
The function you write will accept two arguments. These will be two different values from your array
function cmp($a, $b)
{
if ($a == $b) {
return 0;
}
if($a < $b) {
return -1;
}
if($a > $b) {
return 1;
}
}
You need to write this function to return one of three values.
If $a == $b, return 0
If $a > $b, return -1
if $a > $v, return 1
When you call usort, PHP will run through your array, calling your sorting function/method (in this case cmp over and over again until the array is sorted. In your example, $a and $b will be SimpleXML objects.
I was ready to recommend usort() until I realized you already beat me to it. Since code examples haven't done much good in the past, I'll try to just explain it in plain English and hopefully that'll get you pointed in the right direction.
By using usort(), you create your own user-generated "algorithm". The usort() function calls your own comparison function to determine how each of your objects relates to one another. When writing your comparison function, you will get passed in two objects within your array. With those two objects, you return a result that essentially tells usort() whether the first object is LESS THAN, EQUAL TO, or GREATER THAN the second object. You do this by returning -1, 0, or 1 (respectively). That's it. You only have to concern yourself with how two objects compare to each other and the actual sorting mechanics are handled by usort() for you.
Ok, now for a semi-practical example:
function myCompare($obj1, $obj2) {
if($obj1->someInt == $obj2->someInt) {
return 0; // equal to
} else if($obj1->someInt < $obj2->someInt) {
return -1; // less than
} else {
return 1; // greater than
}
}
$myArray = {a collection of your objects};
usort($myArray, 'myCompare');
This is pretty much the example in the PHP Manual, but hopefully it makes sense in context now. Let me know if I'm not being clear on something.
Here's another example of using usort(). This one allows you to specify the object variable and the sort direction:
function sort_obj_arr(& $arr, $sort_field, $sort_direction)
{
$sort_func = function($obj_1, $obj_2) use ($sort_field, $sort_direction)
{
if ($sort_direction == SORT_ASC) {
return strnatcasecmp($obj_1->$sort_field, $obj_2->$sort_field);
} else {
return strnatcasecmp($obj_2->$sort_field, $obj_1->$sort_field);
}
};
usort($arr, $sort_func);
}
Test code;
class TestClass
{
public $name;
public $age;
public function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}
}
$test[] = new TestClass('Tom', 28);
$test[] = new TestClass('Mary', 48);
$test[] = new TestClass('Beth', 38);
$test[] = new TestClass('Cindy', 18);
$test[] = new TestClass('Sid', 58);
$test[] = new TestClass('Mandy', 8);
$field = 'age';
$direction = SORT_DESC;
sort_obj_arr($test, $field, $direction);
echo '<pre>';
print_r($test);
echo '</pre>';
it's a old thread but here is my solution that a use to extract information from a rss feed in order to sort by title
$xml = simplexml_load_file('rss.xml');
$msg = array();
$msg_count = $xml->channel->item->count();
for ($x=0;$x<$msg_count;$x++){
$msg[$x]['titl'] = (string)$xml->channel->item[$x]->title;
$msg[$x]['link'] = (string)$xml->channel->item[$x]->link;
$msg[$x]['date'] = (string)$xml->channel->item[$x]->pubDate;
$msg[$x]['time'] = strtotime(substr((string)$xml->channel->item[$x]->pubDate,4));
$msg[$x]['desc'] = (string)$xml->channel->item[$x]->description;
$msg[$x]['auth'] = (string)$xml->channel->item[$x]->author;
}
foreach ($msg as $key => $row) {
$titl[$key] = $row['titl'];
$link[$key] = $row['link'];
$pdat[$key] = $row['date'];
$time[$key] = $row['time'];
$cate[$key] = $row['cate'];
$desc[$key] = $row['desc'];
$auth[$key] = $row['auth'];
}
array_multisort(
//change $titl to any variable created by "foreach"
$titl, SORT_ASC,SORT_NATURAL | SORT_FLAG_CASE,
$msg);

Categories