Search in array with relevance - php

I am doing a very small online store application in PHP. So I have an array of maps in PHP. I want to search for a string (a product) in the array. I looked at array_search in PHP and it seems that it only looks for exact match. Do you guys know a better way to do this functionality? Since this is a very small part of what I am actually doing, I was hoping that there was something built in. Any ideas?
Thanks!
EDIT: The array contains "products" in this format:
[6] => SimpleXMLElement Object
(
[#attributes] => Array
(
[id] => 2000-YM
)
[Name] => Team Swim School T-Shirt
[size] => YM
[price] => 15
[group] => Team Clothing
[id] => 2000-YM
)
[7] => SimpleXMLElement Object
(
[#attributes] => Array
(
[id] => 3000-YS
)
[Name] => Youth Track Jacket
[size] => YS
[price] => 55
[group] => Team Clothing
[id] => 3000-YS
)
So I was wondering I can do a search such as "Team" and it would return me first item seen here. I am basing the search on the Name (again this is just something small). I understand that I can find the exact string, I am just stuck on the "best results" if it cannot find the exact item. Efficiency is nice but not required since I only have about 50 items so even if I use a "slow" algorithm it won't take much time.

array_filter lets you specify a custom function to do the searching. In your case, a simple function that uses strpos() to check if your search string is present:
function my_search($haystack) {
$needle = 'value to search for';
return(strpos($haystack, $needle)); // or stripos() if you want case-insensitive searching.
}
$matches = array_filter($your_array, 'my_search');
Alternatively, you could use an anonymous function to help prevent namespace contamination:
$matches = array_filter($your_array, function ($haystack) use ($needle) {
return(strpos($haystack, $needle));
});

foreach($array as $item){
if(strpos($item,"mysearchword")!== false){
echo 'found';
}
}
or you can use preg_match for more flexible search instead of strpos.

I think Marc B's answer was a good starting point but for me it had some problems. Such as you have to know what the Needle is at "compile time" because you can't dynamically change that value. also if the needle appeared at the start of the string element it would act like it's not there at all. so after a little experimenting I manged to come up with a way around both problems. so you don't have to create a new function for every different needle your going to want to use anymore.
function my_search($haystack)
{
global $needle;
if( strpos($haystack, $needle) === false) {return false;} else {return true;}
}
and it would be called like this:
$needle="item to search for";
$matches = array_filter($my_array, 'my_search');
and being as needle is now accessible in the same scope that the rest of the code is you can set needle to any other string variable you wanted, including user input.

Unfortunately, search is one of the more difficult things to do in computer science. If you build for search based on literal string matches or regular expressions (regex), you may find that you'll be unhappy with the relevance of the results that are returned.
If you're interested in rolling up your sleeves and getting a little dirty with a more sophisticated solution, I'd try Zend's Lucene implementation ( http://framework.zend.com/manual/en/zend.search.lucene.html ). I've implemented a search on a site with it. It took a few days, but the results were MUCH better than the 15 minute solution of literal string matching.
PS. Here's an example: http://devzone.zend.com/article/91

I have same Issue but i have created i function to search in array by passing the array, key and value.
public function searchinarr($array, $key, $value)
{
$results = array();
for($i=0;$i<count($array);$i++)
{
foreach($array[$i] as $k=>$val)
{
if($k==$key)
{
if(strpos($val,$value)!== false)
{
$results[] = $array[$i];
}
}
}
}
return $results;
}

Related

array_walk not changing value

function values($id,$col)
{
$vals = [1=>['name'=>'Lifting Heavy Boxes']];
return $vals[$id][$col];
}
$complete = [1=>["id"=>"2","sid"=>"35","material_completed"=>"1","date"=>"2017-12-18"]];
$form = 'my_form';
array_walk($complete, function(&$d,$k) use($form) {
$k = values($k, 'name').' ['.date('m/d/y',strtotime($d['date'])).'] ('.$form.')';
echo 'in walk '.$k."\n";
});
print_r($complete);
the echo outputs:
in walk Lifting Heavy Boxes [12/18/17] (my_form)
the print_r outputs:
Array
(
[1] => Array
(
[id] => 2
[sid] => 35
[material_completed] => 1
[date] => 2017-12-18
)
)
I have another array walk that is very similar that is doing just fine. The only difference I can perceive between them is in the one that's working, the value $d is already a string before it goes through the walk, whereas in the one that's not working, $d is an array that is converted to a string inside the walk (successfully, but ultimately unsuccessfully).
Something I'm missing?
And here's the fixed version:
array_walk($complete, function(&$d,$k) use($form) {
$d = values($k, 'name').' ['.date('m/d/y',strtotime($d['date'])).'] ('.$form.')';
});
That's what I was trying to do anyway. I wasn't trying to change the key. I was under the mistaken impression that to change the value you had to set the key to the new value.
You cannot change the key of the array inside the callback of array_walk():
Only the values of the array may potentially be changed; its structure cannot be altered, i.e., the programmer cannot add, unset or reorder elements. If the callback does not respect this requirement, the behavior of this function is undefined, and unpredictable.
This is also mentioned in the first comment:
It's worth nothing that array_walk can not be used to change keys in the array.
The function may be defined as (&$value, $key) but not (&$value, &$key).
Even though PHP does not complain/warn, it does not modify the key.

Polymorph String To Pattern

I'm working on an issue where users (truck drivers in this case) use SMS to send in information about work status. I want to keep the keying simple as not all users have smart phones so I have adopted some simple short codes for their input. Here are some examples and their meanings:
P#123456-3 (This is for picking up load 123456-3)
D#456789-1 (For the dropping of load 456789-1)
L#345678-9 (Load 345678-9 is going to be late)
This is pretty simple but users (and truck drivers) being what they are will key the updates in somewhat deviant manners such as:
#D 456789-1
D# 456789 - 1
D#.456789-1 This load looks wet to me do weneed to cancelthis order
You can pretty much come up with a dozen other permutations and it's not hard for me to catch and fix those that I can imagine.
I mostly use regular expressions to test the input against all my imagined "bad" patterns and then extract what I assume are the good parts, reassembling them into the correct order.
It's the new errors that cause me problems so I got to wondering if there was a more generic method where I can pass a "pattern" and a "message" to a function that would do it's best to turn the "message" into something matching the "pattern".
My searches have not found anything that really fits what I'm trying to do and I'm not even sure if there is a good general way to do this. I happen to be using PHP for this implementation but any type of example should help. Do any of you have a method?
If the user has problems with your software, fix the software, not the user!
The problem arises because your format looks unnecessary complicated. Why do you need the hash in the first place? How about simplifying it down to the following:
operation-code maybe-space load-number maybe-space and comment
Operation codes are assigned to different phone keys, so that J, K and L mean the same thing. Load-numbers can be sent as digits and as letters as well, e.g. agja means 2452. It's hard for the user to make a mistake using this format.
Here's some code to illustrate this approach:
function parse($msg) {
$codes = array(
3 => 'DROP',
5 => 'LOAD',
// etc
);
preg_match('~(\S)\s*(\S+)(\s+.+)?~', $msg, $m);
if(!$m)
return null; // cannot parse
$a = '.,"?!abcdefghijklmnopqrstuvwxyz';
$d = '1111122233344455566677777888999';
return array(
'opcode' => $codes[strtr($m[1], $a, $d)],
'load' => intval(strtr($m[2], $a, $d)),
'comment' => isset($m[3]) ? trim($m[3]) : ''
);
}
print_r(parse(' j ww03 This load looks wet to me'));
//[opcode] => LOAD
//[load] => 9903
//[comment] => This load looks wet to me
print_r(parse('dxx0123'));
//[opcode] => DROP
//[load] => 990123
//[comment] =>
Try something like this:
function parse($input) {
// Clean up your input: 'D#.456789 - 1 foo bar' to 'D 456789 1 foo far'
$clean = trim(preg_replace('/\W+/', ' ', $input));
// Take first 3 words.
list($status, $loadId1, $loadId2) = explode(' ', $clean);
// Glue back your load ID to '456789-1'
$loadId = $loadId1 . '-' . $loadId2;
return compact('status', 'loadId');
}
Example:
$inputs = array(
'P#123456-3',
'#D 456789-1',
'D# 456789 - 1',
'D#.456789-1 This load looks wet to me do weneed to cancelthis order',
);
echo '<pre>';
foreach ($inputs as $s) {
print_r(parse($s));
}
Output:
Array
(
[status] => P
[loadId] => 123456-3
)
Array
(
[status] => D
[loadId] => 456789-1
)
Array
(
[status] => D
[loadId] => 456789-1
)
Array
(
[status] => D
[loadId] => 456789-1
)
First, remove stuff that shouldn't be there:
$str = preg_replace('/[^PDL\d-]/i', '', $str);
That gives you the following normalised results:
D456789-1
D456789-1
D456789-1ldlddld
Then, attempt to match the data you want:
if (preg_match('/^([PDL])(\d+-\d)/i', $str, $match)) {
$code = $match[1];
$load = $match[2];
} else {
// uh oh, something wrong with the format!
}
Something like
/^[#\s]*([PDL])[#\s]*(\d+[\s-]+\d)/
or to be even more relaxed,
/^[^\d]*([PDL])[^\d]*(\d+)[^\d]+(\d)/
would get you what you want. But I'd prefer HamZa's comment as a solution: throw it back and tell them to get their act together :)

How to write a bann function in php?

I have an array of many banned string and i have a small string contains one keywords, too and i want to write a function in php like this:
function is_ban($keyword,$bannedList) {
}
where $keyword is small string and $bannedList is an array like
Array
(
[0] => php
[1] => html
[2] => java
[3] => css
[....]
)
The function check keyword in banned list and return true or false.
function is_ban($keyword,$bannedList) {
return in_array($keyword, $bannedList);
}
This is my first reply on a php related question. As others have said, if you have a precisely defined array of banned words, and you have already taken the time to get the word $keyword from the user, then by all means just use PHP's native function in_array(). You may however need to do the following:
if(in_array(strtolower($keyword), $bannedList)){ //return true }
Just make sure of course that your $bannedList array is all lowercase as well. If however you need to do pattern matches inside longer strings, then you'll need to resort to regular expressions.
This is a simple way to define your function
function is_ban($keyword,$bannedList)
{
return in_array($keyword, $bannedList);
}

Apply function to every array key

I am using Cassandra and I have saved some byte representations as ID. Everything is working fine, however that data (id) is no good for output.
$users = $db->get('1');
echo '<pre>';
print_r($users);
die();
Outputs
Array
(
[��� X��W��c_ ] => Array
(
[id] => ��� X��W��c_
[name] => steve
[surname] => moss
)
[�*B�X��y�~p��~] => Array
(
[id] => �*B�X��y�~p��~
[name] => john
[surname] => doe
)
)
As you can see ID's are some wierd characters, it's because they are byte representations in database. They actually look like \xf5*B\xa0X\x00\x11\xe1\x99y\xbf~p\xbc\xd1~.
In PHPCASSA there is function CassandraUtil::import(); to which I can pass these bytes and it will return guid. It works fine, but I want my array to automatically converted from bytes to guids.
Only option I find is looping through every item in array and assigning new value to it. Somehow I think that it is not the best approach. Is there any other ways to do this?
TL;DR
Have array with bytes like above, need to use CassandraUtil::import(); on array keys and id's to get readable id's. What is the most effective way of doing so.
UPDATE
Sorry, only saw the top level array key, I think you would have to run the function below as well as another one after:
function cassImportWalkRecur(&$item, $key)
{
if ($key == 'id')
$item = CassandraUtil::import();
}
$array = array_walk_recursive($array, 'cassImportWalkRecur');
That should apply it to the ID fields. If you need to check the data first, there maybe a way to detect the encoding, but I am not sure how to do that.
You should be able to create a function and use array_walk to traverse the array and update the keys. Something like:
function cassImportWalk($item, &$key)
{
$key = CassandraUtil::import();
}
$array = array_walk($array, 'cassImportWalk');
Untested (also you may have to change the CassandraUtil usage), but should work.
Unless I am misunderstanding the question this can be done simply and cleanly like so:
$users = $db->get('1');
$keys = array_keys($users);
$readableKeys = array_map("CassandraUtil::import",$keys);
foreach($users as $currentKey => $subArray) {
$readableKey = array_shift($readableKeys);
$subArray['id'] = $readableKey;
$users[$readableKey] = $subArray;
unset($users[$currentKey]);
}
Would array_flip() all keys and values, then array_walk() and apply my function, before doing a final array_flip().

How to access the same element of an array that has only one element or multiple elements?

I access an API that returns an array of elements.
If there is only one element, it will return the array as:
Array {
[response] => Array {
[name] => Frank
}
}
However, if there are multiple results, it goes one level deeper to account for each result:
Array {
[response] => Array {
[0] = > Array {
[name] => Frank
}
[1] = > Array {
[name] => John
}
}
}
This is quite frustrating as it means I have to first check if there is just one element or more than one, and then code each one separately.
Is there a better solution that automatically takes care of both scenarios (e.g. one result vs. multiple results) and always retrieves the name's that are available regardless ?
You could either write an iterator that would deal with your special case, or you iterate over it an deal with the special case:
foreach ($array['response'] as $responses)
{
isset ($responses[0]) || $responses = array($responses);
foreach ($repsonses as $response)
{
# standard processing code per each item.
}
}
Special cases can be very annoying, so take care of them early and ideally make them disappear.
(If you can) Tweak the original API to always return the array with the indexed keys even when there are only one item in the array.
Else, add this one after you fetch the result from the API.
if(count($result['response'])==1) { $newResult['response'][0]=$result; }
else { $newResult=$result; }

Categories