php associative array : compare adjacent values - php

I would like to check many rows of the same 2 input fields in a form. The validation should fail if one is empty and the other is not.
I have created an associative array based on several input fields(e_me_id, e_md_number, e_md_id1,e_md_number1, p_me_id,p_md_number...) in a form.
$pattern='(md_number|me_id)';
foreach($_POST as $field => $value) {
$success = preg_match($pattern, $field);
if ($success) {
$validate += [$field => $value];
}
}
result of validate =(
[e_me_id] => 1
[e_md_number] => 111
[e_me_id2] => 2
[e_md_number2] => 222
[p_me_id] => 10
[p_md_number] => 101010
[f_me_id] => 16
[f_md_number] => 161616
[d_me_id] => 18
[d_md_number] => 181818 )
I need some looping php to check that the first/second are both null or both filled... same for third/forth, fifth/sixth... etc etc.
I tried to use prev($validate) and next($validate) but could not get it to work.
Any ideas or a different approach.
Thanks in advance.

You can do this to do the validation of pairs:
// assuming this is the array generated by your code ...
$tst= array("e_me_id" => 1,"e_md_number" => 111 ,
"e_me_id2" => 2,"e_md_number2" => 222 ,
"p_me_id" => 0,"p_md_number" => 101010 ,
"f_me_id" => 16,"f_md_number" => 161616 ,
"d_me_id" => 0,"d_md_number" => 0 );
// then this will do the validation of pairs:
$keys=array_keys($tst);
for ($i=0;$i<count($tst);$i+=2)
echo "$keys[$i] and "
.$keys[$i+1]
.(empty($tst[$keys[$i]]) == empty($tst[$keys[$i+1]])?'':' DO NOT')
." pass the validation.\n";
You can see a demo here: https://rextester.com/VEMUFJ14979
I changed a few of the numbers to demonstrate the different possible cases.
Using the == operator between the two empty()-tests is equivalent to using the negated xor operator as suggested by #Markus Zeller.
Edit:
A "shorthand-if statement" should look something like this:
$x = (empty($tst[$keys[$i]]) == empty($tst[$keys[$i+1]])?'':$fieldname);
Although to me it is not quite clear what you intend $x and $fieldname to be in your for-loop. This would set $x to '' if case both tested values were empty or both were "filled" and it would set it to $fieldname when only one of them was empty.
Your most recent comment led me to believe that you want to have an overall validation result in $x. In order to get this you will have to count all errors while looping over the pairs. Something like the following should do that:
for ($i=0,$x=0;$i<count($tst);$i+=2){
if (empty($tst[$keys[$i]]) != empty($tst[$keys[$i+1]])) $x++;
// echo $keys[$i].", error count so far: $x\n";
}
echo ("here ".($x?'validation error!':'OK'));
See the demo here: https://rextester.com/XTK88037

Related

Simplify PHP array with same items

