Related
I have following array:
array(174) {
[0]=>
string(5) "3.0.3"
[1]=>
string(5) "3.0.2"
[2]=>
string(5) "3.0.1"
[3]=>
string(5) "3.0.0"
[9]=>
string(5) "2.9.5"
[10]=>
string(5) "2.9.4"
[11]=>
string(5) "2.9.3"
[12]=>
string(5) "2.9.2"
[13]=>
string(5) "2.9.1"
[14]=>
string(5) "2.9.0"
[18]=>
string(6) "2.8.11"
[19]=>
string(6) "2.8.10"
[20]=>
string(5) "2.8.9"
}
I need to find the highest 3rd number for unique pair of first two numbers x.x. With this example the expected result must be:
3.0.3, 2.9.5, 2.8.11
This is what I tried:
foreach ($array as $key => $value) {
$test = substr($value, 0, 3);
$a = strtr($value, array('.' => '', ',' => ''));
$b = (int) $a;
$c = substr($b, 0, 2);
$new_array = array($c);
$result = array_unique($new_array);
print_object($result);
}
First you must group the versions by the major version. After that you just need to sort the resulted groups using the version_compare function in a descending order and to return the first element of each group:
$versions = array("3.0.3", "3.0.2", "3.0.1", "3.0.0", "2.9.5", "2.9.4",
"2.9.3", "2.9.2", "2.9.1", "2.9.0", "2.8.11", "2.8.10", "2.8.9"
);
$groupedVersions = array();
foreach ($versions as $version) {
preg_match('/^\d+\.\d+/', $version, $majorVersion);
if (!isset($groupedVersions[$majorVersion[0]])) {
$groupedVersions[$majorVersion[0]] = array($version);
} else {
$groupedVersions[$majorVersion[0]][] = $version;
}
}
$groupedVersions = array_map(function ($versions) {
usort($versions, 'version_compare');
return array_reverse($versions);
}, $groupedVersions);
$latestVersions = array_reduce($groupedVersions, function ($carry, $versions) {
$carry[] = $versions[0];
return $carry;
}, array());
echo '<pre>';
var_dump($latestVersions);
echo '</pre>';
The result would be:
array(3) {
[0]=>
string(5) "3.0.3"
[1]=>
string(5) "2.9.5"
[2]=>
string(6) "2.8.11"
}
When grouping array data in PHP, it is often most efficient to generate temporary keys based on the grouping criteria.
I'll use some version terminology based on the accepted answer from: What every digit means in software version (1.7.1.0, for example)?
In this case, the first two integers (Major & Minor release) are used (and they must remain separated by a delimiting character to avoid data collisions). As you iterate, check if the temporary key exists in the output collection. If not, add it. If so, check if the third integer (Maintenance release) is greater than the stored Maintenance release for that Major-Minor release group -- if so, replace the stored Major-Minor release group's value.
This is very concisely performed in my snippet below. If you need the output to be indexed, call array_values(), otherwise omit that step.
Code: (Demo)
$versions = [
"3.0.1", "3.0.3", "3.0.2", "3.0.0",
"2.9.4", "2.9.3", "2.9.2", "2.9.5", "2.9.1", "2.9.0",
"2.8.1", "2.8.11", "2.8.10"
];
foreach ($versions as $version) {
$ints = explode('.', $version, 3);
$tempKey = "{$ints[0]}.{$ints[1]}";
if (!isset($result[$tempKey]) || version_compare($version, $result[$tempKey], 'gt')) {
$result[$tempKey] = $version;
}
}
var_export(array_values($result));
Output:
array (
0 => '3.0.3',
1 => '2.9.5',
2 => '2.8.11',
)
Using version_compare() is an appropriate tool to compare versions, but it can be avoided if you wish to make a simple integer to integer comparison on the Maintenance release value. This will cost a little more memory because you will also need to store an array of grouped Maintenance release integers to make this comparison. I am recommending just comparing the full version strings instead.
You can use only explode() and compare last value for each major varsion, then to build associative array with major and minor version:
<?php
$arr = array(0 => "3.0.3", 1 => "3.0.2", 2 => "3.0.1", 3 => "3.0.0", 9 => "2.9.5", 10 => "2.9.4", 11 => "2.9.3", 12 => "2.9.2", 13 => "2.9.1", 14 => "2.9.0", 18 => "2.8.11", 19 => "2.8.10", 20 => "2.8.9");
$versions = array();
$final = array();
foreach ($arr as $version) {
$explode = explode('.', $version); // split all parts
$end = '';
$begin = '';
if (count($explode) > 0) {
$end = array_pop($explode); // removes the last element, and returns it
if (count($explode) > 0) {
$begin = implode('.', $explode); // glue the remaining pieces back together
}
}
if(!empty($versions[$begin])){
if($versions[$begin] < $end){
$versions[$begin] = $end;
}
}else{
$versions[$begin] = $end;
}
}
foreach($versions as $key=>$value){
$final[] = "$key.$value";
}
print_r($final);
?>
Output:
Array
(
[0] => 3.0.3
[1] => 2.9.5
[2] => 2.8.11
)
I have a plain string like this:
1 //here is a number defines phrase name
09/25/2013 //here is a date
<i>some text goes here</i> //and goes text
2
09/24/2013
text goes on and on
4 //as you can see, the numbers can skip another
09/23/2013
heya i'm a text
I need to create array from this but the numbers that defines phrases must give me the date of that line and return the text when i called it. Like,
$line[4][date] should give me "09/23/2013"
Is that possible, if possible can you explain me how to do this?
$stringExample = "1
09/25/2013
<i>some text goes here</i>
2
09/24/2013
text goes on and on
and on and on
4
09/23/2013
heya i'm a text";
$data = explode("\r\n\r\n", $stringExample);
$line = array();
foreach ($data as $block) {
list($id, $date, $text) = explode("\n", $block, 3);
$index = (int) $id;
$line[$index] = array();
$line[$index]["date"] = trim($date);
$line[$index]["text"] = trim($text);
}
var_dump($line) should output:
array(3) {
[1]=>
array(2) {
["date"]=>
string(10) "09/25/2013"
["text"]=>
string(26) "<i>some text goes here</i>"
}
[2]=>
array(2) {
["date"]=>
string(10) "09/24/2013"
["text"]=>
string(34) "text goes on and on
and on and on"
}
[4]=>
array(2) {
["date"]=>
string(10) "09/23/2013"
["text"]=>
string(15) "heya i'm a text"
}
}
You can test it here.
If the structure of the file is a coerent pattern of rows, with PHP you read each line and each 1st you have the number, each 2nd you have the date, each 3rd you have the text, each 4th you have empty, you reset the counter to 1 and go on like that, and put values in arrays, using even an external index, or the 1st line (number) as index.
try this:
EDIT: should now work for multi line text.
//convert the string into an array at newline.
$str_array = explode("\n", $str);
//remove empty lines.
foreach ($str_array as $key => $val) {
if ($val == "") {
unset($str_array[$key]);
}
}
$str_array = array_values($str_array);
$parsed_array = array();
$count = count($str_array);
$in_block = false;
$block_num = 0;
$date_pattern = "%[\d]{2}/[\d]{2}/[\d]{2,4}%";
for ($i = 0; $i < $count; $i++) {
//start the block at first numeric value.
if (isset($str_array[$i])
&& is_numeric(trim($str_array[$i]))) {
// make sure it's followed by a date value
if (isset($str_array[$i + 1])
&& preg_match($date_pattern, trim($str_array[$i + 1]))) {
//number, followed by date, block confirmed.
$in_block = true;
$block_num = $i;
$parsed_array[$str_array[$i]]["date"] = $str_array[$i + 1];
$parsed_array[$str_array[$i]]['text'] = "";
$i = $i + 2;
}
}
//once a block has been found, everything that
//is not "number followed by date" will be appended to the current block's text.
if ($in_block && isset($str_array[$i])) {
$parsed_array[$str_array[$block_num]]['text'].=$str_array[$i] . "\n";
}
}
var_dump($parsed_array);
how can I get everything from a repeated index and the other in an array? see:
$Produtos = Array( 'Guitarra' , // repeats
'Bateria' , // repeats
'Piano' ,
'Baixo' ,
'Guitarra' , // repeats
'Violão' ,
'Caixas de Som' ,
'Bateria' , // repeats
'Sanfona' );
Reviewed are the indices that are repeated, as I do to get what's between them?
I wish to return:`
Array
(
[0] => Array(
[0] => Piano
[1] => Baixo
[1] => Array(
[0] => Violão
[1] => Caixas de Som
[2] => Array(
[0] => Sanfona
) )
It can be solved like this:
<?php
<?php
$Produtos = Array( 'Guitarra' , // repeats
'Bateria' , // repeats
'Piano' ,
'Baixo' ,
'Guitarra' , // repeats
'Violão' ,
'Caixas de Som' ,
'Bateria' , // repeats
'Sanfona' );
$countedProducts = array_count_values($Produtos);
$c = 0;
foreach ($Produtos as $product)
{
if ($countedProducts[$product] > 1)
{
if (count($novosProdutos))
{
$c++;
}
}else{
$novosProdutos[$c][] = $product;
}
}
print '<pre>';
var_dump($novosProdutos);
print '</pre>';
?>
Output:
array(3) {
[0]=>
array(2) {
[0]=>
string(5) "Piano"
[1]=>
string(5) "Baixo"
}
[1]=>
array(2) {
[0]=>
string(7) "Violão"
[1]=>
string(13) "Caixas de Som"
}
[2]=>
array(1) {
[0]=>
string(7) "Sanfona"
}
}
I have understood in the meantime, what you really wanted to have as an result. I changed it now, and it work also with multiple repeations and starts always from zero.
$finalProducts = array();
$currentKey = 0;
$wordCount = array_count_values($Produtos);
foreach($Produtos as $val) {
if($wordCount[$val] > 1) {
$currentKey++;
}
elseif(strlen($currentKey) > 0) {
$finalProducts[$currentKey][] = $val;
}
}
$finalProducts = array_values($finalProducts);
<?php
function array_between_duplicates($ary)
{
// first, tally up all the values
// we need to know how many times each value repeats
$count = array_count_values($ary);
// next, we want only the values that are not repeated.
// This can be done by filtering the array for values
// present 2+ times
$between = array_filter($count, create_function('$a','return $a==1;'));
// now that we have the unique values, swap the keys
// and value locations using array_keys
$swap = array_keys($between);
// and then intersect the new array with the original
// array so we can get back their original key values.
$intersect = array_intersect($ary, $swap);
var_dump($intersect);
// now, in order to get the nested groups we will use
// skipped keys as a sign that the in-between values
// were repeats. So, iterate over the array and break
// out these groups
$result = array(); $group = array();
foreach ($ary as $key => $value)
{
if (!array_key_exists($key, $intersect) && count($group) > 0)
{
$result[] = $group;
$group = array();
}
if (array_search($value,$intersect) !== false)
$group[] = $value;
}
if (count($group) > 0)
$result[] = $group;
// return the result
return $result;
}
var_dump(array_between_duplicates($Produtos));
Results in:
array(3) {
[0]=>
array(2) {
[0]=>
string(5) "Piano"
[1]=>
string(5) "Baixo"
}
[1]=>
array(2) {
[0]=>
string(7) "Violão"
[1]=>
string(13) "Caixas de Som"
}
[2]=>
array(1) {
[0]=>
string(7) "Sanfona"
}
}
DEMO
I am attempting a basic recursion to create multi-dimensional arrays based on the values of an inputed array.
The recursion works by checking for a value we shall call it "recursion" to start the loop and looks for another value we'll call it "stop_recursion" to end.
Basically taking this array
array('One', 'Two', 'recursion', 'Three', 'Four', 'Five', 'stop_recursion', 'Six', 'Seven')
And making this array
array('One', 'Two', array('Three', 'Four', 'Five'), 'Six', 'Seven')
The code I have for it so far is as follows
function testRecrusion($array, $child = false)
{
$return = array();
foreach ($array as $key => $value) {
if ($value == 'recursion') {
unset($array[$key]);
$new = testRecrusion($array, true);
$array = $new['array'];
$return[] = $new['return'];
} else {
if ($value == 'stop_recursion') {
unset($array[$key]);
if ($child) {
return array('return' => $return, 'array' => $array);
}
} else {
unset($array[$key]);
$return[] = $value;
}
}
}
return $return;
}
But the output from that is
Array
(
[0] => One
[1] => Two
[2] => Array
(
[0] => Three
[1] => Four
[2] => Five
)
[3] => Three
[4] => Four
[5] => Five
[6] => Six
[7] => Seven
)
I guess the real question is...will an array values continuously loop through the first values given from the initial call or once the new array is returned and set will it loop through that new array. I know the answer is basically right here saying that yes it will continue the old array value, but shouldn't this work vice-versa?
Any help will be appreciated :)
------------ edit -------------------
I might as well add that while I can perform this action using a much simpler method, this needs to be recursively checked since this will be ported to a string parser that could have a infinite number of child arrays.
When you return inside the recursion, you need to return both the inner array and the index from which to continue searching for elements so that you don't look at the same element twice. Try this instead:
function testRecursionImpl($array, $i)
{
$return = array();
for (; $i < sizeof($array); ++$i) {
if ($array[$i] == 'recursion') {
$new = testRecursionImpl($array, $i + 1);
$return[] = $new[0];
$i = $new[1];
} else if ($array[$i] == 'stop_recursion') {
return array($return, $i);
} else {
$return[] = $array[$i];
}
}
return array($return, $i);
}
function testRecursion($array)
{
$result = testRecursionImpl($array, 0);
return $result[0];
}
The problem you have in the code above is that you correctly detect when you should call this function recursively but once it finishes running and you append the results to output array you just pick next element (which is the first element that recursive call will get) and append it to output array. What you probably want to do is when you detect that you should run your function recursively you should skip all other characters until you find your stop word (stop_recursion). Obviously the problem will become harder if you allow multi-level recursion then you may need to even skip some stopwords because they could be from the different level call.
Still I don't know why you want such a feature. Maybe you would explain what are you trying to achieve. I'm pretty sure there's another, simpler way of doing it.
Rather than helping with your homework, I would suggest you start with getting rid of this line:
foreach ($array as $key => $value) {
You should just pass in your array, and check for being at the end of the array, since it can't really be infinite, to know when you are done.
Let me give it a try
function testRecursion($arr){
$return = array();
$recurlevel = 0;
foreach ($arr as $v) {
if($v == 'stop_recursion'){
$recurlevel--;
}
if($recurlevel == 0){
if(isset($current)){
$return[] = testRecursion($current);
unset($current);
}else{
if($v != 'recursion'){
$return[] = $v;
}
}
}else{
if(!isset($current)){
$current = array();
}
$current[] = $v;
}
if($v == 'recursion'){
$recurlevel++;
}
}
return $return;
}
Alright nicely done. This will help even if the recursion and stop_recursion are nested in another. See example:
code.php:
<pre><?php
function testRecursion($arr){
$return = array();
$recurlevel = 0;
foreach ($arr as $v) {
if($v == 'stop_recursion'){
$recurlevel--;
}
if($recurlevel == 0){
if(isset($current)){
$return[] = testRecursion($current);
unset($current);
}else{
if($v != 'recursion'){
$return[] = $v;
}
}
}else{
if(!isset($current)){
$current = array();
}
$current[] = $v;
}
if($v == 'recursion'){
$recurlevel++;
}
}
return $return;
}
$a = array('One', 'Two', 'recursion', 'Three', 'recursion', 'Four' , 'stop_recursion', 'Five', 'stop_recursion', 'Six', 'Seven');
var_dump(testRecursion($a));
?>
Browser output:
array(5) {
[0]=>
string(3) "One"
[1]=>
string(3) "Two"
[2]=>
array(3) {
[0]=>
string(5) "Three"
[1]=>
array(1) {
[0]=>
string(4) "Four"
}
[2]=>
string(4) "Five"
}
[3]=>
string(3) "Six"
[4]=>
string(5) "Seven"
}
Since your question for the recursive solution has already been answered ...might I offer a stack-based solution?
$x = array('a', 'b', 'recursion', 'cI', 'cII', 'cIII', 'recursion', 'cIV1', 'cIV2', 'cIV2', 'stop_recursion', 'stop_recursion', 'd', 'e');
$result = array();
$stack = array(&$result);
foreach($x as $e) {
if ( 'recursion'===$e ) {
array_unshift($stack, array());
$stack[1][] = &$stack[0];
}
else if ( 'stop_recursion'===$e ) {
array_shift($stack);
}
else {
$stack[0][] = $e;
}
}
var_dump($result);
prints
array(5) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
array(4) {
[0]=>
string(2) "cI"
[1]=>
string(3) "cII"
[2]=>
string(4) "cIII"
[3]=>
array(3) {
[0]=>
string(4) "cIV1"
[1]=>
string(4) "cIV2"
[2]=>
string(4) "cIV2"
}
}
[3]=>
string(3) "d"
[4]=>
string(5) "e"
}
PHP's explode function returns an array of strings split on some provided substring. It will return empty strings when there are leading, trailing, or consecutive delimiters, like this:
var_dump(explode('/', '1/2//3/'));
array(5) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(0) ""
[3]=>
string(1) "3"
[4]=>
string(0) ""
}
Is there some different function or option or anything that would return everything except the empty strings?
var_dump(different_explode('/', '1/2//3/'));
array(3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
Try preg_split.
$exploded = preg_split('#/#', '1/2//3/', -1, PREG_SPLIT_NO_EMPTY);
array_filter will remove the blank fields, here is an example without the filter:
print_r(explode('/', '1/2//3/'))
prints:
Array
(
[0] => 1
[1] => 2
[2] =>
[3] => 3
[4] =>
)
With the filter:
php> print_r(array_filter(explode('/', '1/2//3/')))
Prints:
Array
(
[0] => 1
[1] => 2
[3] => 3
)
You'll get all values that resolve to "false" filtered out.
see http://uk.php.net/manual/en/function.array-filter.php
Just for variety:
array_diff(explode('/', '1/2//3/'), array(''))
This also works, but does mess up the array indexes unlike preg_split. Some people might like it better than having to declare a callback function to use array_filter.
function not_empty_string($s) {
return $s !== "";
}
array_filter(explode('/', '1/2//3/'), 'not_empty_string');
I have used this in TYPO3, look at the $onlyNonEmptyValues parameter:
function trimExplode($delim, $string, $onlyNonEmptyValues=0){
$temp = explode($delim,$string);
$newtemp=array();
while(list($key,$val)=each($temp)) {
if (!$onlyNonEmptyValues || strcmp("",trim($val))) {
$newtemp[]=trim($val);
}
}
reset($newtemp);
return $newtemp;
}
It doesn't mess up the indexes:
var_dump(trimExplode('/', '1/2//3/',1));
Result:
array(3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[2]=>
string(1) "3"
}
Here is a solution that should output a newly indexed array.
$result = array_deflate( explode( $delim, $array) );
function array_deflate( $arr, $emptyval='' ){
$ret=[];
for($i=0,$L=count($arr); $i<$L; ++$i)
if($arr[$i] !== $emptyval) $ret[]=$arr[$i];
return $ret;
}
While fairly similar to some other suggestion, this implementation has the benefit of generic use. For arrays with non-string elements, provide a typed empty value as the second argument.
array_deflate( $objArray, new stdClass() );
array_deflate( $databaseArray, NULL );
array_deflate( $intArray, NULL );
array_deflate( $arrayArray, [] );
array_deflate( $assocArrayArray, [''=>NULL] );
array_deflate( $processedArray, new Exception('processing error') );
.
.
.
With an optional filter argument..
function array_deflate( $arr, $trigger='', $filter=NULL, $compare=NULL){
$ret=[];
if ($filter === NULL) $filter = function($el) { return $el; };
if ($compare === NULL) $compare = function($a,$b) { return $a===$b; };
for($i=0,$L=count($arr); $i<$L; ++$i)
if( !$compare(arr[$i],$trigger) ) $ret[]=$arr[$i];
else $filter($arr[$i]);
return $ret;
}
With usage..
function targetHandler($t){ /* .... */ }
array_deflate( $haystack, $needle, targetHandler );
Turning array_deflate into a way of processing choice elements and removing them from your array. Also nicer is to turn the if statement into a comparison function that is also passed as an argument in case you get fancy.
array_inflate being the reverse, would take an extra array as the first parameter which matches are pushed to while non-matches are filtered.
function array_inflate($dest,$src,$trigger='', $filter=NULL, $compare=NULL){
if ($filter === NULL) $filter = function($el) { return $el; };
if ($compare === NULL) $compare = function($a,$b) { return $a===$b; };
for($i=0,$L=count($src); $i<$L; ++$i)
if( $compare(src[$i],$trigger) ) $dest[]=$src[$i];
else $filter($src[$i]);
return $dest;
}
With usage..
$smartppl=[];
$smartppl=array_inflate( $smartppl,
$allppl,
(object)['intelligence'=>110],
cureStupid,
isSmart);
function isSmart($a,$threshold){
if( isset($a->intellgence) ) //has intelligence?
if( isset($threshold->intellgence) ) //has intelligence?
if( $a->intelligence >= $threshold->intelligence )
return true;
else return INVALID_THRESHOLD; //error
else return INVALID_TARGET; //error
return false;
}
function cureStupid($person){
$dangerous_chemical = selectNeurosteroid();
applyNeurosteroid($person, $dangerous_chemical);
if( isSmart($person,(object)['intelligence'=>110]) )
return $person;
else
lobotomize($person);
return $person;
}
Thus providing an ideal algorithm for the world's educational problems. Aaand I'll stop there before I tweak this into something else..
Write a wrapper function to strip them
function MyExplode($sep, $str)
{
$arr = explode($sep, $str);
foreach($arr as $item)
if(item != "")
$out[] = $item;
return $out;
}
Use this function to filter the output of the explode function
function filter_empty(&$arrayvar) {
$newarray = array();
foreach ($arrayvar as $k => $value)
if ($value !== "")
$newarray[$k] = $value;
$arrayvar = $newarray;
}
Regular expression solutions tend to be much slower than basic text replacement, so i'd replace double seperators with single seperators, trim the string of any whitespace and then use explode:
// assuming $source = '1/2//3/';
$source = str_replace('//', '/', $source);
$source = trim($source);
$parts = explode('/', $source);
No regex overhead - should be reasonably efficient, strlen just counts the bytes
Drop the array_values() if you don't care about indexes
Make it into function explode_interesting( $array, $fix_index = 0 ) if you want
$interesting = array_values(
array_filter(
explode('/', '/1//2//3///4/0/false' ),
function ($val) { return strlen($val); }
));
echo "<pre>", var_export( $interesting, true ), "</pre>";
enjoy, Jeff
PHP's split function is similar to the explode function, except that it allows you to enter a regex pattern as the delimiter. Something to the effect of:
$exploded_arr = split('/\/+/', '1/2//3/');
I usually wrap it in a call to array_filter, e.g.
var_dump(array_filter(explode('/', '1/2//3/'))
=>
array(3) {
[0]=>
string(1) "1"
[1]=>
string(1) "2"
[3]=>
string(1) "3"
}
Be aware, of course, that array keys are maintained; if you don't want this behaviour, remember to add an outer wrapper call to array_values().