I was surprised not to find an answer to this on SO (or elsewhere on the internet for that matter). It concerns a nested indented list which I want to convert into a multidimensional array according to the level of indentation.
By way of an example, here is some sample input:
Home
Products
Product 1
Product 1 Images
Product 2
Product 2 Images
Where to Buy
About Us
Meet the Team
Careers
Contact Us
Ideally I'd like to feed this into some (recursive?) function and get the following output:
array(
'Home' => array(),
'Products' => array(
'Product 1' => array(
'Product 1 Images' => array(),
),
'Product 2' => array(
'Product 2 Images' => array(),
),
'Where to Buy' => array(),
),
'About Us' => array(
'Meet the Team' => array(),
'Careers' => array(),
),
'Contact Us' => array(),
);
I'm confused by the logic required to perform such a task, so any help would be appreciated.
As it's still unclear if you're trying to read from some given structure (html-dom) or from the given string as plain text, I assumed it's the string you're trying to parse. If so, try:
<?php
$list =
'Home
Products
Product 1
Product 1 Images
Product 2
Product 2 Images
Where to Buy
About Us
Meet the Team
Careers
Contact Us';
function helper($list, $indentation = ' ') {
$result = array();
$path = array();
foreach (explode("\n", $list) as $line) {
// get depth and label
$depth = 0;
while (substr($line, 0, strlen($indentation)) === $indentation) {
$depth += 1;
$line = substr($line, strlen($indentation));
}
// truncate path if needed
while ($depth < sizeof($path)) {
array_pop($path);
}
// keep label (at depth)
$path[$depth] = $line;
// traverse path and add label to result
$parent =& $result;
foreach ($path as $depth => $key) {
if (!isset($parent[$key])) {
$parent[$line] = array();
break;
}
$parent =& $parent[$key];
}
}
// return
return $result;
}
print_r(helper($list));
Demo: http://codepad.org/zgfHvkBV
I'm not going to write the recursive function, but to point you in a helpful direction, take a look at PHP's substr_count function. Based on this, you could count the number of tabs in front of each line and compare it with the number of tabs in the previous line to figure out if it's a child, sibling, etc.
Am I the only one with a beautiful mind seeing the pattern?
The input array is almost the same thing!, with the lines ending in only 3 possible ways: opening, whole, or closing parentheses.
$L = 4;//estimate depth level
function tabbed_text_to_array ($raw, $L) {
$raw = preg_replace("/^(\\t*)([^\\t\\n]*)\\n?/m" , "\t$1'$2' => array(\n" , $raw );
for( ; $L > 0 ; $L-- ) {
for( $i=0; $i<3 ;$i++ ) {
$preL = $L-1;
$s = array( "^(\\t{{$L}})([^\\t\\),]*)(?=\\n\\t{{$L}}')", "^(\\t{{$L}})([^\\t\\),]*)(?=\\n(\\t{0,{$preL}})')", "^(\\t{{$L}})(\\),)(?=\\n(\\t{{$preL}})[^\t])" );
$r = array( "$1$2)," , "$1$2)\n" . str_repeat("\t",$preL) . ")," , "$1),\n$3)," );
$raw = preg_replace( "/$s[$i]/m" , $r[$i], $raw );
}
}
return "array(\n". $raw. ")\n);";
}
This function generates a string with a literal array. Then you just eval() it.
It's not as bad as it looks. The double backslashes makes the reading harder, but it's simple.
Like cell reproduction, it first adds in one pass the most common things: the quotation marks, commas and opening parentheses, and then it adds the smaller details in two more passes. All of them run once for each level you specify (You could waste code in finding out the deepest level to start processing from, but a guess is enough.)
See the working demo / tool to convert tab-indented text into array
Or visit a php fiddle with all the passes, so you can expand on this.
There is a class on PHP Scripts that will do what you need. You can find it here:
Array To List
It takes a multidimensional array and creates the HTML.
Updated
The opposite to this was actually required. To do this, it's probably best to use a DOMDocument object and load the HTML into a object representation.
http://php.net/manual/en/domdocument.loadhtml.php
Related
I have an app where users can customize the product they are going to purchase by choosing options from a menu. This menu has many sections, and each section may have a list of checkboxes for multichoice, or radiobuttons when only one option can be selected. The user must select at least one option in each section. The menu structure is something like this:
$sections = array();
$sections[1] = array(
'multichoice' => true,
'options' => array('A','B','C')
);
$sections[2] = array(
'multichoice' => false,
'options' => array('A','B','C','D')
);
$sections[3] = array(
'multichoice' => false,
'options' => array('A','B')
);
$sections[4] = array(
'multichoice' => true,
'options' => array('A','B','C','D','E')
);
Example: Sandwich is the product. Type of bread is one "section" of choice. You may want light bread, dark bread, milk bread or vegan bread. Only one option can be chosen under this section. Now in the "salad" section, you may choose more than one type of salad to add to the bread.
Now, my boss asked for me to create a page listing all possible combinations in case the user is too lazy to build the product himself. So I must be able to generate a structure like this:
$combinations = array(
array(
1 => array('A','B'),
2 => 'A',
3 => 'A',
4 => array('B','D','E')
),
array(
1 => array('A'),
2 => 'B',
3 => 'A',
4 => array('A','B')
)
// etc...
);
I have managed to find all possible combinations using a random approach, generating hashes for comparision with what has already been generated. This actually works, but runs very slowly (this is brute-force basically):
...
function generate(){
$result = array();
$ids = array();
foreach($this->getSections() as $sect){
$items = $this->getSectionOptions($sect['id']);
if($sect['multi']=='N'){
$item = $items[rand(0, count($items)-1)];
$result[$sect['id']] = $item['id'];
$ids[] = $item['id'];
} else {
$how_many = rand(1,count($items));
shuffle($items);
for($i=1;$i<=$how_many;$i++){
$item = array_shift($items);
$result[$sect['id']][] = $item['id'];
$ids[] = $item['id'];
}
}
}
sort($ids);
return array(
'hash' => implode(',',$ids),
'items' => $result
);
}
function generateMany($attempts=1000){
$result = array();
$hashes = array();
for($i=1;$i<=$attempts;$i++){
$combine = $this->generate();
if(!in_array($combine['hash'],$hashes)){
$result[] = $combine['items'];
$hashes[] = $combine['hash'];
}
}
return $result;
}
...
I want your help to create something more precise and faster. Remember that each combination must have at least one option of each section. And also keep in mind that the order of options in multichoice sections is irrelevant, (i.e. E,B,A is the same as B,E,A)
Thanks
Thanks this was a puzzle really fun to do!
So how did I solve, recursion recursion recursion :D
I've started with the multi choice since it is the hardest one! (and actually it will solve also the mixing)
Explanation
To explain how I got it to work, lets take an example with choices A, B, C. We would have then the following combinations:
A B
A B C
A C
B
B C
C
If we take a look closer, we can see some how a pattern. Lets take the result list the first element (A)
B
B C
C
---
B
B C
C
Hmm, interesting... Now lets take again the first element (B)
C
---
C
It's a simple case but this will happen in any size case.
So I've made the recursion script to get to the end, then adding backwards, the iteration combination and duplicating it with the previous value.
And voila! this is it!
For the final mix where all the elements are demanded, I've made a very similar method, but it must contain 1 element of each
And it's quite fast!
Total time for 100000 iterations with 126 combinations: 14.410287857056 seconds
if you find any mistake ping me :D
Code
https://gist.github.com/MLoureiro/a0ecd1ef477e08b6b83a
So I know there are articles out there about custom ordering but I think my situation may be a little different (Or could just be my workaround sucks :P ). Here is the quick scenario.
I pull XML data from curl off a report url that we have. (not properly formatted!) So let's use the following example: I get the following data back in one long list.
<Site>My Sites</Site>
<Customer>PHPSTEVE</Customer>
<Name>Griswald</Name>
<Site> Evil Dead</Site>
<Customer>Bruce</Customer>
<Name>Campbell</Name>
I can't use the XML function because the format header is incorrect so I won't get into that.. So instead I use a preg_match_all
preg_match_all("/<Site>(.*?)<\/Site>|<Customer>(.*?)<\/Customer>|<Name>(.*?)<\/Name>/is", $resp, $output_array[]);
Then I go through and I know this is not good but it works..
foreach(array_reverse($output_array[0][0]) as $info){
$myinfo = $info . $myinfo;
}
Everything works as I use a str_replace and just put in my table tags etc.. Obviously I'm doing the reading in reverse.. it's ok I want it that way for this.
Site | Customer | Name
----------+----------+------
Evil Dead | Bruce | Campbell
What if I want "Customer" field first? So basically I'm changing the horizontal order of Site and Customer etc... Remember there are multiple records all in a line so I am trying to figure out how I would move Customer to where I want it.. or any field for that matter. Based off of my code above, I don't believe I can accomplish it the way I want to so I'm not sure of another way to do it.
I would like to report it as this:
Customer | Site | Name
---------+-----------+------
Bruce | Evil Dead | Campbell
Any help would be appreciated. NOTE: I don't care about reading bottom up or top down.. just field changes. Thanks
UPDATE: The XMl issue I run into is:
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<results>
<Site>My Sites</Site>
<Customer>PHPSTEVE</Customer>
<Name>Griswald</Name>
<Site> Evil Dead</Site>
<Customer>Bruce</Customer>
<Name>Campbell</Name>
</results>
Maybe something like this?
function sortCustomerKeys($a, $b)
{
if ( $a == 'Customer' && $b != 'Customer' ) {
return 1;
} else if ( $a != 'Customer' && $b == 'Customer' ) {
return -1;
}
return strnatcasecmp($a, $b);
}
$customers = array();
$advance = array(
'Customer' => 0,
'Name' => 0,
'Site' => 0
);
if ( preg_match_all('#<(Customer|Name|Site)>\\s*(.*?)\\s*</\\1>#is', $resp, $m) ) {
for ($i = 0, $j = 0; $i < count($m[1]); $i++) {
$tag = $m[1][$i];
$value = $m[2][$i];
$customers[$j][$tag] = $value;
$advance[$tag] = 1;
if ( array_sum($advance) == count($advance) ) {
$advance = array(
'Customer' => 0,
'Name' => 0,
'Site' => 0
);
uksort($customers[$j], 'sortCustomerKeys');
$j++;
}
}
}
var_dump($customers);
I'm creating a multi-dimensional array to match the hierarchy of a company. I ask the database for the downline (saved in a seperate table) and start looping it. Currently I'm only doing this for the highest level and the people below him. I know he has 6 people below him, thus the end result only shows one. When I var_dump $complete every time in the loop, I see different values in the children-array. (AKA he overwrites instead of adds up).
I've tried both array_push and this method, I hope I've been clear enough, because I probably don't (sorry.)
The code:
foreach ($downpositions as $downposition) {
// start position
if ($downposition['PositionsDownposition']['positionsdownpositions_rel_position'] == 1) {
// check downline only on first level
if ($downposition['PositionsDownposition']['positionsdownpositions_level'] == 1) {
foreach ($downpositions as $mother) {
if ($mother['PositionsDownposition']['positionsdownpositions_rel_downposition'] == 1) {
// format it
$node = array(
'id' => $downposition['PositionsDownposition']['positionsdownpositions_id'],
'name' => $downposition['PositionsDownposition']['positionsdownpositions_rel_position'],
'children' => array()
);
$complete = array(
'id' => $mother['PositionsDownposition']['positionsdownpositions_id'],
'name' => $mother['PositionsDownposition']['positionsdownpositions_rel_position'],
'children' => array()
);
// add up to array
$complete['children'][] = $node;
}
}
}
}
}
Found the problem, im intialising $complete['children'] in the loop, thus resetting the array to empty all the time.
Solution:
Re-do the code, initialise of $complete outside the loop.
I'm pretty new to PHP so bear with me here. I'm trying to iterate through the words in a string of text, look for specific words, categorize them, and then count the number of times each word category was hit. I was able to do the easy part but I'm having problems counting the number of times each category is matched. Here's the main function that accepts my string:
public function matchThemeTest($query){
$marriageNum = 0;
$criminalNum = 0;
$contactNum = 0;
$keywords = array(
'background'=> array('category'=>'criminal'),
'marriage' => array('category'=>'marriage'),
'criminal' => array('category'=>'criminal'),
'arrest' => array('category'=>'criminal'),
'divorce' => array('category'=>'marriage'),
'person' => array('category'=>'contact'),
'contact' => array('category'=>'contact')
);
foreach (preg_split("/\s/", $query) as $word)
{
if (isset($keywords[$word]))
{
echo $keywords[$word]['category'];
if ($keywords[$word]['category'] == 'marriage') {
$marriageNum++;
}
echo $marriageNum;
}
}
//return reset($matches);
}
I've got a php fiddle setup here: http://phpfiddle.org/main/code/i4g-mdu that I've been playing around with. In it's current form, I can get the words into categories but I'm not sure how to count how many times each category gets matched. I feel like I need an additional loop or something simple to count but I'm not exactly sure where. Any advice is greatly appreciated. Thanks in advance.
You may need another array of data, to store the counts. Use an array like this:
$counts = array(
'criminal' => 0,
'marriage' => 0,
'contact' => 0
);
Then when you iterate through your foreach loop, you can use the $keywords[$word]['category'] as the key in $counts and increment it:
if(isset($keywords[$word]) {
$counts[$keywords[$word]['category']]++;
}
Then you can return the $counts array so the caller can use it to find out what the counts of each theme were:
return $counts;
Note: While this is probably a simple fix, I'm new to using arrays and am completely stumped.
I'm trying to save the data from a shortcode array via the Options API in WordPress, then call that array and use the data to create another array to hook into a plugin's function. It's a responsive slider plugin and I'm basically trying to attach a shortcode to it so I can create the slider on the backend and display it on the front end with a shortcode that looks like: [responsive_slider slider_name="imageslider"].
The implementation documentation can be found here, and here's my code:
function responsive_gallery_shortcode($atts, $content=null) {
extract(shortcode_atts( array('slider_name' => 'product_page') , $atts));
foreach ($slider_name as $value) {
update_option('_unique_slider_name', $value );
}
if(function_exists('show_flexslider_rotator'))
echo show_flexslider_rotator( $slider_name );
add_image_size( $slider_name , '550', '250', true );
}
add_shortcode('responsive_gallery', 'responsive_gallery_shortcode');
if (!function_exists('custom_set_flexslider_hg_rotators')) {
function custom_set_flexslider_hg_rotators() {
$slider_name = get_option('_unique_slider_name');
foreach ($slider_name as $value) {
$rotators = array();
$rotators[ $value ] = array( 'size' => $value );
return $rotators;
}
}
}
add_filter('flexslider_hg_rotators', 'custom_set_flexslider_hg_rotators', 9999);
I'm getting an "Invalid argument supplied for foreach()" error on both foreach functions. On the page where I have two shortcodes both errors show up twice. It seems as though $slider_name is a string instead of an array, but there's got to be a way to save it in the update_option() function so that it returns an array. I'm quite new to arrays, and I'm definitely struggling here. I've spent hours on this and have already received a little help on the WordPress side, but I'm not quite getting it.
As the shortcode attribute will arrive as a string, you need to convert it to an array first.
At the same time, as it has to be passed as a string, you'll need to use a separator so you can manage this.
And for all that, you'll need the PHP function explode.
$string = "one,two";
$array = explode( ',', $string );
var_dump( $array );
Results in:
array (size=2)
0 => string 'one' (length=3)
1 => string 'two' (length=3)
And
$string = "one";
$array = explode( ',', $string );
var_dump( $array );
Results in:
array (size=1)
0 => string 'one' (length=3)
PS: It's always worth to consult the PHP Manual and also the comments in each of its pages : http://www.php.net/manual/en/language.types.array.php
[update]
There are many issues with your original code, check the comments of this revised version:
function responsive_gallery_shortcode($atts, $content=null) {
extract(shortcode_atts( array('slider_name' => 'product_page') , $atts));
// Convert string into array
// Using comma as separator when writing the shortcode in the post
$array_slider = explode( ',', $slider_name );
// YOU DON'T NEED A `foreach` HERE
//foreach ($array_slider as $value) {
update_option('_unique_slider_name', $array_slider );
//}
// I DON'T KNOW WHAT THIS FUNCTIONS DOES
// But in any case, being $array_slider an array, maybe it should be inside a `foreach`
if(function_exists('show_flexslider_rotator'))
echo show_flexslider_rotator( $array_slider );
// THIS DOESN'T MAKE SENSE
// You're not supposed to be adding images sizes at each Shortcode call
// And you are dealing with an array
add_image_size( $slider_name , '550', '250', true );
}
add_shortcode('responsive_gallery', 'responsive_gallery_shortcode');
if (!function_exists('custom_set_flexslider_hg_rotators')) {
function custom_set_flexslider_hg_rotators() {
// The option was already saved as array, so we can work directly with it
$slider_name = get_option('_unique_slider_name');
// YOU DON'T WANT TO DECLARE THE VARIABLE AT EACH STEP OF THE LOOP
$rotators = array();
foreach ($slider_name as $value) {
$rotators[ $value ] = array( 'size' => $value );
}
// RETURN THE VALUE ONLY AFTER COMPLETING THE LOOP
return $rotators;
}
// PUT THE FILTER HOOK INSIDE if(!function_exists())
add_filter('flexslider_hg_rotators', 'custom_set_flexslider_hg_rotators', 9999);
}