I have this PHP array:
$this->user_list = array( 0 => 'Not paid',1 => 'Not paid', 2 => 'Not paid', 7 => 'Waiting, 15 => 'Waiting', 10 => 'Cancelled' );
How can I simplify this array as the id numbers are different, but some of them have same status?
I tried it like this:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
but it doesn't work as expected.
Basically I want to achieve this:
echo $this->user_list[15] should give me Waiting, echo $this->user_list[10] should give me Cancelled, etc. So this is working in my first array very well, I am just thinking about grouping duplicate names there.
As mentioned by other contributors, there is no native support in the PHP grammar for your intended use case. As clearly stated in the PHP: Arrays documentation:
An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments.
So basically each element in an array is a key => value pair, which means you cannot associate multiple keys to a single element.
This also explains why your first tentative didn't work:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
If you don't specify a key for an element, PHP uses a progressive index (0, 1, ...). So basically in the example above, the first zero is not actually a key, but a value, and PHP binds it to the key = 0. Maybe it could be easier for you to understand how it works if you print a var_dump or print_r of $this->user_list. You would get something similar to the following structure (NOTE: I have simplified the structure to make it more clear):
[
0 => [
0 => 0
1 => 1
2 => "Not paid"
],
1 => [
0 => 7,
15 => "Waiting"
],
10 => "Cancelled"
]
So how do we resolve this problem? Well... actually there is no need to contort the structure by swapping keys with values as other contributors seem to suggest. Changing the structure might simplify your "data entry" work but might also create big issues in other parts of the program because who knows, maybe accessing the invoice data by "ID" is simply more efficient than by "status" ... or something.
Since PHP does not provide such a feature out of the box, I believe a better solution would be to develop our own function; a good starting point could be the one in the example below.
function explode_array($config, $sep = ',') {
$res = [];
foreach($config as $configKey => $value) {
// split key values
$keys = explode($sep, $configKey);
foreach($keys as $key) {
$res[$key] = $value;
}
}
return $res;
}
$config = [
'0,1,2' => 'Not paid',
'7,15' => 'Waiting',
'10' => 'Cancelled'
];
$myArr = explode_array($config);
print_r($myArr);
The idea is quite simple: since we cannot use an array as key we leverage the next best data type, that is a CSV string. Please note there is no error handling in the above code, so the first thing you may want to do is adding some validation code to the explode_array (or however you wish to name it) function.
you should use like this. if id number is invoice id or something else and other value is there status about it.
$arr = array(
'Not paid' => [0,1,2] ,
'Waiting' => [5,6],
'Cancelled' =>[8]
);
foreach($arr as $key => $val){
foreach($val as $keys => $vals){
echo "invoiceid ".$vals ." status ".$key;
echo"<br>";
}
}
// for only one status you can use like this
foreach($arr['Not paid'] as $key => $val){
echo $val;
echo"<br>";
}
just try to run this and check output.
PHP has no built-in function or structure for handling cases like this. I'd use a simple array value-cloning function to map your duplicates. Simply have one instance of each status, then map the aliases, and then run a function that clones them in. As follows:
// Status list:
$ulist = [ 0 => 'Not paid', 7 => 'Waiting', 10 => 'Cancelled' ];
// Alternative IDs list, mapped to above source IDs:
$aliases = [ 0 => [1,2], 7 => [15] ];
// Function to clone array values:
function clone_values(array &$arr, array $aliases)
{
foreach($aliases as $src => $tgts) {
foreach($tgts as $tgt) {
$arr[$tgt] = $arr[$src];
}
}
ksort($arr); // If the order matters
}
// Let's clone:
clone_values($ulist, $aliases);
This results in the following array:
array(6) {
[0] · string(8) "Not paid"
[1] · string(8) "Not paid"
[2] · string(8) "Not paid"
[7] · string(7) "Waiting"
[10] · string(9) "Cancelled"
[15] · string(7) "Waiting"
}
....which can be accessed as you expect, here $ulist[2] => Not paid, etc. If the use case is as simple as illustrated in the OP, I'd personally just spell it out as is. There's no dramatic complexity to it. However, if you have dozens of aliases, mapping and cloning begins to make sense.
As said in the comments, you can't have multiple keys with one value. The best way is to use the keyword => [ number, number, number...] construction.
//set a result array
$result = [];
//loop the original array
foreach ( $this->user_list as $number => $keyword ){
//if the keyword doesn't exist in the result, create one
if(!isset ( $result [ $keyword ] ) ) $result[ $keyword ] = [];
//add the number to the keyword-array
$result[ $keyword ] [] = $number;
}

Using str_replace() With Array Values Giving Unexpected Results

Using str_replace() to replace values in a couple paragraphs of text data, it seems to do so but in an odd order. The values to be replaced are in a hard-coded array while the replacements are in an array from a query provided by a custom function called DBConnect().
I used print_r() on both to verify that they are correct and they are: both have the same number of entries and are in the same order but the on-screen results are mismatched. I expected this to be straightforward and didn't think it needed any looping for this simple task as str_replace() itself usually handles that but did I miss something?
$replace = array('[MyLocation]','[CustLocation]','[MilesInc]','[ExtraDoc]');
$replacements = DBConnect($sqlPrices,"select",$siteDB);
$PageText = str_replace($replace,$replacements,$PageText);
and $replacements is:
Array
(
[0] => 25
[MyLocation] => 25
[1] => 45
[CustLocation] => 45
[2] => 10
[MilesInc] => 10
[3] => 10
[ExtraDoc] => 10
)
Once I saw what the $replacements array actually looked like, I was able to fix it by filtering out the numeric keys.
$replace = array('[MyLocation]','[CustLocation]','[MilesInc]','[ExtraDoc]');
$replacements = DBConnect($sqlPrices,"select",$siteDB);
foreach ($replacements as $key=>$value) :
if (!is_numeric($key)) $newArray[$key] = $value;
endforeach;
$PageText = str_replace($replace,$newArray,$PageText);
The former $replacements array, filtered to $newArray, looks like this:
Array
(
[MyLocation] => 25
[CustLocation] => 45
[MilesInc] => 10
[ExtraDoc] => 10
)
-- edited: Removed some non sense statements --
#DonP, what you are trying to do is possible.
In my opinion, the strtr() function could be more beneficial to you. All you need to make a few adjustments in your code like this ...
<?php
$replacements = DBConnect($sqlPrices,"select",$siteDB);
$PageText = strtr($PageText, [
'[MyLocation]' => $replacements['MyLocation'],
'[CustLocation]' => $replacements['CustLocation'],
'[MilesInc]' => $replacements['MilesInc'],
'[ExtraDoc]' => $replacements['ExtraDoc'],
]);
?>
This code is kinda verbose and requires writing repetitive strings. Once you understand the way it works, you can use some loops or array functions to refactor it. For example, you could use the following more compact version ...
<?php
// Reference fields.
$fields = ['MyLocation', 'CustLocation', 'MilesInc', 'ExtraDoc'];
// Creating the replacement pairs.
$replacementPairs = [];
foreach($fields as $field){
$replacementPairs["[{$field}]"] = $replacements[$field];
}
// Perform the replacements.
$PageText = strtr($PageText, $replacementPairs);
?>

Eliminate Partial Strings from Array of Strings PHP

I can do this, I'm just wondering if there is a more elegant solution than the 47 hacked lines of code I came up with...
Essentially I have an array (the value is the occurrences of said string);
[Bob] => 2
[Theresa] => 3
[The farm house] => 2
[Bob at the farm house] => 1
I'd like to iterate through the array and eliminate any entries that are sub-strings of others so that the end result would be;
[Theresa] => 3
[Bob at the farm house] => 1
Initially I was looping like (calling this array $baseTags):
foreach($baseTags as $key=>$count){
foreach($baseTags as $k=>$c){
if(stripos($k,$key)){
unset($baseTags[$key]);
}
}
}
I'm assuming I'm looping through each key in the array and if there is the occurrence of that key inside another key to unset it... doesn't seem to be working for me though. Am I missing something obvious?
Thank you in advance.
-H
You're mis-using strpos/stripos. They can return a perfectly valid 0 if the string you're searching for happens to be at the START of the 'haystack' string, e.g. your Bob value. You need to explicitly test for this with
if (stripos($k, $key) !== FALSE) {
unset(...);
}
if strpos/stripos don't find the needle, they return a boolean false, which under PHP's normal weak comparison rules is equal/equivalent to 0. Using the strict comparison operators (===, !==), which compare type AND value, you'll get the proper results.
Don't forget as-well as needing !== false, you need $k != $key so your strings don't match themselves.
You have two problems inside your code-example:
You combine each key with each key, so not only others, but also itself. You would remove all entries so because "Bob" is a substring of "Bob", too.
stripos returns false if not found which can be confused with 0 that stands for found at position 0, which is also equal but not identical to false.
You need to add an additional check to not remove the same key and then fix the check for the "not found" case (Demo):
$baseTags = array(
'Bob' => 2,
'Theresa' => 3,
'The farm house' => 2,
'Bob at the farm house' => 1,
);
foreach ($baseTags as $key => $count)
{
foreach ($baseTags as $k => $c)
{
if ($k === $key)
{
continue;
}
if (false !== stripos($k, $key))
{
unset($baseTags[$key]);
}
}
}
print_r($baseTags);
Output:
Array
(
[Theresa] => 3
[Bob at the farm house] => 1
)

php array_merge without erasing values?

Background: Trevor is working with a PHP implementation of a standard algorithm: take a main set of default name-value pairs, and update those name-value pairs, but only for those name-value pairs where a valid update value actually exists.
Problem: by default, PHP array_merge works like this ... it will overwrite a non-blank value with a blank value.
$aamain = Array('firstname'=>'peter','age'=>'32','nation'=>'');
$update = Array('firstname' => '','lastname' => 'griffin', age =>'33','nation'=>'usa');
print_r(array_merge($aamain,$update));
/*
Array
(
[firstname] => // <-- update set this to blank, NOT COOL!
[age] => 33 // <-- update set this to 33, thats cool
[lastname] => griffin // <-- update added this key-value pair, thats cool
[nation] => usa // <-- update filled in a blank, thats cool.
)
*/
Question: What's the fewest-lines-of-code way to do array_merge where blank values never overwrite already-existing values?
print_r(array_coolmerge($aamain,$update));
/*
Array
(
[firstname] => peter // <-- don't blank out a value if one already exists!
[age] => 33
[lastname] => griffin
[nation] => usa
)
*/
UPDATE: 2016-06-17T11:51:54 the question was updated with clarifying context and rename of variables.
Well, if you want a "clever" way to do it, here it is, but it may not be as readable as simply doing a loop.
$merged = array_merge(array_filter($foo, 'strval'), array_filter($bar, 'strval'));
edit: or using +...
array_replace_recursive($array, $array2);
This is the solution.
Try this:
$merged = array_map(
create_function('$foo,$bar','return ($bar?$bar:$foo);'),
$foobar,$feebar
);
Not the most readable solution, but it should replace only non-empty values, regardless of which order the arrays are passed..
Adjust to your needs:
# Replace keys in $foo
foreach ($foo as $key => $value) {
if ($value != '' || !isset($bar[$key])) continue;
$foo[$key] = $bar[$key];
}
# Add other keys in $bar
# Will not overwrite existing keys in $foo
$foo += $bar;
If you also want to keep the values that are blank in both arrays:
array_filter($foo) + array_filter($bar) + $foo + $bar
This will put duplicates into a new array, I don't know if this is what you want though.
<?php
$foobar = Array('firstname' => 'peter','age' => '33',);
$feebar = Array('firstname' => '','lastname' => 'griffin',);
$merged=$foobar;
foreach($feebar as $k=>$v){
if(isset($foobar[$k]))$merged[$k]=array($v,$foobar[$k]);
else $merged[$k]=$v;
}
print_r($merged);
?>
This will simply assure that feebar will never blank out a value in foobar:
<?php
$foobar = Array('firstname' => 'peter','age' => '33',);
$feebar = Array('firstname' => '','lastname' => 'griffin',);
$merged=$foobar;
foreach($feebar as $k=>$v) if($v)$merged[$k]=$v;
print_r($merged);
?>
or ofcourse,
<?
function cool_merge($array1,$array2){
$result=$array1;
foreach($array2 as $k=>$v) if($v)$result[$k]=$v;
return $result;
}
$foobar = Array('firstname' => 'peter','age' => '33',);
$feebar = Array('firstname' => '','lastname' => 'griffin',);
print_r(cool_merge($foobar,$feebar));
?>

Count Similar Array Keys

I have a POST request coming to one of my pages, here is a small segment:
[shipCountry] => United States
[status] => Accepted
[sku1] => test
[product1] => Test Product
[quantity1] => 1
[price1] => 0.00
This request can be any size, and each products name and quantity's key would come across as "productN" and "quantityN", where N is an integer, starting from 1.
I would like to be able to count how many unique keys match the format above, which would give me a count of how many products were ordered (a number which is not explicitly given in the request).
What's the best way to do this in PHP?
Well, if you know that every product will have a corresponding array key matching "productN", you could do this:
$productKeyCount = count(preg_grep("/^product(\d)+$/",array_keys($_POST)));
preg_grep() works well on arrays for that kind of thing.
What Gumbo meant with his "use array instead" comment is the following:
In your HTML-form use this:
<input type="text" name="quantity[]" />
and $_POST['quantity'] will then be an array of all containing all of your quantities.
If you need to supply an id you can also do this:
<input type="text" name="quantity[0]" />
$_POST['quantity][0] will then hold the corresponding quantity.
As mentioned by gumbo you could group all parameters describing one item in its own array which usually makes it easier to iterate them. You may not have control over the POST parameters but you can restructure them like e.g. with
<?php
$testdata = array(
'shipCountry' => 'United States',
'status' => 'Accepted',
'sku1' => 'test1',
'product1' => 'Test Product1',
'quantity1' => '1',
'price1' => '0.01',
'sku2' => 'test2',
'product2' => 'Test Product2',
'quantity2' => '2',
'price2' => '0.02'
);
$pattern = '/^(.*\D)(\d+)$/';
$foo = array('items'=>array());
foreach($testdata as $k=>$v) {
if ( preg_match($pattern, $k, $m) ) {
$foo['items'][$m[2]][$m[1]] = $v;
}
else {
$foo[$k] = $v;
}
}
print_r($foo);
Though there be plenty of examples, if you're guaranteed that the numbers should be contiguous, I usually take the approach:
<?php
$i = 1;
while( isset($_POST['product'.$i) )
{
// do something
$i++;
}

Categories