I have an array coming with the following structure:
$arr = [
traveler_name_1 => 'value',
hotel_name_1 => 'value',
hotel_in_date_1 => 'value',
traveler_name_2 => 'value',
hotel_name_2 => 'value',
hotel_in_date_2 => 'value',
traveler_name_n => 'value',
hotel_name_n => 'value',
hotel_in_date_n => 'value',
];
I want to check if the string without the index is valid and is allowed. For example:
$allowed_values = [
'traveler_name',
'hotel_name',
'hotel_in_date'
];
The easy way would be iterate over $arr as $key => $value pairs and then make an in_array() but for do that I need to remove first the last portion of the string, for example:
traveler_name_1 => remove the _1
hotel_name_1 => remove the _1
hotel_in_date_1 => remove the _1
So I can look for them on the $allowed_values array.
So the question is how do I remove that part from the string?
The values after the second _ are generated dynamically so they hasn't a constant length. The value before are constant.
If you have a better way to validate this then go ahead and let me know
One way (probably not the most efficient one) is to do this:
$s = "traveler_name_24872";
$arr = explode("_", $s);
array_pop($arr);
$s2 = implode("_", $arr); // traveler_name
depending on how you want to manipulate the data, this would return what you're looking for. You could then put the $index and $value in a new array or just manipulate the data from within that foreach loop.
This returns the full string minus the last two characters (via the -2). If you want to return all but the last 3 characters, you'd replace -2 with -3, etc.
foreach ($arr as $key => $value) {
$index = substr($key, 0, -2);
}
I have found the solution after some tries so here it's what I was looking for:
foreach ($arr as $key => $value) {
if (!in_array(substr(key($key), 0, strrpos(key($key), '_')), $allowed_values, true)) {
echo 'Not allowed';
break;
}
}
Thank you anyway
Related
I need to check if the keys of an array match an array of keys, and if they don't match then they need to be created. While I managed to create the check I still need to return all keys that don't match in the in_array condition so that they can be added to the original array. How can I achieve this?
My current code:
$new_value = ['id','name','age'];
$keys = ['id','name','age','sex','height','weight'];
foreach($new_value as $new_value){
if(!in_array($new_value, $keys )){
$new_value["{$key}"] = '';
}
}
The desired result would be:
Array pre processing:
'id' => 1,
'name' => 'Ed',
'age' => 15,
Array post processing:
'id' => 1,
'name' => 'Ed',
'age' => 15,
'sex' => '',
'height' => '',
'weight' => '',
So, there are a couple of things here.
// When you are running through your foreach, you don't want to overwrite your
// array variable with the value, which is what is happening. Switch this to `new_values` (plural)
$new_values = ['id','name','age'];
$keys = ['id','name', 'age','sex','height','weight'];
// Since $keys is your desired structure, we want to loop through $key rather than $new_values, and add any missing keys to the $new_values
foreach($keys as $key){
// We want to check if $new_values has all the $keys (you were checking if $keys had all of $new_values, which it does already
if(!in_array($key, $new_values )){
// Since $key is already a string, we don't need to place it in quotes, just put the variable directly into the array as a new array item (because you are using an array of strings, the keys will be numerical)
$new_values[] = $key
}
}
If you are looking for a keyed array, so that you can get the value of $new_values['name'], then you will need to set your arrays up differently and do your checks differently. You can also use $keys to hold a default value.
// Set the keys for your array, instead of having an array of strings that is keyed numerically
$new_values = ['id' => 'user_id','name' => 'user name','age' => 'user age'];
$keys = ['id' => 'default_value','name' => 'default_value', 'age' => 'default_value','sex' => 'default_value','height' => 'default_value','weight' => 'default_value'];
// Here we'll get the key and value of each item in the $keys array
foreach($keys as $key => $default) {
// Check if the array key exists in new values and if not, set it to the predefined default value
if(!array_key_exists($key, $new_values) {
$new_values[$key] = $default;
}
}
EDIT
As per one of the comments on the answer, you could further simplify this with array_merge. If you want to throw an error or do something special on each field, you could use the loop. If you just want to fill in the blanks, it would work like:
// Set the keys for your array, instead of having an array of strings that is keyed numerically
$new_values = ['id' => 'user_id','name' => 'user name','age' => 'user age'];
$keys = ['id' => 'default_value','name' => 'default_value', 'age' => 'default_value','sex' => 'default_value','height' => 'default_value','weight' => 'default_value'];
// If you aren't going to be using the original $new_values array for anything, you can just overwrite it.
// As stated in the comment below, $new_values should be the second value, keys that are in both arrays will be overwritten by what's in the second array.
$new_values = array_merge($keys, $new_values);
// If you will be using the original $new_values array later in the code, you can set the output to a new variable
$filled_values = array_merge($keys, $new_values);
I'm not sure, but you can try next code:
<?php
$data = [
"id" => 1,
"name" => "Ed",
"age" => 15,
];
$keys = ["id", "name", "age", "sex", "height", "weight"];
$result = array_reduce(
$keys,
function ($data, $key) {
if (!isset($data[$key])) $data[$key] = '';
return $data;
},
$data
);
var_export($result);
PHP sandbox here
I start with a nested array of some arbitrary depth. Within that array, some keys are a series of tokens separated by dots. For example "billingAddress.street" or "foo.bar.baz". I would like to expand those keyed elements to arrays, so the result is a nested array with all those keys expanded.
For example:
[
'billingAddress.street' => 'My Street',
'foo.bar.baz' => 'biz',
]
should be expanded to:
[
'billingAddress' => [
'street' => 'My Street',
],
'foo' => [
'bar' => [
'baz' => 'biz',
]
]
]
The original "billingAddress.street" can be left alongside the new "billingAddress" array, but it does not need to be (so the solution may operate on the original array or create a new array). Other elements such as "billingAddress.city" may need to be added to the same expanded portion of the array.
Some keys may have more than two tokens separated by dots, so will need to be expanded deeper.
I've looked at array_walk_recursive() but that only operates on elements. For each matching element key, I actually want to modify the parent array those elements are in.
I've looked at array_map, but that does not provide access to the keys, and as far as I know is not recursive.
An example array to expand:
[
'name' => 'Name',
'address.city' => 'City',
'address.street' => 'Street',
'card' => [
'type' => 'visa',
'details.last4' => '1234',
],
]
This is to be expanded to:
[
'name' => 'Name',
'address.city' => 'City', // Optional
'address' => [
'city' => 'City',
'street' => 'Street',
],
'address.street' => 'Street', // Optional
'card' => [
'type' => 'visa',
'details.last4' => '1234', // Optional
'details' => [
'last4' => '1234',
],
],
]
What I think I need, is something that walks to each array in the nested array and can apply a user function to it. But I do suspect I'm missing something obvious. The payment gateway I am working with sends me this mix of arrays and "pretend arrays" using the dot-notation, and my objective is to normalize it into an array for extracting portions.
I believe the problem differs from similar questions on SO due to this mix of arrays and non-arrays for expanding. Conceptually it is a nested array where sound groups of elements at any level need to be replaced with new arrays, so there are two levels of recursion happening here: the tree walking, and the expansion, and then the walking of the expanded trees to see if there is more expansion needed.
You could find it useful to reverse the order of the keys you get from exploding the combined (dotted) key. In that reversed order it is easier to progressively wrap a previous result into a new array, thereby creating the nested result for one dotted key/value pair.
Finally, that partial result can be merged into the accumulated "grand" result with the built-in array_merge_recursive function:
function expandKeys($arr) {
$result = [];
foreach($arr as $key => $value) {
if (is_array($value)) $value = expandKeys($value);
foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
$result = array_merge_recursive($result, $value);
}
return $result;
}
See it run on repl.it
Here's a recursive attempt. Note that this doesn't delete old keys, doesn't maintain any key ordering and ignores keys of the type foo.bar.baz.
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
if (count($e) == 2) {
[$a, $b] = $e;
$data[$a][$b]= $v;
}
expand($data[$k]);
}
}
}
Result:
Array
(
[name] => Name
[address.city] => City
[address.street] => Street
[card] => Array
(
[type] => visa
[details.last4] => 1234
[details] => Array
(
[last4] => 1234
)
)
[address] => Array
(
[city] => City
[street] => Street
)
)
Explanation:
On any call of the function, if the parameter is an array, iterate through the keys and values looking for keys with a . in them. For any such keys, expand them out. Recursively call this function on all keys in the array.
Full version:
Here's a full version that supports multiple .s and cleans up keys afterwards:
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
$a = array_shift($e);
if (count($e) == 1) {
$data[$a][$e[0]] = $v;
}
else if (count($e) > 1) {
$data[$a][implode(".", $e)] = $v;
}
}
foreach ($data as $k => $v) {
expand($data[$k]);
if (preg_match('/\./', $k)) {
unset($data[$k]);
}
}
}
}
Another solution by #trincot has been accepted as being more elegant, and is the solution I am using now.
Here is my solution, which expands on the solution and tips given by #ggorlen
The approach I have taken is:
Create a new array rather than operate on the initial array.
No need to keep the old pre-expanded elements. They can be added easily if needed.
Expanding the keys is done one level at a time, from the root array, with the remaining expansions passed back in recursively.
The class method:
protected function expandKeys($arr)
{
$result = [];
while (count($arr)) {
// Shift the first element off the array - both key and value.
// We are treating this like a stack of elements to work through,
// and some new elements may be added to the stack as we go.
$value = reset($arr);
$key = key($arr);
unset($arr[$key]);
if (strpos($key, '.') !== false) {
list($base, $ext) = explode('.', $key, 2);
if (! array_key_exists($base, $arr)) {
// This will be another array element on the end of the
// arr stack, to recurse into.
$arr[$base] = [];
}
// Add the value nested one level in.
// Value at $arr['bar.baz.biz'] is now at $arr['bar']['baz.biz']
// We may also add to this element before we get to processing it,
// for example $arr['bar.baz.bam']
$arr[$base][$ext] = $value;
} elseif (is_array($value)) {
// We already have an array value, so give the value
// the same treatment in case any keys need expanding further.
$result[$key] = $this->expandKeys($value);
} else {
// A scalar value with no expandable key.
$result[$key] = $value;
}
}
return $result;
}
$result = $this->expandKeys($sourceArray)
I am trying to unset a group of array keys that have the same prefix. I can't seem to get this to work.
foreach ($array as $key => $value) {
unset($array['prefix_' . $key]);
}
How can I get unset to see ['prefix_' . $key] as the actual variable? Thanks
UPDATE: The $array keys will have two keys with the same name. Just one will have the prefix and there are about 5 keys with prefixed keys:
Array {
[name] => name
[prefix_name] => other name
}
I don't want to remove [name] just [prefix_name] from the array.
This works:
$array = array(
'aa' => 'other value aa',
'prefix_aa' => 'value aa',
'bb' => 'other value bb',
'prefix_bb' => 'value bb'
);
$prefix = 'prefix_';
foreach ($array as $key => $value) {
if (substr($key, 0, strlen($prefix)) == $prefix) {
unset($array[$key]);
}
}
If you copy/paste this code at a site like http://writecodeonline.com/php/, you can see for yourself that it works.
You can't use a foreach because it's only a copy of the collection. You'd need to use a for or grab the keys separately and separate your processing from the array you want to manipulate. Something like:
foreach (array_keys($array) as $keyName){
if (strncmp($keyName,'prefix_',7) === 0){
unset($array[$keyName]);
}
}
You're also already iterating over the collection getting every key. Unless you had:
$array = array(
'foo' => 1,
'prefix_foo' => 1
);
(Where every key also has a matching key with "prefix_" in front of it) you'll run in to trouble.
I'm not sure I understand your question, but if you are trying to unset all the keys with a specific prefix, you can iterate through the array and just unset the ones that match the prefix.
Something like:
<?php
foreach ($array as $key => $value) { // loop through keys
if (preg_match('/^prefix_/', $key)) { // if the key stars with 'prefix_'
unset($array[$key]); // unset it
}
}
You're looping over the array keys already, so if you've got
$array = (
'prefix_a' => 'b',
'prefix_c' => 'd'
etc...
)
Then $keys will be prefix_a, prefix_c, etc... What you're doing is generating an entirely NEW key, which'd be prefix_prefix_a, prefix_prefix_c, etc...
Unless you're doing something more complicated, you could just replace the whole loop with
$array = array();
I believe this should work:
foreach ($array as $key => $value) {
unset($array['prefix_' . str_replace('prefix_', '', $key]);
}
I have an array $data
fruit => apple,
seat => sofa,
etc. I want to loop through so that each key becomes type_key[0]['value'] so eg
type_fruit[0]['value'] => apple,
type_seat[0]['value'] => sofa,
and what I thought would do this, namely
foreach ($data as $key => $value)
{
# Create a new, renamed, key.
$array[str_replace("/(.+)/", "type_$1[0]['value']", $key)] = $value;
# Destroy the old key/value pair
unset($array[$key]);
}
print_r($array);
Doesn't work. How can I make it work?
Also, I want everything to be in the keys (not the values) to be lowercase: is there an easy way of doing this too? Thanks.
Do you mean you want to make the keys into separate arrays? Or did you mean to just change the keys in the same array?
$array = array();
foreach ($data as $key => $value)
{
$array['type_' . strtolower($key)] = array(array('value' => $value));
}
if you want your keys to be separate variables, then do this:
extract($array);
Now you will have $type_fruit and $type_sofa. You can find your values as $type_fruit[0]['value'], since we put an extra nested array in there.
Your requirements sound... suspect. Perhaps you meant something like the following:
$arr = array('fruit' => 'apple', 'seat' => 'sofa');
$newarr = array();
foreach ($arr as $key => $value)
{
$newkey = strtolower("type_$key");
$newarr[$newkey] = array(array('value' => $value));
}
var_dump($newarr);
First I wouldn't alter the input-array but create a new one unless you're in serious, serious trouble with your memory limit.
And then you can't simply replace the key to add a deeper nested level to an array.
$x[ 'abc[def]' ] is still only referencing a top-level element, since abc[def] is parsed as one string, but you want $x['abc']['def'].
$data = array(
'fruit' => 'apple',
'seat' => 'sofa'
);
$result = array();
foreach($data as $key=>$value) {
$target = 'type_'.$key;
// this might be superfluous, but who knows... if you want to process more than one array this might be an issue.
if ( !isset($result[$target]) || !is_array($result[$target]) ) {
$result[$target] = array();
}
$result[$target][] = array('value'=>$value);
}
var_dump($result);
for starters
$array[str_replace("/(.+)/", "type_$1[0]['value']", $key)] = $value;
should be
$array[str_replace("/(.+)/", type_$1[0]['value'], $key)] = $value;
So, I have an array that, for unrelated reasons, has one imploded field in itself. Now, I'm interested in exploding the string in that field, and replacing that field with the results of the blast. I kinda-sorta have a working solution here, but it looks clunky, and I'm interested in something more efficient. Or at least aesthetically pleasing.
Example array:
$items = array(
'name' => 'shirt',
'kind' => 'blue|long|L',
'price' => 10,
'amount' => 5);
And the goal is to replace the 'kind' field with 'color', 'lenght', 'size' fields.
So far I've got:
$attribs = array('color', 'lenght', 'size'); // future indices
$temp = explode("|", $items['kind']); // exploding the field
foreach ($items as $key => $value) { // iterating through old array
if ($key == 'kind') {
foreach ($temp as $k => $v) { // iterating through exploded array
$new_items[$attribs[$k]] = $v; // assigning new array, exploded values
}
}
else $new_items[$key] = $value; // assigning new array, untouched values
}
This should (I'm writing by heart, don't have the access to my own code, and I can't verify the one I just wrote... so if there's any errors, I apologize) result in a new array, that looks something like this:
$new_items = array(
'name' => 'shirt',
'color' => 'blue',
'lenght' => 'long',
'size' => 'L',
'price' => 10,
'amount' => 5);
I could, for instance, just append those values to the $items array and unset($items['kind']), but that would throw the order out of whack, and I kinda need it for subsequent foreach loops.
So, is there an easier way to do it?
EDIT:
(Reply to Visage and Ignacio - since reply messes the code up)
If I call one in a foreach loop, it calls them in a specific order. If I just append, I mess with the order I need for a table display. I'd have to complicate the display code, which relies on a set way I get the initial data.
Currently, I display data with (or equivalent):
foreach($new_items as $v) echo "<td>$v</td>\n";
If I just append, I'd have to do something like:
echo "<td>$new_items['name']</td>\n";
foreach ($attribs as $v) echo "<td>$new_items[$v]</td>\n";
echo "<td>$new_items['price']</td>\n";
echo "<td>$new_items['amount']</td>\n";
Associative arrays generally should not depend on order. I would consider modifying the later code to just index the array directly and forgo the loop.
Try this:
$items = array( 'name' => 'shirt', 'kind' => 'blue|long|L', 'price' => 10, 'amount' => 5);
list($items['color'], $items['lenght'], $items['size'])=explode("|",$items['kind']);
unset $items['kind'];
I've not tested it but it should work.
Associative arrays do not have an order, so you can just unset the keys you no longer want and then simply assign the new values to new keys.
one way
$items = array(
'name' => 'shirt',
'kind' => 'blue|long|L',
'price' => 10,
'amount' => 5);
$attribs = array('color', 'lenght', 'size');
$temp = explode("|", $items['kind']);
$s = array_combine($attribs,$temp);
unset($items["kind"]);
print_r( array_merge( $items, $s) );
This should retain the ordering because it splits the keys and values into two numerically ordered arrays and combines them at the end. It isn't more efficient, but the code is easier to read.
$kind_keys = array('color', 'length', 'size');
$kind_values = explode("|", $items['kind']);
$keys = array_keys($items);
$values = array_values($items);
$index = array_search('kind', $keys);
// Put the new keys/values in:
array_splice($keys, $index, 1, $kind_keys);
array_splice($values, $index, 1, $kind_values);
// combine the result into a new array:
$result = array_combine($keys, $values));