PHP using offsets and lengths to replace multiple strings - php

I have a string like this:
'The Pear and Orange are tasty. Which one do you prefer? Pear or Orange?'
I need to replace the 'Pear' and 'Orange' strings at the end and wrap them in links. I have the following information in JSON format:
"string": "The Pear and Orange are tasty. Which one do you prefer? Pear or Orange?",
"links": {
"55": [
{
"url": "http://example.com/pear",
"name": "Pear",
"offset": 55,
"length": 4
}
],
"63": [
{
"url": "http://example.com/orange",
"name": "Orange",
"offset": 63,
"length": 6
}
]
I want to use the offset and length to replace the strings rather than relying on the names, because the main string may contain multiple occurrences of the words.
What I've tried:
1) I've tried using a foreach loop to loop through the links in the JSON and replace them using substr_replace but after the first loop the offset is then not accurate as I've replaced the first string with a link, which increases the length of the original string by 20-30 characters.
2) I then tried passing replace, offset and length arrays to substr_replace to try to replace all the strings in one go, but that didn't work as the original string isn't in an array format, it's just a plain string.
Does anyone have any ideas or pointers for me? Any help would be hugely appreciated as I'm pulling my hair out. It seems so simple yet I can't quite get it!
Many thanks,
John

Your first instinct was right. The only change you need to make is to make the for loop start with the last entry and loop backward, in stead of the other way around. This will work because the offset of a word only changes if you change something that comes before that word in the string.
for($i = count($links)-1; $i >= 0; $i--) {
str_replace( ... );
}
The above approach requires that the links are sorted according to their offset, however, with the highest offset coming last in the array. If the links aren't sorted, you will have to do that first. But if you need to sort the array, you might as well sort them so that the highest offset comes first and loop through the array the normal way around.
For example:
$links = array(
array( 'name' => 'Pear', 'offset' => 55 ),
array( 'name' => 'Orange', 'offset' => 63 ),
array( 'name' => 'LAST', 'offset' => 0 ),
array( 'name' => 'MIDDLE', 'offset' => 60 ),
array( 'name' => 'FIRST', 'offset' => 1000)
);
function mySort($a, $b) {
return $b['offset'] - $a['offset']; // Sorts elements with higher offset first
}
usort($links, 'mySort');

Related

I cant figure out this array_map issue I am having

**EDIT:
I am trying to display the number of keys in my arrays that start with a "P", "M" and "D". I think I should be using array_maps and have some luck with it but I am now stuck and tried looking through the manual, on here and w3schools with no luck.
I'm using version 5.6.36 of PHP with XAMPP on a local server. I've tried playing around with array_maps which I think is the right command to use, but I just cant get my head around how to use it properly. I've read the manual on it, looked on here, looked on youtube and W3Schools with no luck. Can anyone help please?
I have this array:
$tasks = array
(
0 => 'P1',
1 => 'M1',
2 => 'D1',
3 => 'P2',
4 => 'D2'
);
I want it to display this:
Array
(
[P] => 2
[M] => 1
[D] => 2
)
See how it returns the number of P's M's and D's nice and neatly?
From what I understand, the solution code should be something like this:
$array2 = array_map(function(???????){
return ??????????;
}, $tasks);
$array2a = (array_count_values($array2));
echo "<pre>"; print_r($array2a); echo "</pre>";
Please help?!
you can use array_map as following :
$tasks = array
(
0 => 'P1',
1 => 'M1',
2 => 'D1',
3 => 'P2',
4 => 'D2'
);
$charsToCheck = array('P','M','D');
$result = array_map(function($v) use ($charsToCheck){
if(in_array(substr( $v, 0, 1),$charsToCheck))
return substr( $v, 0, 1);
}, $tasks);
print_r(array_count_values($result));
Result:-
Array
(
[P] => 2
[M] => 1
[D] => 2
)
The function array_map() creates one output element from every input element. Since you don't want that, it is the wrong tool. Probably the easiest way to achieve your goal is to use a simple loop. However, if things get more complicated, this may not scale well. For those cases, array_reduce() could come in handy:
$input = [
0 => 'P1',
1 => 'M1',
2 => 'D1',
3 => 'P2',
4 => 'D2',
];
$frequency = array_reduce(
$input,
function ($carry, $item) {
$initial = substr($item, 0, 1);
if (array_key_exists($initial, $carry)) {
$carry[$initial] += 1;
}
return $carry;
},
[
'P' => 0,
'M' => 0,
'D' => 0,
]
);
echo json_encode($frequency, JSON_PRETTY_PRINT) . PHP_EOL;
The point of this is that it defines what to do with a single element ($item) and how to modify the resulting state ($carry) in a single function, keeping this part away from the iteration part. Since this avoids mutable state, this can also be seen as a functional (as in "functional programming") approach.
You cannot use array_map for that... You could use reduce I guess but here's a fast and easy way... Basically you create your new array and do the counting according to the first letter of your tasks array.
$list = new Array();
foreach($tasks as $task){
if($list[$task{0}]){
$list[$task{0}]++;
}else{
$list[$task{0}] = 1;
}
}
The problem you'd get with array_map is that it would always produce a 1:1 ratio of your array, which is not what you want...
(sorry for the bad PHP if it is, been ages...)
EDIT:
Using your edited question, here's your possible usage:
$array2 = array_map(function($val){
return $val{0};
}, $tasks);
The key to both answers is the $var{0} part, this extracts the character at index 0...

