PHP - Count all elements of an Array that Satisfy a Condition [duplicate] - php

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Search for PHP array element containing string
I've created a mysql query that pulls through several products, all with the following information:
Product ID
Product Name
Product Price
and
Product Category
Further down the page, I've looped through these with a foreach and a few 'ifs' so it only displays those products where the name contains 'x' in one div and displays those products where the name contains 'y' in another div.
I'm struggling to count how many products are going to be in each div before I do the loop.
So essentially, what I'm asking is:
How do you count all elements in an array that satisfy a certain condition?
Added Code which shows the loop:
<div id="a">
<?php
$i = 1;
foreach ($products AS $product) {
if (strpos($product->name,'X') !== false) {
=$product->name
}
$i++;
} ?>
</div>
<div id="b">
$i = 1;
foreach ($products AS $product) {
if (strpos($product->name,'Y') !== false) {
=$product->name
}
$i++;
} ?>
</div>
I'd like to know how many of these are going to be in here before I actually do the loop.

Well, without seeing the code, so generally speaking, if you're going to split them anyway, you might as well do that up-front?
<?php
// getting all the results.
$products = $db->query('SELECT name FROM foo')->fetchAll();
$div1 = array_filter($products, function($product) {
// condition which makes a result belong to div1.
return substr('X', $product->name) !== false;
});
$div2 = array_filter($products, function($product) {
// condition which makes a result belong to div2.
return substr('Y', $product->name) !== false;
});
printf("%d elements in div1", count($div1));
printf("%d elements in div2", count($div2));
// then print the divs. No need for ifs here, because results are already filtered.
echo '<div id="a">' . PHP_EOL;
foreach( $div1 as $product ) {
echo $product->name;
}
echo '</div>';
echo '<div id="b">' . PHP_EOL;
foreach( $div2 as $product ) {
echo $product->name;
}
echo '</div>';
That being said: you should take notice of the comment which says "This is normally faster in SQL", because it is the more sane approach if you want to filter the values.
EDIT: Changed the name of the variables to adapt the variable names in the example code.

