How to sort array by substring? (¨PHP) - php

I have created method to sort array with values like this: array('regdate','birthday','editdate') which should sort the elements in the way that the elements containing word date should be moved to left like this array('regdate','editdate','birthday')
public function sortColumnsBySubstring($haystack, $substr){
if ($haystack == $substr) {
return 0;
}
return strpos($substr) !== false ? -1 : 1;
}
However it is not clear to me how to make this working. Example which I have found in php manual shows function with no arguments or closures - I use php version 5.2 so I cannot use closures.
All I can think up is this usort($date_cols, $this->sortColumnsBySubstring($value, 'date') but here $value is undefined so it's not solution.
Question is how to implement the function to work correctly?

You need to pass the callback as an array:
usort($date_cols, [$this, 'sortColumnsBySubstring']);
See Callbacks / Callables in PHP docs.

First solution is to my original question:
function cmp($a, $b)
{
$adate = (strpos($a, 'date') !== false);
$bdate = (strpos($b, 'date') !== false);
if (!($adate ^ $bdate)) return strcmp($a, $b);
return $adate ? -1 : 1;
}
$a = array('birthday', 'regdate', 'editdate');
usort($a, 'cmp');
Second solution uses splitting into two arrays, sort and then merge them back. I have tried to use more word related to time to identify the values related to time.
private function getDateColumns(&$array)
{
$search_date_columns = array('date','datetime','timestamp','time','edited','changed','modified','created','datum');
$result = array( array(), array() );
foreach($array as $v1):
$found = false;
foreach($search_date_columns as $v2)
if ( strpos($v1, $v2)!==false )
{ $found = true; break; }
if ($found)
$result[0][] = $v1;
else
$result[1][] = $v1;
endforeach;
return $result;
}
Which is implemented like that:
$date_cols = array('regdate','time','editdate','createdate','personal','mojedatum','edited','test','modified','changed','pokus','timestamp','hlava');
$arrays = $this->getDateColumns($date_cols);
rsort($arrays[0]);
$date_cols = array_merge($arrays[0], $arrays[1]);
unset($arrays);
print_r($date_cols);

Related

CakePHP 3.8: How to use uasort() in a Controller

I'm trying to use the uasort() function in a CakePHP Controller.
I have a txt file containing a filename in each line.
The filename has the format SOME_TEXT_YY.MM.DD_HH-MM_SOME_TEXT
I want to sort the files after the given Date and save them again therefore I have written this php code which works correctly using plain php:
<?
// compare function
function cmp($a, $b) {
$a1 = explode('-', $a);
$a2 = explode('S', $a1[1]);
$ac = substr($a1[0], -11,11).$a2[0];
$b1 = explode('-', $b);
$b2 = explode('S', $b1[1]);
$bc = substr($b1[0], -11,11).$b2[0];
if ($ac == $bc) {
return 0;
}
return ($ac < $bc) ? -1 : 1;
}
$files = file("files.txt");
uasort($files, 'cmp');
file_put_contents('sorted.txt', $files);
foreach ($files as $line) {
echo $line."</br>";
}
?>
When I am trying to run this in an Controller Function in CakePHP it somehow does not work.
I assume it is because of the "nested" function.
Does someone know how to get this to work inside of a Controller?
Thanks in advance
UPDATE
The Error I get is
Warning (2): uasort() expects parameter 2 to be a valid callback, function 'cmp' not found or invalid function name [APP/Controller/KeysController.php, line 123]
class KeysController extends AppController {
[...]
public function update() {
$dir = WWW_ROOT . 'data';
$files_filename = $dir . DS . "files.txt";
function cmp($a, $b) {
$a1 = explode('-', $a);
$a2 = explode('TVOON_DE', $a1[1]);
$ac = substr($a1[0], -11,11).$a2[0];
$b1 = explode('-', $b);
$b2 = explode('TVOON_DE', $b1[1]);
$bc = substr($b1[0], -11,11).$b2[0];
if ($ac == $bc) {
return 0;
}
return ($ac < $bc) ? -1 : 1;
}
$otrkeys = file($files_filename);
uasort($otrkeys, 'cmp');
file_put_contents($files_filename, $otrkeys);
echo "sortiert";
}
}
While #El_Vanja's suggestion will work, it's less than ideal in the case where you need to re-use that same function in multiple controller actions.
Per the documentation on callables, "A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1". So, you could do this with uasort($otrkeys, [$this, 'cmp']);.

Custom order for php array based on indexes

I have PHP array that looks like
$my_arr['cats'] = array('Shadow', 'Tiger', 'Luna');
$my_arr['dogs'] = array('Buddy', 'Lucy', 'Bella');
$my_arr['dolphins'] = array('Sunny', 'Comet', 'Pumpkin');
$my_arr['lizzards'] = array('Apollo', 'Eddie', 'Bruce');
//and many more lines like this
I need to sort it based on it keys using sorting array like
$order = array('lizzards', 'cats');
I want that the first item should be lizzards array, second item - cats and then all items that were not specified in $order array. How it can be done using usort / uasort / uksort functions?
You can achieve this by below code
<?php
function sortByKey(&$arr,$key_order)
{
if(count(array_intersect(array_keys($arr),$key_order))!=count($key_order))
{
return false;
}
$ordered_keys=array_merge($key_order,array_diff(array_keys($arr),$key_order));
$sorted_arr=[];
foreach($ordered_keys as $key)
{
$sorted_arr[$key]=$arr[$key];
}
$arr=$sorted_arr;
return true;
}
$my_arr=[];
$my_arr['cats'] = array('Shadow', 'Tiger', 'Luna');
$my_arr['dogs'] = array('Buddy', 'Lucy', 'Bella');
$my_arr['dolphins'] = array('Sunny', 'Comet', 'Pumpkin');
$my_arr['lizzards'] = array('Apollo', 'Eddie', 'Bruce');
$order = array('lizzards', 'cats');
if(sortByKey($my_arr,$order){
echo "Sorting done successfully";
}
else
{
echo "Sorting ignored, order element miss matched";
}
print_r($my_arr);
?>
A shorter solution using uksort:
uksort($my_arr, function ($a,$b) use ($order) {
//Look for elements indexes in the 'order' array
$aKey = array_search($a, $order);
$bKey = array_search($b, $order);
if($aKey !== FALSE && $bKey !== FALSE) {
return $aKey - $bKey;
} else if($aKey !== FALSE) {
return -1;
} else if($bKey !== FALSE) {
return 1;
}
return 0;
});
You don't need to employ a sorting algorithm at all.
If you know that the nominated elements will exist in your master array, then create an array with the leading elements' keys, then place your master array onto of that.
Code: (Demo)
var_export(array_replace(array_flip($order), $my_arr));
or
var_export(array_merge(array_flip($order), $my_arr));

Sorting array of Php objects by two fields

I have formed this array of objects using this:
foreach($snapdealProductCollection as $snapdealProduct){
$item = new stdClass();
$item->id = $snapdealProduct->getId();
$item->vendortype=2;
$item->name = $snapdealProduct->getTitle();
$item->imgsrc = $snapdealProduct->getImageLink();
$item->price = $snapdealProduct->getEffectivePrice();
$item->source = "Snapdeal.com";
$item->redirectUrl = $snapdealProduct->getLink().$affProgram;
$item->type = $snapedealType[$snapdealProduct->getId()];
$item->simid = $snapdealsimid[$snapdealProduct->getId()];
$item->stype = 2;
$i++;
array_push($items, $item);
}
I need to sort firstly by type, then by simid. How should I sort it?
Full code:
$unsortedItems = $this->getSimilarItems($pid);
$vendors=$this->getAllVendors();
usort($unsortedItems , array($this, "cmp"));
function cmp($a, $b)
{
return strcmp($a->type, $b->type) || strcmp($a->simid, $b->simid);
}
$unSortedItems is the array returned from the foreach block
You can use usort() function for this, and your comparison function should be like this:
function cmp($a, $b){
if(strcmp($a->type, $b->type) == 0){
if ($a->simid == $b->simid) {
return 0;
}
return ($a->simid < $b->simid) ? -1 : 1;
}
return strcmp($a->type, $b->type);
}
usort($unsortedItems , array($this, "cmp"));
Here are the relevant references:
usort()
strcmp()
Basically you compare the first field first and the second field second :-)
function cmp($a, $b) {
return strcmp($a->type, $b->type) || strcmp($a->simid, $b->simid);
}
If the first compare returns 0, the second one will be evaluated.
You can write the same longer: if first fields are equal, compare another fields, else return the result of the first comparison.

PHP: find biggest overlap between multiple strings

I have this array:
$array = array('abc123', 'ac123', 'tbc123', '1ac123');
I want to compare each string to each other and find the longest common substring. In the example above the result would be c123.
Update
I've completely misunderstood the question; the aim was to find the biggest overlap between an array of strings:
$array = array('abc123', 'ac123', 'tbc123', '1ac123');
function overlap($a, $b)
{
if (!strlen($b)) {
return '';
}
if (strpos($a, $b) !== false) {
return $b;
}
$left = overlap($a, substr($b, 1));
$right = overlap($a, substr($b, 0, -1));
return strlen($left) > strlen($right) ? $left : $right;
}
$biggest = null;
foreach ($array as $item) {
if ($biggest === null) {
$biggest = $item;
}
if (($biggest = overlap($biggest, $item)) === '') {
break;
}
}
echo "Biggest match = $biggest\n";
I'm not great at recursion, but I believe this should work ;-)
Old answer
I would probably use preg_grep() for that; it returns an array with the matches it found based on your search string:
$matches = preg_grep('/' . preg_quote($find, '/') . '/', $array);
Alternatively, you could use array_filter():
$matches = array_filter($array, function($item) use ($find) {
return strpos($item, $find) !== false;
});
I need to extract the value "c123" like it is the biggest match for all strings in array
I think what you would want to do here is then sort the above output based on string length (i.e. smallest string length first) and then take the first item:
if ($matches) {
usort($matches, function($a, $b) {
return strlen($a) - strlen($b);
});
echo current($matches); // take first one: ac123
}
Let me know if I'm wrong about that.
If you're just after knowing whether $find matches an element exactly:
$matching_keys = array_keys($array, $find, true); // could be empty array
Or:
$matching_key = array_search($find, $array, true); // could be false
Or event:
$have_value = in_array($find, $array, true);
in_array($find, $array);
returns true if it's in the array, but it has to be the exact match, in your case it won't finde 'ac123'.
if you want to see if it contains the string then you need to loop through the array and use a preg_match() or similar
You could use array_filter with a callback.
$output = array_filter ($input, function ($elem) { return false !== strpos ($elem, 'c123'); });
<?php
$array1 = array('abc123', 'ac123', 'tbc123', '1ac123');
if (in_array("c123", $array1)) {
echo "Got c123";
}
?>
You can use in_array as used here http://codepad.org/nOdaajNe
or use can use array_search as used here http://codepad.org/DAC1bVCi
see if it can help you ..
Documentation link : http://php.net/manual/en/function.array-search.php and http://www.php.net/manual/en/function.in-array.php

Most efficient way to search for object in an array by a specific property's value

What would be the fastest, most efficient way to implement a search method that will return an object with a qualifying id?
Sample object array:
$array = [
(object) ['id' => 'one', 'color' => 'white'],
(object) ['id' => 'two', 'color' => 'red'],
(object) ['id' => 'three', 'color' => 'blue']
];
What do I write inside of:
function findObjectById($id){
}
The desired result would return the object at $array[0] if I called:
$obj = findObjectById('one')
Otherwise, it would return false if I passed 'four' as the parameter.
You can iterate that objects:
function findObjectById($id){
$array = array( /* your array of objects */ );
foreach ( $array as $element ) {
if ( $id == $element->id ) {
return $element;
}
}
return false;
}
Edit:
Faster way is to have an array with keys equals to objects' ids (if unique);
Then you can build your function as follow:
function findObjectById($id){
$array = array( /* your array of objects with ids as keys */ );
if ( isset( $array[$id] ) ) {
return $array[$id];
}
return false;
}
It's an old question but for the canonical reference as it was missing in the pure form:
$obj = array_column($array, null, 'id')['one'] ?? false;
The false is per the questions requirement to return false. It represents the non-matching value, e.g. you can make it null for example as an alternative suggestion.
This works transparently since PHP 7.0. In case you (still) have an older version, there are user-space implementations of it that can be used as a drop-in replacement.
However array_column also means to copy a whole array. This might not be wanted.
Instead it could be used to index the array and then map over with array_flip:
$index = array_column($array, 'id');
$map = array_flip($index);
$obj = $array[$map['one'] ?? null] ?? false;
On the index the search problem might still be the same, the map just offers the index in the original array so there is a reference system.
Keep in mind thought that this might not be necessary as PHP has copy-on-write. So there might be less duplication as intentionally thought. So this is to show some options.
Another option is to go through the whole array and unless the object is already found, check for a match. One way to do this is with array_reduce:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry === false && $item->id === 'one' ? $item : $carry;
}, false);
This variant again is with the returning false requirement for no-match.
It is a bit more straight forward with null:
$obj = array_reduce($array, static function ($carry, $item) {
return $carry ?? ($item->id === 'one' ? $item : $carry);
}, null);
And a different no-match requirement can then be added with $obj = ...) ?? false; for example.
Fully exposing to foreach within a function of its own even has the benefit to directly exit on match:
$result = null;
foreach ($array as $object) {
if ($object->id === 'one') {
$result = $object;
break;
}
}
unset($object);
$obj = $result ?? false;
This is effectively the original answer by hsz, which shows how universally it can be applied.
You can use the function array_search of php like this
$key=array_search("one", array_column(json_decode(json_encode($array),TRUE), 'color'));
var_dump($array[$key]);
i: is the index of item in array
1: is the property value looking for
$arr: Array looking inside
'ID': the property key
$i = array_search(1, array_column($arr, 'ID'));
$element = ($i !== false ? $arr[$i] : null);
Well, you would would have to loop through them and check compare the ID's unless your array is sorted (by ID) in which case you can implement a searching algorithm like binary search or something of that sort to make it quicker.
My suggestion would be to first sort the arrays using a sorting algorithm (binary sort, insertion sort or quick sort) if the array is not sorted already. Then you can implement a search algorithm which should improve performance and I think that's as good as it gets.
http://www.algolist.net/Algorithms/Binary_search
This is my absolute favorite algorithm for very quickly finding what I need in a very large array, quickly. It is a Binary Search Algorithm implementation I created and use extensively in my PHP code. It hands-down beats straight-forward iterative search routines. You can vary it a multitude of ways to fit your need, but the basic algorithm remains the same.
To use it (this variation), the array must be sorted, by the index you want to find, in lowest-to-highest order.
function quick_find(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($array[$m]->{$property} < $value_to_find) {
$l = $m + 1;
} else if ($array[$m]->{$property} > $value_to_find) {
$r = $m - 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
And to test it out:
/* Define a class to put into our array of objects */
class test_object {
public $index;
public $whatever_you_want;
public function __construct( $index_to_assign ) {
$this->index = $index_to_assign;
$this->whatever_you_want = rand(1, 10000000);
}
}
/* Initialize an empty array we will fill with our objects */
$my_array = array();
/* Get a random starting index to simulate data (possibly loaded from a database) */
$my_index = rand(1256, 30000);
/* Say we are needing to locate the record with this index */
$index_to_locate = $my_index + rand(200, 30234);
/*
* Fill "$my_array()" with ONE MILLION objects of type "test_object"
*
* 1,000,000 objects may take a little bit to generate. If you don't
* feel patient, you may lower the number!
*
*/
for ($i = 0; $i < 1000000; $i++) {
$searchable_object = new test_object($my_index); // Create the object
array_push($my_array, $searchable_object); // Add it to the "$my_array" array
$my_index++; /* Increment our unique index */
}
echo "Searching array of ".count($my_array)." objects for index: " . $index_to_locate ."\n\n";
$index_found = -1; // Variable into which the array-index at which our object was found will be placed upon return of the function.
$object = quick_find($my_array, "index", $index_to_locate, $index_found);
if ($object == NULL) {
echo "Index $index_to_locate was not contained in the array.\n";
} else {
echo "Object found at index $index_found!\n";
print_r($object);
}
echo "\n\n";
Now, a few notes:
You MAY use this to find non-unique indexes; the array MUST still be sorted in ascending order. Then, when it finds an element matching your criteria, you must walk the array backwards to find the first element, or forward to find the last. It will add a few "hops" to your search, but it will still most likely be faster than iterating a large array.
For STRING indexes, you can change the arithmetic comparisons (i.e. " > " and " < " ) in quick_find() to PHP's function "strcasecmp()". Just make sure the STRING indexes are sorted the same way (for the example implementation): Alphabetically and Ascending.
And if you want to have a version that can search arrays of objects sorted in EITHER ascending OR decending order:
function quick_find_a(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($array[$m]->{$property} < $value_to_find) {
$l = $m + 1;
} else if ($array[$m]->{$property} > $value_to_find) {
$r = $m - 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
function quick_find_d(&$array, $property, $value_to_find, &$first_index) {
$l = 0;
$r = count($array) - 1;
$m = 0;
while ($l <= $r) {
$m = floor(($l + $r) / 2);
if ($value_to_find > $array[$m]->{$property}) {
$r = $m - 1;
} else if ($value_to_find < $array[$m]->{$property}) {
$l = $m + 1;
} else {
$first_index = $m;
return $array[$m];
}
}
return FALSE;
}
function quick_find(&$array, $property, $value_to_find, &$first_index) {
if ($array[0]->{$property} < $array[count($array)-1]->{$property}) {
return quick_find_a($array, $property, $value_to_find, $first_index);
} else {
return quick_find_d($array, $property, $value_to_find, $first_index);
}
}
The thing with performance of data structures is not only how to get but mostly how to store my data.
If you are free to design your array, use an associative array:
$array['one']->id = 'one';
$array['one']->color = 'white';
$array['two']->id = 'two';
$array['two']->color = 'red';
$array['three']->id = 'three';
$array['three']->color = 'blue';
Finding is then the most cheap: $one = $array['one];
UPDATE:
If you cannot modify your array constitution, you could create a separate array which maps ids to indexes. Finding an object this way does not cost any time:
$map['one'] = 0;
$map['two'] = 1;
$map['three'] = 2;
...
getObjectById() then first lookups the index of the id within the original array and secondly returns the right object:
$index = $map[$id];
return $array[$index];
Something I like to do in these situations is to create a referential array, thus avoiding having to re-copy the object but having the power to use the reference to it like the object itself.
$array['one']->id = 'one';
$array['one']->color = 'white';
$array['two']->id = 'two';
$array['two']->color = 'red';
$array['three']->id = 'three';
$array['three']->color = 'blue';
Then we can create a simple referential array:
$ref = array();
foreach ( $array as $row )
$ref[$row->id] = &$array[$row->id];
Now we can simply test if an instance exists in the array and even use it like the original object if we wanted:
if ( isset( $ref['one'] ) )
echo $ref['one']->color;
would output:
white
If the id in question did not exist, the isset() would return false, so there's no need to iterate the original object over and over looking for a value...we just use PHP's isset() function and avoid using a separate function altogether.
Please note when using references that you want use the "&" with the original array and not the iterator, so using &$row would not give you what you want.
This is definitely not efficient, O(N). But it looks sexy:
$result = array_reduce($array, function ($found, $obj) use ($id) {
return $obj['id'] == $id ? $obj : $found;
}, null);
addendum:
I see hakre already posted something akin to this.
Here is what I use. Reusable functions that loop through an array of objects. The second one allows you to retrieve a single object directly out of all matches (the first one to match criteria).
function get_objects_where($match, $objects) {
if ($match == '' || !is_array($match)) return array ();
$wanted_objects = array ();
foreach ($objects as $object) {
$wanted = false;
foreach ($match as $k => $v) {
if (is_object($object) && isset($object->$k) && $object->$k == $v) {
$wanted = true;
} else {
$wanted = false;
break;
};
};
if ($wanted) $wanted_objects[] = $object;
};
return $wanted_objects;
};
function get_object_where($match, $objects) {
if ($match == '' || !is_array($match)) return (object) array ();
$wanted_objects = get_objects_where($match, $objects);
return count($wanted_objects) > 0 ? $wanted_objects[0] : (object) array ();
};
The easiest way:
function objectToArray($obj) {
return json_decode(json_encode($obj), true);
}

Categories