PHP selecting only 4 from multidimensional array and shuffling

I have the following PHP multidimensional array, I'm looking to try select 4 random items and then show them with the title, image and text. With the code I've used I seem to get a single number which is randomized and not what I need.
<?php
$arr = array(
array(
"image" => "",
"title" => "Open 7 days.",
"text" => "We’re open 7 days a week."
),
array(
"image" => "",
"title" => "Well done",
"text" => "Well done you done great."
),
array(
"image" => "",
"title" => "Rice",
"text" => "Various flavours"
),
array(
"image" => "",
"title" => "Rooms",
"text" => "Roomy rooms for a roomyful time"
),
array(
"image" => "",
"title" => "Keep in touch.",
"text" => "Stay in touchwith us as we'll miss you"
),
array(
"image" => "",
"title" => "Location",
"text" => "We'll show you where we are."
),
array(
"image" => "",
"title" => "The Home",
"text" => "See our home page"
)
);
print_r(array_rand($arr));
If you're picking only one entry, array_rand() returns a single key for a random entry. If you use the num to specify how many keys should be picked, then it returns num number of keys of random entries.
The function only returns the keys of the random entries and not the array chunks itself. You'll have to manually build the array from the returned keys:
// get the random keys
$keys = array_rand($arr, 4);
// initialize result array
$result = array();
// loop through the keys and build the array
foreach ($keys as $k) {
$result[] = $arr[$k];
}
print_r($result);
Update
From a quick benchmark, it seems array_rand() is signficantly faster than using shuffle() for larger arrays. The benchmark was done on an array having 14336 elements with 10000 iterations each.
The results obtained on my dev machine were as follows:
Number of elements in the array - 14336
Number of iterations for each method - 10000
array_rand() version took 4.659 seconds
shuffle() version took 15.071 seconds
The code used for benchmarking can be found in this gist.
No need to loop !
shuffle() and array_slice() does the job.
Simply shuffle your array such that it rearranges the entries , now pick the first 4 items using array_slice.
shuffle($arr);
print_r(array_slice($arr,0,4));
Demo
Read about the $num parameter array_rand()
print_r(array_rand($arr, 4));
To display all:
foreach(array_rand($arr, 4) as $key) {
echo $arr[$key]['text'] ."\n";
//etc
}

MongoDB $multiply operator PHP example?

I'm trying to multiple all "point" fields.
$out = $kuponmac->aggregate(
array('$match' => array('random'=>$random, 'ekleyen'=>$ekleyen)),
array('$group' => array(
'_id' => '$state',
'totalPop' => array('$multiply' => '$point')
)
),
array(
'$match' => array('totalPop' => array('$gte' => 0))
)
);
this is my code. I'm trying to use multiply but I couldn't use it correctly.
How can I make multiplication all "point" fields?
I'm looking http://docs.mongodb.org/manual/reference/aggregation/multiply/ but there's no detailed explanation or example.
could u help me?
It says on the page how it works:
Takes an array of one or more numbers and multiples them, returning the resulting product.
So you do:
array('$multiply' => array('$point', '$otherNumber'))
Since you cannot mulitply one number meaningfully.
$multiply is not a supported $group operator (unlike $sum), so you cannot used it in that context to compute the value for a grouped field. Instead, $multiply could be used in an expression, which may itself be included as an argument to a $group operator or as a field value in a projection.
I attempted to rewrite your pipeline as follows:
[
{ $group: { _id: "$state", points: { $push: "$point" }}},
{ $project: { product: { $multiply: "$points" }}}
]
The idea here is that we'd use $push to collect all point values into an array for each state's $group projection, and then compute the product, using that array as the argument to $multiply.
Unfortunately, this resulted in an error, as MongoDB treated the de-referenced points field as a single argument to be multiplied, instead of an array of numbers. This was recently reported as a bug in SERVER-10676. I'd encourage you to watch that issue for resolution.
Additionally, I opened SERVER-10682 to discuss adding a $product operator for $group, which would be ideal for your use case.

