I'm trying to do a find replace of the json data below. I'm trying to replace "Consumer" with "CON", "Industrial" with "IND", and "Technology" with "TCH". Ideally, I would like to pass a function an array of finds (i.e., {"Consumer", "Industrial", "Technology"}) along with an array of replaces (i.e., {"CON", "IND", "TCH"}), or some other way to quickly define multiple find and replace pairs (there will be many more pairs than these 3). What is the best function to do this? Thanks, any help is appreciated.
[{"category":"Consumer","price":"18.9","number":"5"},{"category":"Industrial","price":"13.4","number":"4"},{"category":"Technology","price":"15.5","number":"3"}]
Here's a solution that goes through the json data object piece by piece (examining both key and values and replacing as possible only if the key/value is fully in the array of $replaces)
function replace(&$array, $replaces) {
foreach ($array as $k => $v) {
$new_k = replace_word($k, $replaces);
if (is_array($v)) {
replace($v, $replaces);
}
else {
$v = replace_word($v, $replaces);
}
$array[$new_k] = $v;
if ($new_k != $k) {
unset($array[$k]);
}
}
}
function replace_word($word, $replaces) {
if (array_key_exists($word, $replaces)) {
$word = str_replace($word, $replaces[$word], $word);
}
return $word;
}
/* TEST FUNCTIONS */
$json = '[{"category":"Consumer","price":"18.9","number":"5"},{"category":"Industrial","price":"13.4","number":"4"},{"category":"Technology","price":"15.5","number":"3"}]';
$replaces = array("category" => "cat", "Consumer" => "cons");
$json_data = json_decode($json, true);
var_dump($json_data);
replace($json_data, $replaces);
var_dump($json_data);
$json = json_encode($json_data);
preg_replace can take pattern and replacement as arrays.
http://php.net/manual/en/function.preg-replace.php
So, for example:
preg_replace(array('Consumer', 'Industrial', 'Technology'), array('CON', 'IND', 'TCH'), $json);
Note, however, that unless you're absolutely sure of the JSON you'll be getting it is probably better practice to parse the JSON and replace within the actual key/values.
Related
I'm trying to all URLs that contain http://api.example.com/data/ to https://example.com/data/
in an array from a Database while using the Fat-Free Framework, the code iv tried below gives me
Internal Server Error, Array to string conversion
$f3->route('GET /details/#itemid',
function($f3) {
$arr = array('result' => $f3->get('DBh')->exec('SELECT * FROM DB_table WHERE id=?',$f3->get('PARAMS.itemid')));
str_replace('http://api.example.com/data/', 'https://example.com/data/', $arr);
$f3->mset($arr);
echo \Template::instance()->render('views/details.html');
}
);
Well you've got a couple simple options to help with this. I'm also going to assume you mean to reference $arr['result'] and not just $arr and I'd need to assume you're only looking for one column out of the db column. I'll call this column url.
Option 1
foreach($arr['result'] as $key => $value) {
if(empty($value['url'])) {
continue;
}
$arr['result'][$key]['url'] = str_replace('http://api.example.com/data/', 'https://example.com/data/', $value['url']);
}
Option 2
foreach($arr['result'] as &$value) {
if(empty($value['url'])) {
continue;
}
$value['url'] = str_replace('http://api.example.com/data/', 'https://example.com/data/', $value['url']);
}
Option 3
$arr['result'] = array_map(function($value) {
$value['url'] = str_replace('http://api.example.com/data/', 'https://example.com/data/', $value['url']);
return $value;
}, $arr['result']);
My personal preference for readability and simplicity is Option 2. I believe foreach statements have been optimized to actually run faster than array_map which is......interesting.
I have an associative array:
$input = [
['key'=>'x', 'value'=>'a'],
['key'=>'x', 'value'=>'b'],
['key'=>'x', 'value'=>'c'],
['key'=>'y', 'value'=>'d'],
['key'=>'y', 'value'=>'e'],
['key'=>'z', 'value'=>'f'],
['key'=>'m', 'value'=>'n'],
];
And I want to reform it simple in:
$output = [
'x'=>['a','b','c'],
'y'=>['d','e'],
'z'=>'f',
'm'=>'n'
]
So basically, conditions are:
1. If same key found then put values in an array.
2. If no same key found then value remains string.
You can replace associative array with object if you are more comfortable with objects.
Here is my working solution for this problem:
foreach($input as $in){
if(!empty($output[$in['key']])){
if(is_array($output[$in['key']])){
$output[$in['key']][] = $in['value'];
continue;
}
$output[$in['key']] = [$output[$in['key']],$in['value']];
continue;
}
$output[$in['key']] = $in['value'];
}
print_r($output);
However I believe that it can be done in much compact and efficient way.
Please comment your answers if someone has better solution.
Your help is much appreciated!
Reformat array to [ [ x=>a ], [x=>b],.. ] and merge all sub-arrays
$input = array_map(function($x) { return [$x['key'] => $x['value']]; }, $input);
$input = array_merge_recursive(...$input);
print_r($input);
demo
I would suggest
<?php
$input = [
['key'=>'x', 'value'=>'a'],
['key'=>'x', 'value'=>'b'],
['key'=>'x', 'value'=>'c'],
['key'=>'y', 'value'=>'d'],
['key'=>'y', 'value'=>'e'],
['key'=>'z', 'value'=>'f'],
['key'=>'m', 'value'=>'n'],
];
$reducer = function($carry, $item) {
$carry[$item['key']][] = $item['value'];
return $carry;
};
$mapper = function ($item) {
if (count($item) === 1) {
return $item[0];
}
return $item;
};
$output = array_map($mapper, array_reduce($input, $reducer, []));
var_dump($output);
You can see the result here: https://3v4l.org/8JjjS
You can use array_reduce to loop over an existing array and build up a new one:
$output = array_reduce($input, function ($carry, $i) {
$carry[$i['key']][] = $i['value'];
return $carry;
}, []);
Each element in $input is passed to the anonymous function, along with the $carry variable that's being built up as we go along. Inside, we just add each value to a sub-element indexed by key. The third argument [] is to set the initial value of the result to an empty array.
See https://eval.in/935015
(I'm assuming that the duplicate x keys in the question are a typo, and that the second is supposed to z, since that matches up with your suggested output)
For your original code you may find extract() interesting. I replaced the continue-s with else-s, but that is more like a matter of taste:
foreach($input as $in){
extract($in);
if(!empty($output[$key])){
if(is_array($output[$key])){
$output[$key][] = $value;
} else {
$output[$key] = [$output[$key],$value];
}
} else {
$output[$key] = $value;
}
On a side note I would probably use two much simpler loops, one for building lists and another for extracting single elements:
foreach($input as $in){
$output[$in['key']][] = $in['value'];
/* or: extract($in);
$output[$key][]=$value; */
}
foreach($output as $key => $value){
if(count($value)==1){
$output[$key]=$value[0];
}
}
I have an object which is an array of JSON objects. Kinda Like this,
$object = [
{
"id":1,
"name":"blue",
"order":4
},
{
"id":2,
"name":"green",
"order":6
},
{
"id":3,
"name":"yellow",
"order":2
}
]
I wanted to access the properties in a simple way and a single line maybe like this,
Say if I wanted the "order" of the object with name "blue"
$blue_order = $object[something]->[name="blue"]->order;
Kinda mixed Jquery in this. But I hope you understand. Right now the best I've got is this,
for($i=0; $i<count($object); $i++){
if($object[$i]->name == "blue"){
$blue_order = $object[$i]->order;
}
}
This seems very inefficient though and I don't want to use a loop because the array is very large and looping through it will be very slow. So how do I do this?
I used a "for" loop instead of foreach because the array can be null. And also the order of the array elements will not always be the same.
So I can't do something like
$object[0]->order
<?php
$arr = array(
array('id'=>1,'name'=>'blue','order'=>4),
array('id'=>2,'name'=>'green','order'=>6),
array('id'=>3,'name'=>'yellow','order'=>2),
);
// $json is JSON version of the arrays in $arr
$json = json_encode($arr);
// show $json
echo $json . "\n";
// create arrays from JSON so it can be used in PHP easier
$obj = json_decode($json);
$color = 'blue';
$blue_order = array_filter($obj, function($i) use ($color) { return $i->name == $color; })[0]->order;
echo "Blue Order: " . $blue_order;
You may be able to use array_filter to help this become a one-liner. I included the json_decode and json_encode so I could have a complete example.
You could still use foreach but check if the array isn't empty first, like this:
if(!empty($object)){
foreach($object as $element){
if($element->name == "blue"){
$blue_order = $element->order;
}
}
}
Also, your code looks efficient to me, what I would probably add is a break after you find the value, like this:
if(!empty($object)){
foreach($object as $element){
if($element->name == "blue"){
$blue_order = $element->order;
break;
}
}
}
If your object has a lot of information and you're going to do a lot of searches, then you could do some pre-processing so it get easier to search, like this:
$object_by_name = array();
if(!empty($object)){
foreach($object as $element){
$object_by_name[$element->name] = $element;
}
}
Then, you could search it like this:
if(!empty($object_by_name['blue'])){
$blue_order = $object_by_name['blue']->order
}
Keep it simple.
You're writing way too much code.
$array_list = ($array_list)?:[];
$array_list = array_filter($array_list,function($var) {
return ($var->name=="blue");
});
$order = ($array_list)? $array_list[0]->order :'';
If the parameter name of an URL contains squared brackets (no matter if they are url-encoded or not), any following character is ignored by PHP and is not made available to the script (e.g. via $_GET).
Example request:
...showget.php?xxx%5B1%5Dyyy=42
$_GET:
Array
(
[xxx] => Array
(
[1] => 42
)
)
As you can see, "yyy" didn't made it. ^^
(tested in PHP 5.3.28 & 5.5.10)
Does somebody know if such URLs are even syntactically valid?
Is this behaviour intended and documented (could not find anything) or should it rather be considered as a bug within PHP?
If intended: Can i change the respective behaviour by changing a special setting or so?
Thanks!
This is intended behaviour. As you saw in your example, PHP builds arrays from GET parameters if it can, that is, if it finds square brackets in the variable name. There's a FAQ entry showing how that can sometimes be useful.
In your case, PHP sees xxx[1]yyy=42 as xxx[1]=42 which becomes an array.
As far as I know, PHP's query string parsing can not be changed, but you could use $_SERVER['QUERY_STRING'] and parse that yourself.
[] in query key names is a hint to PHP that you want an array, e.g.
example.com?foo[]=bar&foo[]=baz
produces
$_GET = array(
'foo' => array('bar', 'baz')
);
This notation also lets you specify keys in the url:
example.com?foo[bar]=baz
$_GET = array(
'foo' => array('bar' => 'baz')
);
But once you get into this array notation, you're not permitted to have anything in the keyname AFTER the [] portion:
example.com?foo[bar]baz=qux
$_GET = array(
'foo' => array('bar' => 'qux')
);
Basically it's related to PHP syntax, where somethign like
$foo['bar']baz
would be a syntax error.
Came across this myself earlier too, and wrote a function to handle it from POST data, but it shouldn't take much to get it to use GET data instead. Such a large amount of code simply to account for the fact that PHP doesn't account for nested square brackets ;-)
/**
* Gets the _POST data with correct handling of nested brackets:
* "path[to][data[nested]]=value"
* "path"
* -> "to"
* -> "data[nested]" = value
* #return array
*/
function get_real_post() {
function set_nested_value(&$arr, &$keys, &$value) {
$key = array_shift($keys);
if (count($keys)) {
// Got deeper to go
if (!array_key_exists($key, $arr)) {
// Make sure we can get deeper if we've not hit this key before
$arr[$key] = array();
} elseif (!is_array($arr[$key])) {
// This should never be relevant for well formed input data
throw new Exception("Setting a value and an array with the same key: $key");
}
set_nested_value($arr[$key], $keys, $value);
} elseif (empty($key)) {
// Setting an Array
$arr[] = $value;
} else {
// Setting an Object
$arr[$key] = $value;
}
}
$input = array();
$parts = array();
$pairs = explode("&", file_get_contents("php://input"));
foreach ($pairs as $pair) {
$key_value = explode("=", $pair, 2);
preg_match_all("/([a-zA-Z0-9]*)(?:\[([^\[\]]*(?:(?R)[^\[\]]*)*)\])?/", urldecode($key_value[0]), $parts);
$keys = array($parts[1][0]);
if (!empty($parts[2][0])) {
array_pop($parts[2]); // Remove the blank one on the end
$keys = array_merge($keys, $parts[2]);
}
$value = urldecode($key_value[1]);
if ($value == "true") {
$value = true;
} else if ($value == "false") {
$value = false;
} else if (is_numeric($value)) {
if (strpos($value, ".") !== false) {
$num = floatval($value);
} else {
$num = intval($value);
}
if (strval($num) === $value) {
$value = $num;
}
}
set_nested_value($input, $keys, $value);
}
return $input;
}
Snippet:
$temp = array();
foreach ($data as $key => $value) {
// This line varies, from HTML to URLs, to anything in between
array_push($temp, "<b>$key:</b> $value");
}
$request = implode('<br/>', $temp); // Glue also varies depending on needs
unset($temp);
This is a getaway from the usual $string .= 'blah<br/>'. Never mind the rtrim.
Can anyone show me a better way to achieve above without use of a temp array and possibly without a loop? Bonus points for not using above code in a function, lambda acceptable though.
P.S. While writing this question I have found a wonderful http_build_query(). One down, one to go.
Edit:
What result should look like:
<b>Title: </b> Value</br>
<b>Title2: </b> Value2</br>
<b>Title3: </b> Value3
Or with different settings (not required, but shows possibility):
key=value&key2=value2&key3=value3
I am trying to improve my code, I use the above snippet everywhere.
My answer: There are some ways, but:
Loops & arrays are the best friends of any programmer.
They provide pretty good readability, reusability and generally are considered to be a right way for performing pretty much the same actions.
You may also take a look on array_map or array_walk. Less code, but it is a loop anyways.
array_walk($data, function($value, $key){
$temp[] = "<b>{$key}:</b> {$value}" ; //Shorthand for array_push is $arr[] = $value ;
}) ;
I suppose this is one way; yay for "one-liners":
$data = ...;
echo join("\n", array_map(function($key) use ($data) {
return sprintf('<b>%s:</b> %s',
htmlspecialchars($key, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($data[$key], ENT_QUOTES, 'UTF-8')
);
}, array_keys($data)));
You basically map the keys using a closure that binds the original array as well so that you have access to both the key and value in each invocation.
I think you will prefere your solution:
$data = range('a', 'e'); // some data
$request = ''; // empty string
array_map(function($value, $key)use(&$request, $data){
static $i = 1;
if($i == count($data)){
$request .= "<b>$key:</b> $value";
}else{
$request .= "<b>$key:</b> $value<br/>";
}
$i++;
}, $data, array_keys($data));
echo $request;
Online demo
A solution with a loop but without $temp array:
$data = range('a', 'e'); // some data
$request = ""; // empty string
foreach ($data as $key => $value) {
$request .= "<b>$key:</b> $value<br/>"; // add to string
}
$request = substr($request,0,-5); // remove last <br/>
echo $request; //output