EDIT:
I have the following:
$EXCLUDE['extensions']="199";
$EXCLUDE['extensions']="800-1000":
What I want to do is be able to create a list of values, either single digit or a range.
End result:
I have this(listed below) and I want to replace the 799 and 1000 with what I have below and set it so that it DOES NOT display those extensions if they contain those numbers. But I also want to include a range as well.
foreach($obj as $file) {
if($file['dir_list']=="yes"){
if($file['user']<="799" || $file['user']>="1000"){
$D = $domain;
$V = $file['user'];
$g = $this->get_presence($D,$V);
Make a 2-dimensional array of start/end values.
$exclude = array(
array('start' => 199, 'end' => 199),
array('start' => 800, 'end => 1000)
);
Then iterate over the array to see if the value is in on of the excluded ranges.
foreach ($obj as $file) {
$excluded = false;
foreach ($exclude as $e) {
$num = intval($file['user']);
if ($num >= $e['start'] && $num <= $e['end']) {
$excluded = true;
break;
}
}
if (!$excluded) {
$D = $domain;
$V = $file['user'];
$g = $this->get_presence($D, $V);
}
}
Not sure whether I've understood the question correctly, but here it is:
The initialization of an array can be done with array(), for example:
<?php
$a = array(1, 5, "foo", 8, 19, "some text", "foo");
?>
This creates an array with seven elements. As you can see, types within an array can be mixed.
Values that you put in an array must neither be array nor come from arrays, but it is allowed. The following code creates an array of arrays:
<?php
//array of arrays
$a = array(
array(1,2,3),
array("test", "text"),
array(1,2,"foo")
);
?>
Related
Given a string that contains values separated by dots:
property.entry.item
What is the best way to convert that to a key for an associative array?
$result['imported_data']['property']['entry']['item']
The string may be of any length, with any number of dots and contain an value:
people.arizona.phoenix.smith
I've tried the following without success:
//found a dot, means we are expecting output from a previous function
if( preg_match('[.]',$value)) {
//check for previous function output
if(!is_null($result['import'])) {
$chained_result_array = explode('.',$value);
//make sure we have an array to work with
if(is_array($chained_result_array)) {
$array_key = '';
foreach($chained_result_array as $key) {
$array_key .= '[\''.$key.'\']';
}
}
die(print_r(${result.'[\'import\']'.$array_key}));
}
}
I was thinking I could convert the string to a variable variable, but I get an array to string conversion error.
You can explode the string into an array and loop through the array. (DEMO)
/**
* This is a test array
*/
$testArray['property']['entry']['item'] = 'Hello World';
/**
* This is the path
*/
$string = 'property.entry.item';
/**
* This is the function
*/
$array = explode('.', $string);
foreach($array as $i){
if(!isset($tmp)){
$tmp = &$testArray[$i];
} else {
$tmp = $tmp[$i];
}
}
var_dump( $tmp ); // output = Hello World
Split the string into parts, and itterate the array, accessing each element in turn:
function arrayDotNotation($array, $dotString){
foreach(explode('.', $dotString) as $section){
$array = $array[$section];
}
return $array;
}
$array = ['one'=>['two'=>['three'=>'hello']]];
$string = 'one.two.three';
echo arrayDotNotation($array, $string); //outputs hello
Live example: http://codepad.viper-7.com/Vu8Hhy
You should really check to see if keys exist before you reference them. Otherwise, you're going to spew a lot of warnings.
function getProp($array, $propname) {
foreach(explode('.', $propname) as $node) {
if(isset($array[$node]))
$array = &$array[$node];
else
return null;
}
return $array;
}
Now you can do things like:
$x = array(
'name' => array(
'first' => 'Joe',
'last' => 'Bloe',
),
'age' => 27,
'employer' => array(
'current' => array(
'name' => 'Some Company',
)
)
);
assert(getProp($x, 'age') == 27);
assert(getProp($x, 'name.first') == 'Joe');
assert(getProp($x, 'employer.current.name') == 'Some Company');
assert(getProp($x, 'badthing') === NULL);
assert(getProp($x, 'address.zip') === NULL);
Or, if you are only interested in the import section of the tree:
getProp($x['import'], 'some.path');
Given a string that contains values separated by dots:
property.entry.item
What is the best way to convert that to a key for an associative array?
$result['imported_data']['property']['entry']['item']
The string may be of any length, with any number of dots and contain an value:
people.arizona.phoenix.smith
I've tried the following without success:
//found a dot, means we are expecting output from a previous function
if( preg_match('[.]',$value)) {
//check for previous function output
if(!is_null($result['import'])) {
$chained_result_array = explode('.',$value);
//make sure we have an array to work with
if(is_array($chained_result_array)) {
$array_key = '';
foreach($chained_result_array as $key) {
$array_key .= '[\''.$key.'\']';
}
}
die(print_r(${result.'[\'import\']'.$array_key}));
}
}
I was thinking I could convert the string to a variable variable, but I get an array to string conversion error.
You can explode the string into an array and loop through the array. (DEMO)
/**
* This is a test array
*/
$testArray['property']['entry']['item'] = 'Hello World';
/**
* This is the path
*/
$string = 'property.entry.item';
/**
* This is the function
*/
$array = explode('.', $string);
foreach($array as $i){
if(!isset($tmp)){
$tmp = &$testArray[$i];
} else {
$tmp = $tmp[$i];
}
}
var_dump( $tmp ); // output = Hello World
Split the string into parts, and itterate the array, accessing each element in turn:
function arrayDotNotation($array, $dotString){
foreach(explode('.', $dotString) as $section){
$array = $array[$section];
}
return $array;
}
$array = ['one'=>['two'=>['three'=>'hello']]];
$string = 'one.two.three';
echo arrayDotNotation($array, $string); //outputs hello
Live example: http://codepad.viper-7.com/Vu8Hhy
You should really check to see if keys exist before you reference them. Otherwise, you're going to spew a lot of warnings.
function getProp($array, $propname) {
foreach(explode('.', $propname) as $node) {
if(isset($array[$node]))
$array = &$array[$node];
else
return null;
}
return $array;
}
Now you can do things like:
$x = array(
'name' => array(
'first' => 'Joe',
'last' => 'Bloe',
),
'age' => 27,
'employer' => array(
'current' => array(
'name' => 'Some Company',
)
)
);
assert(getProp($x, 'age') == 27);
assert(getProp($x, 'name.first') == 'Joe');
assert(getProp($x, 'employer.current.name') == 'Some Company');
assert(getProp($x, 'badthing') === NULL);
assert(getProp($x, 'address.zip') === NULL);
Or, if you are only interested in the import section of the tree:
getProp($x['import'], 'some.path');
I am checking that certain elements in sub-arrays in a multidimensional array are not equal to a value and un-setting the array with that value from the multi array. I built a function so that I could easily implement this, however it doesn't appear to be working.
function multi_unset($array, $unset) {
foreach($array as $key=>$value) {
$arrayU = $array[$key];
$check = array();
for($i = 0; $i < count($unset); $i++) { // produces $array[$key][0, 2, 3]
array_push($check, $arrayU[$unset[$i]]);
}
if(in_array("-", $check)) {
unset($array[$key]);
}
}
return $array;
}
$arr = array(array("-", "test", "test", "test"), array("test", "test", "test", "test"));
$unset = array(0, 2, 3); // keys in individual arrays to check
multi_unset($arr, $unset);
print_r($arr); // Should output without $arr[0]
In this case, I'm checking if each sub-array has a "-" value in it and un-setting the array from the multi array. I am only checking specific keys in the sub-arrays (0, 2, 3) however it outputs an array without any changes. I figured I must have some scoping wrong and tried to use "global" everywhere possible, but that didn't seem to fix it.
Modified your version a bit and handled the return value.
function multi_unset($array, $unset)
{
$retVal = array();
foreach($array as $key => $value)
{
$remove = false;
foreach($unset as $checkKey)
{
if ($value[$checkKey] == "-")
$remove = true;
}
if (!$remove)
$retVal[] = $value;
}
return $retVal;
}
$arr = array(array("-", "test", "test", "test"), array("test", "test", "test", "test"));
$unset = array(0, 2, 3);
$arr = multi_unset($arr, $unset);
print_r($arr);
You may want to do some reading into Passing by Reference vs passing by value in PHP.
Heres some code that works with the given data set....
// Note the pass by reference.
function multi_unset(&$array, $unset) {
foreach($array as $pos => $subArray) {
foreach($unset as $index) {
$found = ("-" == $subArray[$index]);
if($found){
unset($subArray[$index]);
// Ver 2: remove sub array from main array; comment out previous line, and uncomment next line.
// unset($array[$pos]);
}
$array[$pos] = $subArray; // Ver 2: Comment this line out
}
}
//return $array; // No need to return since the array will be changed since we accepted a reference to it.
}
$arr = array(array("-", "test", "test", "test"), array("test", "test", "test", "test"));
$unset = array(0, 2, 3);
multi_unset($arr, $unset);
print_r($arr);
I'm writing a function to take the tags from a mustache template and generate a hash (the reason for this is to be able to take any given template and quickly show a developer what the expected variables are).
I extract the tags into a flat array (easy enough), but the next step is tricky - I need to turn the flat array into a multi-dimensional array to indicate nested variable.
Here's my sample flat array:
$arr = array(
'one',
'#two',
'sub1',
'sub2',
'/two',
'three'
);
And the expected output:
$newArray = array(
'one'=>'',
'two'=>array(
'sub1'=>'',
'sub2'=>''
),
'three'=>''
);
I have been getting close, but am not quite there yet. I thought a recursive function would be the way to go (though I am open to a different solution). Here is what I have so far:
function recurse($array, $i = 0) {
$nested = array();
while ($i < count($array)):
$tag = $array[$i];
if (preg_match('/\//',$tag)) {
return $nested;
} elseif (preg_match('/^#/',$tag)) {
$tag = str_replace('#','',$tag);
$nested[$tag] = recurse($array, $i+1);
$i+= count($nested[$tag])+1;
} else {
$nested[$tag] = '';
$i++;
}
endwhile;
return $nested;
}
I think the bug may be that it hits the first 'if' and returns all the way out of the function, but I'm not certain, nor am I sure how to fix it.
Just for fun I decided to make you one without recursion and using references instead (more efficient that recursion, storing array element aliases on a stack). Works with nested subsets too.
$arr = array(
'one',
'#two','sub1',
'#twotwo','sub1','sub2','/twotwo',
'sub2','/two',
'three'
);
$out = array();
$stack = array();
$sp = 0;
$stack[$sp] = &$out;
foreach ($arr as $item) {
$cur =& $stack[$sp];
if ($item[0] == '#') {
$item = substr($item, 1);
$cur[$item] = array();
$stack[++$sp] = &$cur[$item];
}
elseif ($item[0] == '/') {
$sp--;
}
else {
$cur[] = $item;
}
}
var_dump($out);
Output:
array
0 => string 'one' (length=3)
'two' => &
array
0 => string 'sub1' (length=4)
'twotwo' => &
array
0 => string 'sub1' (length=4)
1 => string 'sub2' (length=4)
1 => string 'sub2' (length=4)
1 => string 'three' (length=5)
You can ignore the fact in the output you see & array in places instead of simply array. This signifies that in the symbol table the reference count for that particular element is > 1.
The reason for this is that $stack is still maintaining a reference. If you do an unset($stack); before returning the output, the additional references are removed and the &s in the output will disappear.
I modified your function a bit to match your needs, see if it works for you:
$arr = array(
'one',
'#two',
'sub1',
'#sub2',
'subsub1',
'subsub2',
'subsub3',
'subsub4',
'/sub2',
'sub3',
'/two',
'three'
);
function recurse($array, &$i, $current_tag = "")
{
$nested = array();
while ($i < count($array)):
$tag = $array[$i];
if ($tag == '/'.$current_tag)
{
$i++;
return $nested;
}
elseif (preg_match('/^#/',$tag))
{
$tag = str_replace('#','',$tag);
$i++;
$nested[$tag] = recurse($array, $i, $tag);
} else
{
$nested[$tag] = '';
$i++;
}
endwhile;
return $nested;
}
$i = 0;
$a = recurse($arr, $i);
echo '<pre>'.print_r($a, true).'</pre>';
You had some issues with that $i... I gave it as reference so that it will automatically update with the function system, and used another parameter to match exactly the next closing tag..., so that it will validate.
Yes, recurse function is the way. Some advices :
Do not include "count" functions in loops when you have not to do (your "$array" is not updated, so his size still the same from the begening to the end)
Do not use preg_match when you have simple comparison to do.
Use references, else you should quickly get a memory error with huge arrays used in recurse functions.
Here an other way to do what you want to :
<?php
function recurse(&$array, &$return = array(), &$i = 0, $limit = NULL)
{
if(!isset($limit)){
$limit = count($array) ;
}
for(;$i < $limit;$i++){
if($array[$i]{0} == '#'){
//opening
$key = substr($array[$i++], 1) ;
$return[$key] = array();
recurse($array, $return[$key], $i, $limit) ;
}elseif($array[$i]{0} == '/'){
return ;
}else{
//same level
$return[$array[$i]] = '';
}
}
}
$arr = array(
'one',
'#two',
'sub1',
'#t2',
'sub1.1',
'sub1.2',
'/t2',
'sub2',
'/two',
'three'
);
$nested = array();
recurse($arr, $nested);
var_dump($nested);
?>
This might be more of what you are looking for (and a little more closer to true recursion), but I didn't test it because I don't have a PHP instance to work off of at the moment
Usage:
$input = array(
'one',
'#two',
'sub1',
'sub2',
'/two',
'three'
);
$result = array();
recurse($input, $result, '', 0);
Steps:
If the position is greater than the array count, we are done.
If we need to go back up to root, remove tag and call again
If we need to go into a tag, add tag and call again
If we are in root, add the key and blank entry
If we are in a tag, add the key to the tag with a blank entry
Code:
function recurse($input, &$result, $tag, $position)
{
if($position >= count($input))
{
return;
}
if(preg_match('#\/#',$input[$position]))
{
recurse($input, $result, '', $position + 1);
}
else if (preg_match('#^##',$input[$position]))
{
$result[substr($input[$position], 1)] = array();
recurse($input, $result, substr($input[$position], 1), $position + 1);
}
else if($tag == '')
{
$result[$input[$position]] = '';
recurse($input, $result, $tag, $position + 1);
}
else
{
$result[$tag][$input[$position]] = '';
recurse($input, $result, $tag, $position + 1);
}
}
Off by one error
$tag = str_replace('#','',$tag);
$nested[$tag] = recurse($array, $i+1);
$i+= count($nested[$tag])+1;
When you return the nested array, you have to skip over the closing tag, so it should be $i += count($nested[$tag]) + 2;.
I want to make a class for parsing flat-file database information into one large analogous multidimensional array. I had the idea of formatting the database in a sort of python-esque format as follows:
"tree #1":
"key" "value"
"sub-tree #1":
"key" "value"
"key #2" "value"
"key #3" "value"
I am trying to make it parse this and build and array while parsing it to throw the keys/values into, and I want it to be very dynamic and expandable. I've tried many different techniques and I've been stumped in each of these attempts. This is my most recent:
function parse($file=null) {
$file = $file ? $file : $this->dbfile;
### character variables
# get values of
$src = file_get_contents($file);
# current character number
$p = 0;
### array variables
# temp shit
$a = array();
# set $ln keys
$ln = array("q"=>0,"k"=>null,"v"=>null,"s"=>null,"p"=>null);
# indent level
$ilvl = 0;
### go time
while (strlen($src) > $p) {
$chr = $src[$p];
# quote
if ($chr == "\"") {
if ($ln["q"] == 1) { // quote open?
$ln["q"] = 0; // close it
if (!$ln["k"]) { // key yet?
$ln["k"] = $ln["s"]; // set key
$ln["s"] = null;
$a[$ln["k"]] = $ln["v"]; // write to current array
} else { // value time
$ln["v"] = $ln["s"]; // set value
$ln["s"] = null;
}
} else {
$ln["q"] = 1; // open quote
}
}
elseif ($chr == "\n" && $ln["q"] == 0) {
$ln = array("q"=>0,"k"=>null,"v"=>null,"s"=>null,"p"=>null);
$llvl = $ilvl;
}
# beginning of subset
elseif ($chr == ":" && $ln["q"] == 0) {
$ilvl++;
if (!array_key_exists($ilvl,$a)) { $a[$ilvl] = array(); }
$a[$ilvl][$ln["k"]] = array("#mbdb-parent"=> $ilvl-1 .":".$ln["k"]);
$ln = array("q"=>0,"k"=>null,"v"=>null,"s"=>null,"p"=>null);
$this->debug("INDENT++",$ilvl);
}
# end of subset
elseif ($chr == "}") {
$ilvl--;
$this->debug("INDENT--",$ilvl);
}
# other characters
else {
if ($ln["q"] == 1) {
$ln["s"] .= $chr;
} else {
# error
}
}
$p++;
}
var_dump($a);
}
I honestly have no idea where to go from here. The thing troubling me most is setting the multidimensional values like $this->c["main"]["sub"]["etc"] the way I have it here. Can it even be done? How can I actually nest the arrays as the data is nested in the db file?
This is all going to depend on how human-readable you want your "flat file" to be.
Want human-readable?
XML
Yaml
Semi-human-readable?
JSON
Not really human-readable?
Serialized PHP (also PHP-only)
Mysql Dump
Writing your own format is going to be painful. Unless you want to do this purely for the academic experience, then I say don't bother.
Looks like JSON might be a happy medium for you.
$configData = array(
'tree #1' => array(
'key' => 'value'
, 'sub-tree #1' => array(
'key' => 'value'
, 'key #2' => 'value'
, 'key #3' => 'value'
)
)
);
// Save config data
file_put_contents( 'path/to/config.json', json_format( json_encode( $configData ) ) );
// Load it back out
$configData = json_decode( file_get_contents( 'path/to/config.json' ), true );
// Change something
$configData['tree #1']['sub-tree #1']['key #2'] = 'foo';
// Re-Save (same as above)
file_put_contents( 'path/to/config.json', json_format( json_encode( $configData ) ) );
You can get the json_format() function here, which just pretty-formats for easier human-reading. If you don't care about human-readability, you can skip it.
Well, you could use serialize and unserialize but that would be no fun, right? You should be using formats specifically designed for this purpose, but for sake of exercise, I'll try and see what I can come up with.
There seems to be two kinds of datatypes in your flatfile, key-value pairs and arrays. key-value pairs are denoted with two sets of quotes and arrays with one pair of quotes and a following colon. As you go through the file, you must parse each row and determine what it represents. That's easy with regular expressions. The hard part is to keep track of the level we're going at and act accordingly. Here's a function that parses the tree you provided:
function parse_flatfile($filename) {
$file = file($filename);
$result = array();
$open = false;
foreach($file as $row) {
$level = strlen($row) - strlen(ltrim($row));
$row = rtrim($row);
// Regular expression to catch key-value pairs
$isKeyValue = preg_match('/"(.*?)" "(.*?)"$/', $row, $match);
if($isKeyValue == 1) {
if($open && $open['level'] < $level) {
$open['item'][$match[1]] = $match[2];
} else {
$open = array('level' => $level - 1, 'item' => &$open['parent']);
if($open) {
$open['item'][$match[1]] = $match[2];
} else {
$result[$match[1]] = $match[2];
}
}
// Regular expression to catch arrays
} elseif(($isArray = preg_match('/"(.*?)":$/', $row, $match)) > 0) {
if($open && $open['level'] < $level) {
$open['item'][$match[1]] = array();
$open = array('level' => $level, 'item' => &$open['item'][$match[1]], 'parent' => &$open['item']);
} else {
$result[$match[1]] = array();
$open = array('level' => $level, 'item' => &$result[$match[1]], 'parent' => false);
}
}
}
return $result;
}
I won't go into greater detail on how that works, but it short, as we progress deeper into the array, the previous level is stored in a reference $open and so on. Here's a more complex tree using your notation:
"tree_1":
"key" "value"
"sub_tree_1":
"key" "value"
"key_2" "value"
"key_3" "value"
"key_4" "value"
"key_5" "value"
"tree_2":
"key_6" "value"
"sub_tree_2":
"sub_tree_3":
"sub_tree_4":
"key_6" "value"
"key_7" "value"
"key_8" "value"
"key_9" "value"
"key_10" "value"
And to parse that file you could use:
$result = parse_flatfile('flat.txt');
print_r($result);
And that would output:
Array
(
[tree_1] => Array
(
[key] => value
[sub_tree_1] => Array
(
[key] => value
[key_2] => value
[key_3] => value
)
[key_4] => value
[key_5] => value
)
[tree_2] => Array
(
[key_6] => value
[sub_tree_2] => Array
(
[sub_tree_3] => Array
(
[sub_tree_4] => Array
(
[key_6] => value
[key_7] => value
[key_8] => value
[key_9] => value
[key_10] => value
)
)
)
)
)
I guess my test file covers all the bases, and it should work without breaking. But I won't give any guarantees.
Transforming a multidimensional array to flatfile using this notation will be left as an exercise to the reader :)