I'm posting this question & answer because I've searched SO and haven't found a satisfactory answer for this problem and I hope this question & answer will help others in the future. Feel free to edit or add more different solutions to the ones I've included in my answer.
This question is for one-dimensional arrays only.
So let's say that I have this php array of strings:
$stringsArr = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet'];
And I want to know how many total characters are in that array.
I could loop through the array and run a strlen on each element like this:
$stringsTotalLength = 0;
foreach ($stringsArr as $string) {
$stringsTotalLength += strlen($string);
}
echo $stringsTotalLength; // Returns 22 correctly.
But I was wondering if there was any built-in php function or simple one-liner that could do this more elegantly.
So there are a bunch of different ways to accomplish this, some more elegant, some less so. (Also, benchmarks on these solutions are welcome).
In 1st place, the winner is a combination of strlen and implode:
$stringsTotalLength = strlen(implode($stringsArr));
This works by concatenating all of the elements of the array and getting the length of that string, e.g. ['Lorem', 'ipsum'] -> 'Loremipsum' -> 10.
And in a close 2nd, there's a combination of array_sum, array_map, and strlen:
$stringsTotalLength = array_sum(array_map('strlen', $stringsArr));
This replaces the elements of the array with their lengths, and then gets the sum of the whole array, e.g. ['Lorem', 'ipsum'] -> [5, 5] -> 10.
In 3rd place is the plain old foreach loop, though, while it is very simple, it is also kind of verbose:
$stringsTotalLength = 0;
foreach ($stringsArr as $string) {
$stringsTotalLength += strlen($string);
}
Finally, in last place is a solution even worse than a foreach loop (IMHO), array_map:
$stringsTotalLength = 0;
array_map(function ($string) {
global $stringsTotalLength;
$stringsTotalLength += strlen($string);
}, $stringsArr);
use implode to change the array to string and count the length using strlen
<?php
$stringsArr = ['Lorem', 'ipsum', 'dolor', 'sit', 'amet'];
echo strlen(implode($stringsArr)); //22
?>
Related
I have an array containing links. I am trying to cut a part of those links. For example:
$array = [
"https://eksisozluk.com/merve-sanayin-pizzaciya-kapiyi-ciplak-acmasi--5868043?a=popular",
"https://eksisozluk.com/merve-sanayin-pizzaciya-kapiyi-ciplak-acmasi--5868043?a=popular"
];
I want change these links like below:
array(2) {
[0]=>
string(91) "https://eksisozluk.com/merve-sanayin"
[1]=>
string(91) "https://eksisozluk.com/merve-sanayin"
[2]=>
}
Is there any possible way to edit array items?
Given the array:
$array = [
"https://eksisozluk.com/merve-sanayin-pizzaciya-kapiyi-ciplak-acmasi--5868043?a=popular",
"https://eksisozluk.com/merve-sanayin-pizzaciya-kapiyi-ciplak-acmasi--5868043?a=popular"
];
Using array_walk() (modifies the array in place).
Using a regular expression this time:
function filter_url(&$item)
{
preg_match('|(https:\/\/\w+\.\w{2,4}\/\w+-\w+)-.+|', $item, $matches);
$item = $matches[1];
}
array_walk($array, 'filter_url');
(See it working here).
Note that filter_url passes the first parameter by reference, as explained in the documentation, so changes to each of the array items are performed in place and affect the original array.
Using array_map() (returns a modified array)
Simply using substr, since we know next to nothing about your actual requirements:
function clean_url($item)
{
return substr($item, 0, 36);
}
$new_array = array_map('clean_url', $array);
Working here.
The specifics of how actually filter the array elements are up to you.
The example shown here seems kinda pointless, since you are setting all elements exactly to the same value. If you know the lenght you can use substr, or you could just could write a more robust regex.
Since all the elements of your input array are the same in the example, I am going to assume this doesn't represent your actual input.
You could also iterate the array using either for, foreach or while, but either of those options seems less elegant when you have specific array functions to deal with this kind of situation.
There are multiple ways. One way is to iterate over the items and clip them with substr().
$arr = array("https://eksisozluk.com/merve-sanayin-pizzaciya-kapiyi-ciplak-acmasi--5868043?a=popular",
"https://eksisozluk.com/merve-sanayin-pizzaciya-kapiyi-ciplak-acmasi--5868043?a=popular");
for ($i = 0; $i < count($arr); $i++)
{
$arr[$i] = substr($arr[$i], 0, 36);
}
What's an efficient way to pop the last n elements in an array?
Here's one:
$arr = range(1,10);
$n = 2;
$popped_array = array();
for ($i=0; $i < $n; $i++) {
$popped_array[] = array_pop($arr);
}
print_r($popped_array); // returns array(10,9);
Is there a more efficient way?
Use array_splice():
If you're trying to remove the last n elements, use the following function:
function array_pop_n(array $arr, $n) {
return array_splice($arr, 0, -$n);
}
Demo
If you want to retrieve only the last n elements, then you can use the following function:
function array_pop_n(array $arr, $n) {
array_splice($arr,0,-$n);
return $arr;
}
Demo
It's important to note, looking at the other answers, that array_slice will leave the original array alone, so it will still contain the elements at the end, and array_splice will mutate the original array, removing the elements at the beginning (though in the example given, the function creates a copy, so the original array still would contain all elements). If you want something that literally mimics array_pop (and you don't require the order to be reversed, as it is in your OP), then do the following.
$arr = range(1, 10);
$n = 2;
$popped_array = array_slice($arr, -$n);
$arr = array_slice($arr, 0, -$n);
print_r($popped_array); // returns array(9,10);
print_r($arr); // returns array(1,2,3,4,5,6,7,8);
If you require $popped_array to be reversed, array_reverse it, or just pop it like your original example, it's efficient enough as is and much more direct.
Why not use array_slice. You can give a start and a length, so if you do 2 from the end you will get the last two items in the array:
$arr = range(1,10);
$n = 2;
$start = count($arr) - $n;
print_r(array_slice($arr, $start, $n));
Thanks for the array_slice comments. I don't know why that didn't immediately come to mind.
It looks (to me) like the easiest way is:
$arr = range(1,10);
$n = 2;
$popped_array = array_slice($arr,-$n);
print_r($popped_array); // returns array(10,9);
I have an array of 17,000 strings. Many of the strings have similar matches, for example:
User Report XYZ123
Bob Smith
User Report YEI723
User Report
User Report
Number of Hits 27
Frank's Weekly Transaction Report
Transaction Report 123
What is the best way to find the top "similar strings"? For instance, using the example above, I would want to see "User Report" and "Transaction Report" as two of the top "similar strings".
Without giving you all the source code to do this, you could go through the array and remove components you consider useless, like any letters with numbers, and so on.
Then you can use array_count_values() and sort that array to see the top ones involved.
I guess you could do a foreach through each of the strings and eliminate the ones that you don't want for that particular search. Then go through the once you have left (possibly with another foreach) and keep shrinking the number of strings that you have an interest in down until there are just a few. Then sort those by something like alphabetical order.
You could compute the Levenstein distance for each string compared with others and then sort them by that value.
$strings = array('str1', 'str2', 'car', 'dog', 'apple', 'house', 'str3');
$len = count($strings);
$distances = array_fill(0, $len, 0);
for($i=0; $i<$len-1; ++$i)
for($j=$i+1; $j<$len; ++$j)
{
$dist = levenshtein($strings[$i], $strings[$j]);
$distances[$i] += $dist;
$distances[$j] += $dist;
}
// Here $distances indicates how of "similar" is each string
// The lower values are more "similar"
If you are able to get all the strings as an array and loop them in a foreach() like this:
$string_array = array('string', 'string1', 'string2', 'does-not-match');
$needle = 'string';
$results = array();
foreach($string_array as $key => $val):
if (fnmatch($needle, $val):
$results[] = $val;
endif;
endforeach;
in the end you should end having the entries that match $needle. As alternative to fnmatch() you could use preg_match() and as pattern /string/i
$string_array = array('string', 'string1', 'string2', 'does-not-match');
$needle = '/string/i';
$results = array();
foreach($string_array as $key => $val):
if (!empty(preg_match($needle, $val)):
$results[] = $val;
endif;
endforeach;
Note there could be issues when using empty() and pass the result of preg_match().:
Prior to PHP 5.5, empty() only supports variables; anything else will result in a parse error. In other words, the following will not work: empty(trim($name)). Instead, use trim($name) == false.
No errors should be issued with PHP version 5.3.x < 5.4
This is fairly confusing, but I'll try to explain as best I can...
I've got a MYSQL table full of strings like this:
{3}12{2}3{5}52
{3}7{2}44
{3}15{2}2{4}132{5}52{6}22
{3}15{2}3{4}168{5}52
Each string is a combination of product options and option values. The numbers inside the { } are the option, for example {3} = Color. The number immediately following each { } number is that option's value, for example 12 = Blue. I've already got the PHP code that knows how to parse these strings and deliver the information correctly, with one exception: For reasons that are probably too convoluted to get into here, the order of the options needs to be 3,4,2,5,6. (To try to modify the rest of the system to accept the current order would be too monumental a task.) It's fine if a particular combination doesn't have all five options, for instance "{3}7{2}44" delivers the expected result. The problem is just with combinations that include option 2 AND option 4-- their order needs to be switched so that any combination that includes both options 2 and 4, the {4} and its corresponding value comes before the {2} and it's corresponding value.
I've tried bringing the column into Excel and using Text to Columns, splitting them up by the "{" and "}" characters and re-ordering the columns, but since not every string yields the same number of columns, the order gets messed up in other ways (like option 5 coming before option 2).
I've also experimented with using PHP to explode each string into an array (which I thought I could then re-sort) using "}" as the delimiter, but I had no luck with that either because then the numbers blend together in other ways that make them unusable.
TL;DR: I have a bunch of strings like the ones quoted above. In every string that contains both a "{2}" and a "{4}", the placement of both of those values needs to be switched, so that the {4} and the number that follows it comes before the {2} and the number that follows it. In other words:
{3}15{2}3{4}168{5}52
needs to become
{3}15{4}168{2}3{5}52
The closest I've been able to come to a solution, in pseudocode, would be something like:
for each string,
if "{4}" is present in this string AND "{2}" is present in this string,
take the "{4}" and every digit that follows it UNTIL you hit another "{" and store that substring as a variable, then remove it from the string.
then, insert that substring back into the string, at a position starting immediately before the "{2}".
I hope that makes some kind of sense...
Is there any way with PHP, Excel, Notepad++, regular expressions, etc., that I can do this? Any help would be insanely appreciated.
EDITED TO ADD: After several people posted solutions, which I tried, I realized that it would be crucial to mention that my host is running PHP 5.2.17, which doesn't seem to allow for usort with custom sorting. If I could upvote everyone's solution (all of which I tried in PHP Sandbox and all of which worked), I would, but my rep is too low.
How would something like this work for you. The first 9 lines just transform your string into an array with each element being an array of the option number and value. The Order establishes an order for the items to appear in and the last does a usort utilizing the order array for positions.
$str = "{3}15{2}2{4}132{5}52{6}22";
$matches = array();
preg_match_all('/\{([0-9]+)\}([0-9]+)/', $str, $matches);
array_shift($matches);
$options = array();
for($x = 0; $x < count($matches[0]); $x++){
$options[] = array($matches[0][$x], $matches[1][$x]);
}
$order = [3,4,2,5,6];
usort($options, function($a, $b) use ($order) {
return array_search($a[0], $order) - array_search($b[0], $order);
});
To get you data back into the required format you would just
$str = "";
foreach($options as $opt){
$str.="{".$opt[0]."}".$opt[1];
}
On of the bonuses here is that when you add a new options type inserting adjusting the order is just a matter of inserting the option number in the correct position of the $order array.
First of all, those options should probably be in a separate table. You're breaking all kinds of normalization rules stuffing those things into a string like that.
But if you really want to parse that out in php, split the string into a key=>value array with something like this:
$options = [];
$pairs = explode('{', $option_string);
foreach($pairs as $pair) {
list($key,$value) = explode('}', $pair);
$options[$key] = $value;
}
I think this will give you:
$options[3]=15;
$options[2]=3;
$options[4]=168;
$options[5]=52;
Another option would be to use some sort of existing serialization (either serialize() or json_encode() in php) instead of rolling your own:
$options_string = json_encode($options);
// store $options_string in db
then
// get $options_string from db
$options = json_decode($options_string);
Here's a neat solution:
$order = array(3, 4, 2, 5, 6);
$string = '{3}15{2}3{4}168{5}52';
$split = preg_split('#\b(?={)#', $string);
usort($split, function($a, $b) use ($order) {
$a = array_search(preg_replace('#^{(\d+)}\d+$#', '$1', $a), $order);
$b = array_search(preg_replace('#^{(\d+)}\d+$#', '$1', $b), $order);
return $a - $b;
});
$split = implode('', $split);
var_dump($split);
I have an array that reflects rebate percentages depending on the number of items ordered:
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35)
meaning that for one or two items, you get no rebate; for 3+ items you get 10%, for 5+ items 20%, for 10+ 35% and so on.
Is there an elegant, one-line way to get the correct rebate percentage for an arbitrary number of items, say 7?
Obviously, this can be solved using a simple loop: That's not what I'm looking for. I'm interested whether there is a core array or other function that I don't know of that can do this more elegantly.
I'm going to award the accepted answer a bounty of 200, but apparently, I have to wait 24 hours until I can do that. The question is solved.
Here's another one, again not short at all.
$percent = $rebates[max(array_intersect(array_keys($rebates),range(0,$items)))];
The idea is basically to get the highest key (max) which is somewhere between 0 and $items.
I think that the above one-line solutions aren't really elegant or readable. So why not use something that can really be understood by someone on first glance?
$items = NUM_OF_ITEMS;
$rabate = 0;
foreach ($rabates as $rItems => $rRabate) {
if ($rItems > $items) break;
$rabate = $rRabate;
}
This obviously needs a sorted array, but at least in your example this is given ;)
Okay, I know, you don't want the solution with the simple loop. But what about this:
while (!isset($rabates[$items])) {
--$items;
}
$rabate = $rabates[$items];
Still quite straightforward, but a little shorter. Can we do even shorter?
for (; !isset($rabates[$items]); --$items);
$rabate = $rabates[$items];
We are already getting near one line. So let's do a little bit of cheating:
for (; !isset($rabates[$items]) || 0 > $rabate = $rabates[$items]; --$items);
This is shorter then all of the approaches in other answers. It has only one disadvantage: It changes the value of $items which you may still need later on. So we could do:
for ($i = $items; !isset($rabates[$i]) || 0 > $rabate = $rabates[$i]; --$i);
That's again one character less and we keep $items.
Though I think that the last two versions are already too hacky. Better stick with this one, as it is both short and understandable:
for ($i = $items; !isset($rabates[$i]); --$i);
$rabate = $rabates[$i];
This might work without changing the rebate array.
But the array must be constructed in another way for this to work
$rebates = array(
3 => 0, //Every number below this will get this rebate
5 => 10,
10 => 25,
1000 => 35); //Arbitrary large numer to catch all
$count = $_REQUEST["count"];
$rv = $rebates[array_shift(array_filter(array_keys($rebates), function ($v) {global $count; return $v > $count;}))];
echo $rv;
Working testcase, just change count in url
http://empirium.dnet.nu/arraytest.php?count=5
http://empirium.dnet.nu/arraytest.php?count=10
Best I can manage so far:
$testValue = 7;
array_walk( $rebates, function($value, $key, &$test) { if ($key > $test[0]) unset($test[1][$key]); } array($testValue,&$rebates) );
Uses a nasty little quirk of passing by reference, and strips off any entry in the $rebates array where the key is numerically greater than $testValue... unfortunately, it still leaves lower-keyed entries, so an array_pop() would be needed to get the correct value. Note that it actively reduces the entries in the original $rebates array.
Perhaps somebody can build on this to discard the lower entries in the array.
Don't have 5.3.3 available to hand at the moment, so not tested using an anonymous function, but works (as much as it works) when using a standard callback function.
EDIT
Building on my previous one-liner, adding a second line (so probably shouldn't count):
$testValue = 7;
array_walk( $rebates, function($value, $key, &$test) { if ($key > $test[0]) unset($test[1][$key]); } array($testValue,&$rebates) );
array_walk( array_reverse($rebates,true), function($value, $key, &$test) { if ($key < $test[0]) unset($test[1][$key]); } array(array_pop(array_keys($rebates)),&$rebates) );
Now results in the $rebates array containing only a single element, being the highest break point key from the original $rebates array that is a lower key than $testValue.