I have the following input (only for example, real input contains much more crazy data)
$values = [
'32/34, 36/38, 40/42, 44/46',
'40/42/44/46/48',
'58/60',
'39-42',
'40-50-60',
'24-25,26,28,30',
'36 40,5 44',
];
and want to split it by separators like / or , but keep pairs of values. This should be done only, if separator does not occur multiple times, so the result should look like:
'32/34, 36/38, 40/42, 44/46'
=> [ '32/34', '36/38', '40/42', '44/46' ]
'40/42/44/46/48'
=> [ '40', '42', '44', '46', '48' ]
'58/60'
=> [ '58/60' ]
'39-42'
=> [ '39-42' ]
'40-50-60'
=> [ '40', '50', '60' ]
'24-25,26,28,30'
=> [ '24-25', '26', '28', '30' ]
'36 40,5 44'
=> [ '36', '40,5', '44' ]
What I have so far is
$separator = '^|$|[\s,\/-]';
$decimals = '\d+(?:[,.][05])?';
foreach ($values as $value) {
preg_match_all('/' .
'(?<=' . $separator . ')' .
'(?:' .
'(?P<var1>(' . $decimals . ')[\/-](?-1)|(?-1))' .
')(?=' . $separator . ')' .
'/ui', $value, $matches);
print_r($matches);
}
But this fails for 40/42/44/46/48 which returns
[var1] => Array
(
[0] => 40/42
[1] => 44/46
[2] => 48
)
But each number should be returned separately. Modifying regex to '(?P<var1>(' . $decimals . ')([\/-])(?-2)|(?-2))(?!\3)' is better, but still returns wrong result
[var1] => Array
(
[0] => 40
[1] => 42
[2] => 44
[3] => 46/48
)
How should the correct regex look like?
As stated in comments above, I know that a 100% match is not possible, because of user input. But I've found a regex which fits most of my use cases:
(?<=^|$|[\s,\/-])(?:(?P<var1>(?<![\/-])(?!(?:(\d+(?:[,.][05])?)[\/-]){2}(?-1))(\d+(?:[,.][05])?)[\/-](?-1)|(?-1)))(?=^|$|[\s,\/-])
See https://regex101.com/r/q3YSa7/1
Related
I am writing a script that loops through a multidimensional array and it's working as hoped (sort of) but I get errors that I just can't remedy.
I am still not that comfortable building loops to manage nested arrays.
Here is my code. The goal is to sort each layer by the value of the sequence key and in the end I export the array as json.
The sequence key may or may not exist in every sub array so that may need some sort of if clause
<?php
$list = [
"key" => "book",
"sequence" => 1,
"items" => [
[
"key" => "verse",
"sequence" => 2,
"items" => [
["sequence" => 3],
["sequence" => 1],
["sequence" => 2],
],
],
[
"key" => "page",
"sequence" => 1,
"items" => [
[
"key" => "page",
"sequence" => 2,
"items" => [
["sequence" => 2],
["sequence" => 1],
["sequence" => 3],
],
],
[
"key" => "paragraph",
"sequence" => 1,
"items" => [
["sequence" => 2],
["sequence" => 1],
["sequence" => 3],
],
],
],
],
],
];
function sortit(&$array){
foreach($array as $key => &$value){
//If $value is an array.
if(is_array($value)){
if($key == "items"){
uasort($value, function($a,&$b) {
return $a["sequence"] <=> $b["sequence"];
});
}
//We need to loop through it.
sortit($value);
} else{
//It is not an array, so print it out.
echo $key . " : " . $value . "<br/>";
}
}
}
sortit($list);
echo "<pre>";
print_r($list);
?>
Here is the output and error I am getting, and I think I understand why the error is being thrown but at the same time I can not implement the proper checks needed to fix the error.
key : book
sequence : 1
key : page
sequence : 1
E_WARNING : type 2 -- Illegal string offset 'sequence' -- at line 39
E_NOTICE : type 8 -- Undefined index: sequence -- at line 39
sequence : 1
sequence : 2
sequence : 3
sequence : 1
key : page
E_WARNING : type 2 -- Illegal string offset 'sequence' -- at line 39
E_NOTICE : type 8 -- Undefined index: sequence -- at line 39
sequence : 1
sequence : 2
sequence : 3
sequence : 2
key : verse
Not that I am worried to much but another thing that I would like is the array to still be structured in the original order, ie: key, sequence, items
Using usort and array references makes it straightforward. If we're dealing with an array with a set item key, sort the item array and recurse on its children, otherwise, we're at a leaf node and can return.
function seqSort(&$arr) {
if (is_array($arr) && array_key_exists("items", $arr)) {
usort($arr["items"], function ($a, $b) {
return $a["sequence"] - $b["sequence"];
});
foreach ($arr["items"] as &$item) {
$item = seqSort($item);
}
}
return $arr;
}
Result:
array (
'key' => 'book',
'sequence' => 1,
'items' =>
array (
0 =>
array (
'key' => 'page',
'sequence' => 1,
'items' =>
array (
0 =>
array (
'key' => 'page',
'sequence' => 1,
'items' =>
array (
0 =>
array (
'sequence' => 1,
),
1 =>
array (
'sequence' => 2,
),
2 =>
array (
'sequence' => 3,
),
),
),
),
),
1 =>
array (
'key' => 'verse',
'sequence' => 2,
'items' =>
array (
0 =>
array (
'sequence' => 1,
),
1 =>
array (
'sequence' => 2,
),
2 =>
array (
'sequence' => 3,
),
),
),
),
)
Try it!
Note that the outermost structure is a root node that isn't part of an array and can't be sorted (this may be unintentional and causing confusion).
I have a sample array like this:
[
"BTCUSD",
"DASHBTC",
"DOGEUSD",
"LTCBTC",
"LTCUSD",
"SCBTC",
"STEEMBTC",
"WAVESBTC",
"SNGLSBTC",
"1STBTC",
"DASHUSD",
"BQXETH",
"PTOYETH",
"XAURETH",
"BTCUSDT"
]
How to get just one currency for every string? I can't use explode because there isn't a single/static delimiter. I can't use substr() or strpos() because the substrings vary.
So how to split it?
Update
This is my expected output
[
"BTC",
"DASH",
"DOGE",
"LTC",
"LTC",
"SC",
"STEEM",
"WAVES",
"SNGLS",
"1ST",
"DASH",
"BQX",
"PTOY",
"XAUR",
"BTC"
]
You need to have an array to your expected currencies and then filter against it.
Here is a quick and dirty solution: https://3v4l.org/6mMbN or with recursive function call: https://3v4l.org/mhjBD
<?php
$currencies = [
'USD',
'BTC',
'DASH',
'LTC',
'SC',
'STEEM',
'WAVES',
'SNGLS',
'1ST',
'BQX',
'ETH',
'PTOY',
'XAUR',
];
$input = [
"BTCUSD",
"DASHBTC",
"DOGEUSD",
"LTCBTC",
"LTCUSD",
"SCBTC",
"STEEMBTC",
"WAVESBTC",
"SNGLSBTC",
"1STBTC",
"DASHUSD",
"BQXETH",
"PTOYETH",
"XAURETH",
"BTCUSDT",
];
$output=[];
foreach($input as $doubleCurrency){
foreach($currencies as $currency){
$pattern = '/^'.$currency. '/';
preg_match($pattern, $doubleCurrency, $matches);
if(array_key_exists(0, $matches)){
$output[]=$matches[0];
}
}
}
var_dump($output);
You are always trimming the right side of the string, just use preg_replace() with an end of string anchor. No lookup array is necessary. It doesn't get much easier that this:
Code: (Demo)
$input=[
"BTCUSD",
"DASHBTC",
"DOGEUSD",
"LTCBTC",
"LTCUSD",
"SCBTC",
"STEEMBTC",
"WAVESBTC",
"SNGLSBTC",
"1STBTC",
"DASHUSD",
"BQXETH",
"PTOYETH",
"XAURETH",
"BTCUSDT"
];
var_export(preg_replace('/USDT$|USD$|ETH$|BTC$/','',$input));
Output:
array (
0 => 'BTC',
1 => 'DASH',
2 => 'DOGE',
3 => 'LTC',
4 => 'LTC',
5 => 'SC',
6 => 'STEEM',
7 => 'WAVES',
8 => 'SNGLS',
9 => '1ST',
10 => 'DASH',
11 => 'BQX',
12 => 'PTOY',
13 => 'XAUR',
14 => 'BTC',
)
(The pattern can be condensed if you don't mind the syntax. /USDT?$|ETH$|BTC$/ is a little faster.)
Here is an answer that is similar to Edwin's but is probably faster.
I use str_replace to create a space in-between the currencies then I loop the new array and remove anything after the first space.
Example:
'STEEMBTC' becomes STEEM BTC '.
Then substr it to: 'STEEM'.
Probably quicker code as it loops less and does not use regex.
$currencies = [
'USD',
'BTC',
'DASH',
'LTC',
'SC',
'STEEM',
'WAVES',
'SNGLS',
'1ST',
'BQX',
'ETH',
'PTOY',
'XAUR',
'DOGE'
];
$repl = [
'USD ',
'BTC ',
'DASH ',
'LTC ',
'SC ',
'STEEM ',
'WAVES ',
'SNGLS ',
'1ST ',
'BQX ',
'ETH ',
'PTOY ',
'XAUR ',
'DOGE '
];
$input = [
"BTCUSD",
"DASHBTC",
"DOGEUSD",
"LTCBTC",
"LTCUSD",
"SCBTC",
"STEEMBTC",
"WAVESBTC",
"SNGLSBTC",
"1STBTC",
"DASHUSD",
"BQXETH",
"PTOYETH",
"XAURETH",
"BTCUSDT",
];
$new = str_replace($currencies, $repl, $input);
Foreach($new as &$item){
$item = substr($item, 0, strpos($item, " "));
}
Var_dump($new);
https://3v4l.org/QDMjS
** I have edited this to show how I got my code to work using array_search
I have an array, $arr1 with 5 columns as such:
key id name style age whim
0 14 bob big 33 no
1 72 jill big 22 yes
2 39 sue yes 111 yes
3 994 lucy small 23 no
4 15 sis med 24 no
5 16 maj med 87 yes
6 879 Ike larg 56 no
7 286 Jed big 23 yes
This array is in a cache, not a database.
I then have a second array with a list of id values -
$arr2 = array(0=>14, 1=>72, 2=>8790)
How do I filter $arr1 so it returns only the rows with the id values in $arr2?
I got my code to work as follows:
$arr1 = new CachedStuff(); // get cache
$resultingArray = []; // create an empty array to hold rows
$filter_function = function ($row) use ($arr2) {
return (array_search($row['id'], $arr2));
};
$resultingArrayIDs = $arr1->GetIds($filter_function, $resultingArray);
This gives me two outputs: $resultingArray & $resultingArrayIDs both of which represent the intersection of the $arr1 and $arr2.
This whole task can be accomplished with just one slick, native function call -- array_uintersect().
Because the two compared parameters in the custom callback may come either input array, try to access from the id column and if there isn't one declered, then fallback to the parameter's value.
Under the hood, this function performs sorting while evaluating as a means to improve execution time / processing speed. I expect this approach to outperform iterated calls of in_array() purely from a point of minimized function calls.
Code: (Demo)
var_export(
array_uintersect(
$arr1,
$arr2,
fn($a, $b) =>
($a['id'] ?? $a)
<=>
($b['id'] ?? $b)
)
);
Something like this should do it, provided I've understood your question and data structure correctly:
$dataArray = [
[ 'key' => 0, 'id' => 14 , 'name' => 'bob' , 'style' => 'big' , 'age' => 33 , 'whim' => 'no' ],
[ 'key' => 1, 'id' => 72 , 'name' => 'jill' , 'style' => 'big' , 'age' => 22 , 'whim' => 'yes' ],
[ 'key' => 2, 'id' => 39 , 'name' => 'sue' , 'style' => 'yes' , 'age' => 111 , 'whim' => 'yes' ],
[ 'key' => 3, 'id' => 994 , 'name' => 'lucy' , 'style' => 'small' , 'age' => 23 , 'whim' => 'no' ],
[ 'key' => 4, 'id' => 15 , 'name' => 'sis' , 'style' => 'med' , 'age' => 24 , 'whim' => 'no' ],
[ 'key' => 5, 'id' => 16 , 'name' => 'maj' , 'style' => 'med' , 'age' => 87 , 'whim' => 'yes' ],
[ 'key' => 6, 'id' => 879 , 'name' => 'Ike' , 'style' => 'larg' , 'age' => 56 , 'whim' => 'no' ],
[ 'key' => 7, 'id' => 286 , 'name' => 'Jed' , 'style' => 'big' , 'age' => 23 , 'whim' => 'yes' ]
];
$filterArray = [14, 72, 879];
$resultArray = array_filter( $dataArray, function( $row ) use ( $filterArray ) {
return in_array( $row[ 'id' ], $filterArray );
} );
View this example on eval.in
However, your question appears to suggest this data might be coming from a database; is that correct? If so, perhaps it's more efficient to pre-filter the results at the database-level. Either by adding a field in the SELECT query, that represents a boolean value whether a row matched your filter ids, or by simply not returning the other rows at all.
One way is with foreach loop with array_search()
$result = [];
foreach ($arr1 as $value) { // Loop thru $arr1
if (array_search($value['id'], $arr2) !== false) { // Check if id is in $arr2
$result[] = $value; // Push to result if true
}
}
// print result
print_r($result);
As #DecentDabbler mentioned - if the data is coming out of a database, using an IN on your WHERE will allow you to retrieve only the relevant data.
Another way to filter is to use array functions
array_column extracts the value of the id column into an array
array_intersect returns the elements which are in both $arr1['id'] and $arr2
array_flip flips the resulting array such that the indices into $arr1 indicate the elements in both $arr1 and $arr2
$arr1 = [ [ 'id' => 14, 'name' => 'bob'],
['id' => 72, 'name' => 'jill'],
['id' => 39, 'name' => 'sue'],
['id' => 994, 'name' => 'lucy'],
['id' => 879, 'name'=> 'large']];
$arr2 = [ 14,72,879 ];
$intersection = array_flip(array_intersect(array_column($arr1,'id'),$arr2));
foreach ($intersection as $i) {
var_dump($arr1[$i]);;
}
I have an array based MySql database.
This is the array.
[
0 => [
'id' => '1997'
'lokasi_terakhir' => 'YA4121'
]
1 => [
'id' => '1998'
'lokasi_terakhir' => 'PL2115'
]
2 => [
'id' => '1999'
'lokasi_terakhir' => 'PL4111'
]
]
How can I get the element lokasi_terakhir that grouped by the first character ? What the best way ?
This is the goal :
[
"Y" => 1,
"P" => 2
]
Please advise
Here are two refined methods. Which one you choose will come down to your personal preference (you won't find better methods).
In the first, I am iterating the array, declaring the first character of the lokasi_terakhir value as the key in the $result declaration. If the key doesn't yet exist in the output array then it must be declared / set to 1. After it has been instantiated, it can then be incremented -- I am using "pre-incrementation".
The second method first maps a new array using the first character of the lokasi_terakhir value from each subarray, then counts each occurrence of each letter.
(Demonstrations Link)
Method #1: (foreach)
foreach($array as $item){
if(!isset($result[$item['lokasi_terakhir'][0]])){
$result[$item['lokasi_terakhir'][0]]=1; // instantiate
}else{
++$result[$item['lokasi_terakhir'][0]]; // increment
}
}
var_export($result);
Method #2: (functional)
var_export(array_count_values(array_map(function($a){return $a['lokasi_terakhir'][0];},$array)));
// generate array of single-character elements, then count occurrences
Output: (from either)
array (
'Y' => 1,
'P' => 2,
)
You can group those items like this:
$array = [
0 => [
'id' => '1997',
'lokasi_terakhir' => 'YA4121'
],
1 => [
'id' => '1998',
'lokasi_terakhir' => 'PL2115'
],
2 => [
'id' => '1999',
'lokasi_terakhir' => 'PL4111'
]
];
$result = array();
foreach($array as $item) {
$char = substr($item['lokasi_terakhir'], 0, 1);
if(!isset($result[$char])) {
$result[$char] = array();
}
$result[$char][] = $item;
}
<?php
$array=[
0 => [
'id' => '1997',
'lokasi_terakhir' => 'YA4121'
],
1 => [
'id' => '1998',
'lokasi_terakhir' => 'PL2115'
],
2 => [
'id' => '1999',
'lokasi_terakhir' => 'PL4111'
]
];
foreach($array as $row){
$newArray[]=$row['lokasi_terakhir'][0];
}
print_r(array_flip(array_unique($newArray)));
this code gets the first letter of the fields lokasi_terakhir , get the unique values to avoid duplicates and just flips the array to get the outcome you want.
The output is this :
Array ( [Y] => 0 [P] => 1 )
The problem I'm trying to solve is this.
I have an array of people, some of the people repeat in this array, but their details aren't always exactly the same, there are slight variations in their name. So what I'm trying to do is loop through all the names and compare how close the name is, then if that's a match compare their age and hometown.
Later I hope to create a new array and say "this person also has appears with the following ids" ...
What I have so far is a bubble sort, which if I let it run for long enough I'm sure I can get the job done. I'm just looking to see if someone has a better solution?
<?php
$arr = [
[
'id' => '123',
'name' => 'gary strange',
'home' => 'london',
'age' => 23
],
[
'id' => '124',
'name' => 'john jones',
'home' => 'london',
'age' => 45
],
[
'id' => '125',
'name' => 'bob smith',
'home' => 'paris',
'age' => 63
],
[
'id' => '126',
'name' => 'g strange',
'home' => 'london',
'age' => 23
],
[
'id' => '127',
'name' => 'gary strange',
'home' => 'paris',
'age' => 23
],
[
'id' => '128',
'name' => 'g f. strange',
'home' => 'london',
'age' => 23
]
];
for($i = 0; $i < count($arr); $i++) {
echo "Getting details for " . $arr[$i]['name'] . "\n";
for($j = 0; $j < count($arr); $j++) {
if($j == $i) continue;
else{
echo "Comparing to " . $arr[$j]['name'];
$str1 = $arr[$i]['name'];
$str2 = $arr[$j]['name'];
similar_text($str1,$str2,$percent);
echo " - " . $percent . "%\n";
}
}
echo "******\n";
}
It would probably be more efficient to compare only against people who have a similar age and hometown, and THEN do your comparison checks on name. Comparing against everyone first seems to be like it would be extraordinarily slow.
Something like this for your SQL, as you're iterating through each user:
SELECT name, age, hometown
FROM users
WHERE age BETWEEN value1 AND value2
AND hometown LIKE value3
And THEN do your comparison check. Make sure you use iterators -- this will keep your memory management at bay if you're iterating through tens of thousands of people.
This way, for each user, you're probably only comparing against a handful of possible matches (instead of potentially thousands).