Accessing multi-dimensional, unordered, associative arrays in an ordered fashion using an incremental loop

This is primarily a PHP answer, but the methodology may in fact be language agnostic.
After running a CSV file through a simple parser, I was given a multidimensional array that resembled something akin to this:
array( 'estimated' =>
array(
array( "TITLE" => 'MAIN', "CURR_PERF" => 100, "POT_PERF" => 75 ),
array( "TITLE" => 'HEAT', "CURR_PERF" => 90, "POT_PERF" => 60 ),
array( "TITLE" => 'CO2', "CURR_PERF" => 56, "POT_PERF" => 40 ),
),
'actual' =>
array(
array( "TITLE" => 'MAIN', "CURR_PERF" => 100, "POT_PERF" => 75 ),
array( "TITLE" => 'HEAT', "CURR_PERF" => 89 , "POT_PERF" => 75),
array( "TITLE" => 'CO2', "CURR_PERF" => 40, "POT_PERF" => 20 ),
);
);
Now, horrific data structure to one side, without refactoring of the underlying parser - how would be the best way to ensure that you can access these in a specific order? Without necessarily touching the underlying parser?
If you loop through using a for()/foreach() loop you're only going to be able to read them in a linear order - increasing or decreasing down the elements. You're not necessarily going to be able to drill down and get the specific value required.
For instance, the CSV file could express the values for estimated in a different order to the values for actual; and it may be required that they are output in yet another order.
For example, here are three different orders off the top of my head:
-> MAIN HEAT CO2
-> HEAT MAIN CO2
-> CO2 HEAT MAIN
Furthermore, as is quite usual, the label names in the CSV file are not exactly user-friendly - so they need to be 'translated' (if you like) in to something more human-friendly. Naturally, without the use of lots of if() statements if preferable!
Given it's a very specific use case, but it's something I've seen before with regards to arrays being serialised - and often finding out that they are actually nested.
I've posted one possible solution, but would be happy to see others. (In the past when I've done similar I've never accepted my own answer ;)) I'm sure there must be a more elegant way than the one I've devised..!
This was the quickest (and neatest) solution I could come up with on the spot at the time, but it's not one that I take very much pride in..! It involves multiple loops, the use of several arrays and seems to be an over-engineered mechanism for doing something which should surely be pretty simplistic?
First of all I've created an associative array to use as a dictionary to look up translations for text strings found in the CSV file:
$tbl = array( "MAIN"=>"Main Costs",
"TOTAL"=>"Total Costs",
"CO2"=>"Gas expended" );
Next up, I created an array to use as an 'index' - I entered the key values here in the order I would like them to be accessed in the application:
$index = array( "MAIN", "TOTAL", "CO2" );
I then created two blank arrays and populated them with the data from the child arrays, by using a loop I was able to make them associative - allowing me to specify use the TITLE fields as keys:
$estimated = array();
$actual = array();
foreach( $bills['estimated'] as $bill ){
$estimated[ $bill['title'] ] = $bill;
}
foreach( $bills['actual'] as $bill ){
$actual[ $bill['title'] ] = $bill;
}
By doing this I could loop through them in a specific order, regardless of the order they were parsed in, like so:
for($i=0; $i<3; $i++){
$bill = $estimated[ $index[ $i ] ];
printf(" %s: %d ", $tbl[ $index[ $i ] ], $bill['CURR_PERF'] );
}
Which would output the following, in the order I specified:
// 1. Main Costs: 100
// 2. Total Costs: 90
// 3. Gas Expended: 56
Naturally, this order could easily be changed if required. It does however:
require using an array specifically to act as an index
require using two loops simply to initialise
uses a total of four extra arrays
If this array was assigned to variable $ary like:
$ary = array(
'estimated' =>
array(
array('TITLE' => 'MAIN', 'CURR_PERF' => 100, 'POT_PERF' => 75),
array('TITLE' => 'HEAT', 'CURR_PERF' => 90, 'POT_PERF' => 60),
array('TITLE' => 'CO2', 'CURR_PERF' => 56, 'POT_PERF' => 40),
),
'actual' =>
array(
array('TITLE' => 'MAIN', 'CURR_PERF' => 100, 'POT_PERF' => 75),
array( 'TITLE' => 'HEAT', 'CURR_PERF' => 89 , 'POT_PERF' => 75),
array( 'TITLE' => 'CO2', 'CURR_PERF' => 40, 'POT_PERF' => 20 ),
);
);
Your estimated array could be accessed like:
$newVar = $ary['estimated'][2]['CURR_PERF'];
$newVar would be 56. You can reassign a value as well, like:
$ary['estimated'][0]['POT_PERF'] = 300;
That first array in the estimated array used to be 75 and is now 300.
This is how you get and set specific values in a Multi-dimensional Array.
To sort the arrays, if you need to loop over and maintain their indexes, see PHP's uasort() function. This may take some work, on your part, to develop a cmp_function, but should do what you're looking for in a different way.
See,
http://www.php.net/manual/en/function.uasort.php
and
http://www.php.net/manual/en/function.usort.php ,
which explains cmp_function better.