Use an array-filter: http://www.php.net/manual/en/function.array-filter.php
array array_filter ( array $input [, callable $callback = "" ] )
Iterates over each value in the input array passing them to the callback function. If the callback function returns true, the current value from input is returned into the result array. Array keys are preserved.
<?php
function odd($var)
{
// returns whether the input integer is odd
return($var & 1);
}
function even($var)
{
// returns whether the input integer is even
return(!($var & 1));
}
$array1 = array("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);
$array2 = array(6, 7, 8, 9, 10, 11, 12);
echo "Odd :\n";
print_r(array_filter($array1, "odd"));
echo "Even:\n";
print_r(array_filter($array2, "even"));
?>
But be aware, this is a loop though, and your the SQL query will be faster.

Related

How do I fix this "Warning: explode(): Empty delimiter" error message with WordPress categories?

I have the below function in functions.php of the WordPress theme I'm working with. It's supposed to exclude a designated post category (such as "Uncategorized") from being displayed on the front-end of a website. It does work for that purpose.
function the_category_filter($thelist,$separator=' ') {
// list the IDs of the categories to exclude
$exclude = array(1);
// create an empty array
$exclude2 = array();
// loop through the excluded IDs and get their actual names
foreach($exclude as $c) {
// store the names in the second array
$exclude2[] = get_cat_name($c);
}
// get the list of categories for the current post
$cats = explode($separator,$thelist);
// create another empty array
$newlist = array();
foreach($cats as $cat) {
// remove the tags from each category
$catname = trim(strip_tags($cat));
// check against the excluded categories
if(!in_array($catname,$exclude2))
// if not in that list, add to the new array
$newlist[] = $cat;
}
// return the new, shortened list
return implode($separator,$newlist);
}
// add the filter to 'the_category' tag
add_filter('the_category','the_category_filter', 10, 2);
However, in certain custom post types with different categories than the one that I'm selecting for in the above code, when I'm creating a new post, I get this error
Warning: explode(): Empty delimiter
in the Categories selection panel on the right-hand side of the post editor.
This line is the one it's saying is incorrect:
$cats = explode($separator,$thelist);
I've tried wrapping that part of the code in an if condition to check for the empty delimiter, like this:
if ( !empty( explode($separator,$thelist) ) ) {
$cats = explode($separator,$thelist);
// create another empty array
$newlist = array();
foreach($cats as $cat) {
// remove the tags from each category
$catname = trim(strip_tags($cat));
// check against the excluded categories
if(!in_array($catname,$exclude2))
// if not in that list, add to the new array
$newlist[] = $cat;
}
// return the new, shortened list
return implode($separator,$newlist);
}
But while that does make the error disappear from the Categories panel, what results is a bunch of blanks where the category names ought to appear.
What am I doing wrong?
With the answers given by Martin and aynber, I solved the problem with this code:
function the_category_filter($thelist,$separator=' ') {
// list the IDs of the categories to exclude
$exclude = array(1);
// create an empty array
$exclude2 = array();
// loop through the excluded IDs and get their actual names
foreach($exclude as $c) {
// store the names in the second array
$exclude2[] = get_cat_name($c);
}
// get the list of categories for the current post
if ( !empty( $separator ) && !empty( $thelist ) ) {
$cats = explode($separator,$thelist);
// create another empty array
$newlist = array();
foreach($cats as $cat) {
// remove the tags from each category
$catname = trim(strip_tags($cat));
// check against the excluded categories
if(!in_array($catname,$exclude2))
// if not in that list, add to the new array
$newlist[] = $cat;
}
// return the new, shortened list
return implode($separator,$newlist);
} else {
return $thelist;
}
}
$cats = explode($separator,$thelist);
The source value $separatoris not allowed to be an empty string (or equivilant), therefore you can either check the value before you run the explode or you can use a ternary operator within the function:
A
Ensure the given $seperator value is not empty;
if ( !empty($separator) && !empty($thelist) ) {
$cats = explode($separator, $thelist);
}
Example using A here:
$seperator = "";
$thelist = "1 2 3 4 5 6 7 8 9";
$cats = "failed";
if ( !empty($separator) && !empty($thelist) ) {
$cats = explode($separator, $thelist);
}
print_r($cats);
// output string "failed"
B
Set a default seperator value of your choice if it is empty;
if (!empty($thelist) ) {
$cats = explode( ($separator?:' '),$thelist);
}
Example using B here:
$seperator = "";
$thelist = "1 2 3 4 5 6 7 8 9";
$cats = "failed";
if (!empty($thelist) ) {
$cats = explode(($separator?:' '),$thelist);
}
print_r($cats);
// output array of numerical values.
Further to your question:
$catname = trim(strip_tags($cat));
The "Strip_tags" function is extremely problematic and can easily return empty strings if the HTML tags in the string do not line up, such as <p>something here. this can result in empty strings as you example. I can't say if this is what's happening for you but I would suggest you try and comment out this function and see if that improves the result.
I would suggest you can build yourself a more robust preg_replace() functionality to remove specific characters such as anything that is not alphanumeric, etc..

Nested Foreach() PHP

I need some assistance with my code logic:
The expected output of the following code is 1110, how to achieve that value?
<?php
$user_accepted_events = [1,2,3];
$all_events = [1,2,3,4];
//Nested foreach loop for testing if the user accepted an event
foreach ($all_events as $single_row) {
foreach ($user_accepted_events as $user_single_id) {
if ($single_row == $user_single_id) { //This prints expected value
print_r("1"); //User has accepted event
} else { //Here it logically print's 0 nine times
print_r("0"); //User has not accepted Event
}
}
}
//Expected Output is 1110
//Real Output is 100010001000
?>
Thanks.
As you have nested loops - it will compare each item against every nested item and most of them won't match - producing multiple results for each item.
Instead - use in_array() to check each item...
foreach ($all_events as $single_row) {
if ( in_array($single_row, $user_accepted_events)) {
print_r("1"); //User has accepted event
} else {
print_r("0"); //User has not accepted Event
}
}
You've got a logical error in your code: you are printing 1 or 0 for every permutation of both loops.
As there are 4 items in the outer loop and 3 in the inner, you are receiving 12 outputs.
Instead, keeping the same approach you have already adopted, you can capture whether the user has attended the event in a variable, and break if so.
Then once for each of the outer loops, output the result:
$user_accepted_events = [1,2,3];
$all_events = [1,2,3,4];
foreach ($all_events as $single_row) {
$hasAccepted = false;
foreach ($user_accepted_events as $user_single_id) {
if ($single_row == $user_single_id) {
$hasAccepted = true;
break;
}
}
print_r($hasAccepted ? 1 : 0);
}
Output:
1110

Get First And Last Data From conditioned foreach loop

Hello here i am with freaky problem,i wants the first data and last data from for-each loop. for that i have seen this answer. this would be really helpful but here my condition is really a little bit complex.
I have loop as following
<?php
$count = 0;
$length = count($myDataArray);
foreach ($myDataArray as $value) {
if($count >= 7)
{
//Some Data to Print
//this is first data for me
<tr >
<td><?=$myfinaldate?></td>
<td><?=$stockdata[1]?></td>
<td><?=$stockdata[2]?></td>
<td><?=$stockdata[3]?></td>
<td <?php if($count == 8)echo "style='background-color:#47ff77;'"; ?>><?=$stockdata[4]?></td>
<td><?=$stockdata[5]?></td>
<td><?php echo $mydate; ?></td>
</tr>
<?php
}
$count++;
}
Now How can i Get First And Last Data from loop ?
I imagine you could use your length attribute.
As you have the total of your array, just check
myDataArray[0] and myDataArray[$length-1] ?
To fetch first and last value of the array use the below function :
$array = $myDataArray;
$array_values = array_values($myDataArray);
// get the first value in the array
print $array_values[0]; // prints 'first item'
// get the last value in the array
print $array_values[count($array_values) - 1]; // prints 'last item'
You can use array_values to remove the keys of an array and replace them with indices. If you do this, you can access the specified fields directly. Like this you can check for your requirements on the array without looping over it, as shown in the if-conditions below:
$length = count($myDataArray);
$dataArrayValues = array_values($myDataArray);
$wantedFields = [];
if ($length >= 8) {
$wantedFields[] = $dataArrayValues[7];
if ($length > 8) {
$wantedFields[] = end($dataArrayValues);
}
}
Due to the conditions you will not print the 8th field twice in case it is the last field as well.
foreach ($wantedFields as $value) {
<tr>
... //Your previous code
</tr>
}

Nested looping with an associative array of arrays, getting no output (PHP)

Thanks in advance for looking.
I am trying to build some HTML using a foreach loop with a few layers of arrays.
Groups of data and groups of titles for that data are stored in sets of arrays.
In turn, those arrays of data are stored in an array ($titlegroups and datagroups).
The aim being to set up a nested loop, where each group of data and titles populate the relevant fields in some html.
Here is a full set of code (structure) of my attempt.
$a=1;
$b=2;
$c=3;
$d=4;
$titlesA=array('string1','string2');
$titlesB=array('string3','string4');
$dataA=array($a,$b);
$dataB=array($c,$d);
$titlegroups=array($titlesA,$titlesB);
$datagroups=array($dataA,$dataB);
$groups=array(array_combine($titlegroups, $datagroups));
$j=0;
foreach($groups as $titlesX => $dataX)
{
$j++;
echo'<div class="something">';
$i=0;
foreach(array_combine($titlesX, $dataX) as $title => $var)
{
$i++;
echo '
<li>'.$title.'</li><input name="'.$j.'x'.$i.'" value="'.$var.'" />
';
}
echo '</div>';
}
Checking it in ideone I get the error:
Warning: array_combine() expects parameter 1 to be array, integer
given in /home/0zw0mb/prog.php on line 26
Line 26 is:
foreach(array_combine($titlesX, $dataX) as $title => $var)
but $titlesX and $dataX should both be arrays?
If anyone can set me straight I'd appreciate it. Thanks.
You can't have arrays as keys, they are converted to strings. You logic required some changes and an additional sentinel variable ($idx), to tell when the code crosses one group to another.
array_merge was necessary due to the key requirements of arrays.
Check the below code:
$a=1;
$b=2;
$c=3;
$d=4;
$titlesA=array('string1','string2');
$titlesB=array('string3','string4');
$dataA=array($a,$b);
$dataB=array($c,$d);
$titlegroups=array_merge($titlesA,$titlesB);
$datagroups=array_merge($dataA,$dataB);
$items=array_combine($titlegroups, $datagroups);
$indexes = array(count($dataA), count($dataB));
$i = 0;
$j = 0;
$idx = $indexes[$j];
echo '<div class="something">';
foreach($items as $title => $var)
{
echo '
<li>'.$title.'</li><input name="'.$j.'x'.($i++).'" value="'.$var.'" />
';
$idx--;
if ($idx == 0) {
/* End of a group */
echo '</div>';
$idx = $indexes[++$j];
/* If there is another group, create new container */
if ($idx != null) {
echo '<div class="something">';
}
}
}

Accounting for missing array keys, within PHP foreach loop

I'm parsing a document for several different values, with PHP and Xpath. I'm throwing the results/matches of my Xpath queries into an array. So for example, I build my $prices array like this:
$prices = array();
$result = $xpath->query("//div[#class='the-price']");
foreach ($result as $object) {
$prices[] = $object->nodeValue; }
Once I have my array built, I loop through and throw the values into some HTML like this:
$i = 0;
foreach ($links as $link) {
echo <<<EOF
<div class="the-product">
<div class="the-name"><a title="{$names[$i]}" href="{$link}" target="blank">{$names[$i]}</a></div>
<br />
<div class="the-image"><a title="{$names[$i]}" href="{$link}" target="blank"><img src="{$images[$i]}" /></a></div>
<br />
<div class="the-current-price">Price is: <br> {$prices[$i]}</div>
</div>
EOF;
$i++; }
The problem is, some items in the original document that I'm parsing don't have a price, as in, they don't even contain <div class='the-price'>, so my Xpath isn't finding a value, and isn't inserting a value into the $prices array. I end up returning 20 products, and an array which contains only 17 keys/values, leading to Notice: Undefined offset errors all over the place.
So my question is, how can I account for items that are missing key values and throwing off my arrays? Can I insert dummy values into the array for these items? I've tried as many different solutions as I can think of. Mainly, IF statements within my foreach loops, but nothing seems to work.
Thank you
I suggest you look for an element inside your html which is always present in your "price"-loop. After you find this object you start looking for the "price" element, if there is none, you insert an empty string, etc. into your array.
Instead of directly looking for the the-price elements, look for the containing the-product. Loop on those, then do a subquery using those nodes as the starting context. That way you get all of the the-product nodes, plus the prices for those that have them.
e.g.
$products = array();
$products = $xpath->query("//div[#class='the-product']");
$found = 0 ;
foreach ($products as $product) {
$products[$found] = array();
$price = $xpath->query("//div[#class='the-price']", $product);
if ($price->length > 0) {
$products[$found] = $price->item(0)->nodeValue;
}
$found++;
}
If you don't want to show the products that don't have a price attached to them you could check if $prices[$i] is set first.
foreach($links AS $link){
if(isset($prices[$i])){
// echo content
}
}
Or if you wanted to fill it will dummy values you could say
$prices = array_merge($prices,
array_fill(count($prices), count($links)-count($prices),0));
And that would insert 0 as a dummy value for any remaining values. array_fill starts off by taking the first index of the array (so we start one after the amount of keys in $prices), then how many we need to fill, so we subtract how many are in $prices from how many are in $links, then we fill it with the dummy value 0.
Alternatively you could use the same logic in the first example and just apply that by saying:
echo isset($prices[$i]) ? $prices[$i] : '0';
Hard to understand the relation between $links and $prices with the code shown. Since you are building the $prices array without any relation to the $links array, I don't see how you would do this.
Is $links also built via xpath? If so, is 'the-price' div always nested within the DOM element used to populate $links?
If it is you could nest your xpath query to find the price within the query used to find the links and use a counter to match the two.
i.e.
$links_result = $xpath->query('path-to-link')
$i = 0
foreach ($links_result as $link_object) {
$links[$i] = $link_object->nodeValue;
// pass $link_object as context reference to xpath query looking for price
$price_result = $xpath->query('path-to-price-within-link-node', $link_object);
if (false !== $price_result) {
$prices[$i] = $price_result->nodeValue;
} else {
$prices[$i] = 0; // or whatever value you want to show to indicate that no price was available.
}
$i++;
}
Obviously, there could be additional handling in there to verify that only one price value exists per link node and so forth, but that is basic idea.

Categories