Pass array to javascript as array not JSON from PHP

First this is not a duplicate questions. I've looked through some similar problem and most of the answer is what I am using right now.
Here is the problem set up,
on PHP side
$array = array('name' => 'a', 'data' => array('0'=>15,'0.25'=>'18','0.35'=>19,'1' =>20));
echo json_encode($array);
on the JS side
data = $.parseJSON(data); // data is the return from the php script
above
As you can see the $array['data'] is an associative array with numeric number as its key and sorted in order. While parsing into JSON, javascript altered the order of that array and sorted 0 and 1 as numeric key and put them to the head of the object.
I know this is standard behavior for certain browser such as chrome, and IE9.
I've read somewhere that people suggest stick with array strictly if I want to maintain the order of the array.
But my question is how do you feed back an array from PHP to javascript as an array instead of using json object? Or is there other solution to this kind of problem . Thanks for the input in advance.
Thanks for the input in advance
Use an array to maintain order, and then an object to create the map. There are two ways. I would suggest:
$array = array('name' => 'a', 'data' =>
array(
array('key' => 0, 'value' => 15),
array('key' => 0.25, 'value' => 18),
array('key' => 0.35, 'value' => 19),
array('key' => 1, 'value' => 20),
)
);
echo json_encode($array);
Which will give you the JSON:
{
"name": "a",
"data": [
{"key": 0, "value": 15},
{"key": 0.25, "value": 18},
{"key": 0.35, "value": 19},
{"key": 1, "value": 20}
]
}
Then you will have order but to look up a certain key will be more difficult. If you want that to be easy you can return a mapping object as well like this:
$array = array('name' => 'a', 'data' =>
array(
"0" => 15,
"0.25" => 18,
"0.35" => 19,
"1" => 20,
),
'order' => array("0", "0.25", "0.35", "1")
);
echo json_encode($array);
Which will give you:
{
"name": "a",
"data": {
"0": 15,
"0.25": 18,
"0.35": 19,
"1": 20
},
"order": ["0", "0.25", "0.35", "1"]
}
One of these two methods of returning your data should prove to be the most useful for your specific use case.
Actually, it's PHP that takes the "0" and "1" keys and makes them numeric keys. This has nothing to do with your JavaScript.
There isn't any real way to work around this, but ideally your code should not rely on such things as "what order the keys of an object are in". It may be a better idea, just from what I see here, to separate the data into an array of keys and an array of values, then zip them back together on the JS side.
I'd suggest another field for storing order.
$array = array('name' => 'a',
'data' => array('0'=>15,'0.25'=>'18','0.35'=>19,'1' =>20),
'order'=> '0,0.25,0.35,1'
);
echo json_encode($array);

Categories