PHP Aligning multiple arrays of different lengths - php

I'm probably just overlooking the obvious but I'd like to blame it on the fact that I'm new to PHP.
I have some number of arrays being returned with similar information but differing amounts of it.
I'll put some example arrays below:
(t1-s1-1=1, t1-s1-2=1, t1-s2-1=1, t1-s2-2=1)
(t2-s1-1=1, t2-s2-1=2, t2-s2-2=1)
(t3-s1-1=1, t3-s2-1=1, t3-s3-1=1, t3-s3-2=3)
So I would like to make a table out of this information. Something like this:
test .. s1-1 .. s1-2 .. s2-1 .. s2-2 .. s3-1 .. s3-2
t1 ........1 .....1 ..........1 ....... 1.........1..........1
t2 ........1 .......X..........1..........1........1..........1
t3 ........1 .......X..........1..........X........1..........1
( where x is something that wasn't there. )
So every array has an s1 but could have s1-1, s1-2, s1-3 or simply s1-1. That creates very different sized arrays.
The problem is that each array can have wildly different information and because they are Indexed arrays instead of Associative arrays I'm not sure how to best equalize them. I can't consistently say index 3 is s1-3 or something else.
I can't just loop through manually because I never know where a gap will appear. I can't look for specific indexes because the arrays aren't associative so the titles are built into the value and I don't know how to access them separately.
Any good ideas out there that maybe a newbie is overlooking? I'm open to non-tabular display ideas as well as long as I can easily sort and display the information.
Thanks

I'm assuming your original arrays contain values as string, so for instance, in PHP syntax, they look like:
['t1-s1-1=1', 't1-s1-2=1', 't1-s2-1=1', 't1-s2-2=1']
Basically, you should create a bi-dimensional array:
go through all arrays and by using a regex extract the different parts, that is, for the first element in the array above: t1 (the index for the first level in the bi-dimensional array), s1-1 (the index for the second level in the bi-dimensional array) and the value 1
insert the value in the bi-dimensional array
keep in a separate array, let's call it allColumns every second index above (sx-y), even you will have duplicate values you can, at the end, delete those duplicate and order it alphabetically
After that, you will have all the value in the bi-dimensional array but you still miss the gaps, so what you can do it iterate over the bi-dimensional array, and for every dimension tz (t1, t2,...), go through for all the values stored in allColumns and if you don't find the entry for that sx-y in the bi-dimensional array for that tz, add it with value x (or probably with value = 0)
I think an example can clarify the above:
// arrays of arrays, I don't know how you receive the data
$arrays = [
['t1-s1-1=1', 't1-s1-2=1', 't1-s2-1=1', 't1-s2-2=1'],
['t2-s1-1=1', 't2-s2-1=2', 't2-s2-2=1'],
['t3-s1-1=1', 't3-s2-1=1', 't3-s3-1=1', 't3-s3-2=3']
];
// bi-dimensional array
$output = [];
// it will store all columns you find in the $arrays entry
$allColumns = [];
// iterate for every array you receive, i.e. ['t1-s1-1=1', 't1-s1-2=1', 't1-s2-1=1', 't1-s2-2=1']
foreach ($arrays as $array) {
// iterate over every element in the array: 't1-s1-1=1', 't1-s1-2=1', 't1-s2-1=1' and 't1-s2-2=1'
foreach ($array as $item) {
// extract the parts on every element: $matches is an array containing the different parts
preg_match('/^(t\d+)-(s\d+-\d+)=(\d+)/', $item, $matches);
/**
* $matches[0] would contains the element if matched: 't1-s1-1=1'
* $matches[1] would contains 't1' if matched
* $matches[2] would contains 's1-1' if matched
* $matches[2] would contains 1 (integer) if matched
*/
if (!empty($matches)) {
$output[$matches[1]][$matches[2]] = $matches[3];
$allColumns[] = $matches[2];
}
}
}
// clean duplicates
$allColumns = array_unique($allColumns);
// sort values alphabetically
sort($allColumns);
// iterate over the just created bi-dimensional array
foreach ($output as $row => $columns) {
// iterate for all columns collected before
foreach ($allColumns as $column) {
// if one of column in 'allColumns' doesn't exit in $output you added in the correct place adding a zero value
if (!in_array($column, array_keys($columns))) {
$output[$row][$column] = 0;
}
}
}
To print the output you should only iterate over $ouput
This will be the array internally:
(
[t1] => Array
(
[s1-1] => 1
[s1-2] => 1
[s2-1] => 1
[s2-2] => 1
[s3-1] => 0
[s3-2] => 0
)
[t2] => Array
(
[s1-1] => 1
[s2-1] => 2
[s2-2] => 1
[s1-2] => 0
[s3-1] => 0
[s3-2] => 0
)
[t3] => Array
(
[s1-1] => 1
[s2-1] => 1
[s3-1] => 1
[s3-2] => 3
[s1-2] => 0
[s2-2] => 0
)
)
It exists other ways to implement the above, like skip the step where you fill the gaps and do it on the fly, ...
Updated
The simplest way to display the results in a HTML page is by embedding a php script to iterate over the associative array and compose the HTML table (I encourage you to study and research MVC to separate logic from the view)
<!DOCTYPE html>
<?php
// arrays of arrays, I don't know how you receive the data
$arrays = [
['t1-s1-1=1', 't1-s1-2=1', 't1-s2-1=1', 't1-s2-2=1'],
['t2-s1-1=1', 't2-s2-1=2', 't2-s2-2=1'],
['t3-s1-1=1', 't3-s2-1=1', 't3-s3-1=1', 't3-s3-2=3']
];
// bi-dimensional array
$output = [];
// it will store all columns you find in the $arrays entry
$allColumns = [];
// iterate for every array you receive, i.e. ['t1-s1-1=1', 't1-s1-2=1', 't1-s2-1=1', 't1-s2-2=1']
foreach ($arrays as $array) {
// iterate over every element in the array: 't1-s1-1=1', 't1-s1-2=1', 't1-s2-1=1' and 't1-s2-2=1'
foreach ($array as $item) {
// extract the parts on every element: $matches is an array containing the different parts
preg_match('/^(t\d+)-(s\d+-\d+)=(\d+)/', $item, $matches);
/**
* $matches[0] would contains the element if matched: 't1-s1-1=1'
* $matches[1] would contains 't1' if matched
* $matches[2] would contains 's1-1' if matched
* $matches[2] would contains 1 (integer) if matched
*/
if (!empty($matches)) {
$output[$matches[1]][$matches[2]] = $matches[3];
$allColumns[] = $matches[2];
}
}
}
// clean duplicates
$allColumns = array_unique($allColumns);
// sort values alphabetically
sort($allColumns);
// iterate over the just created bi-dimensional array
foreach ($output as $row => $columns) {
// iterate for all columns collected before
foreach ($allColumns as $column) {
// if one of column in 'allColumns' doesn't exit in $output you added in the correct place adding a zero value
if (!in_array($column, array_keys($columns))) {
$output[$row][$column] = 0;
}
}
}
?>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Table Page</title>
</head>
<body>
<table>
<thead>
<?php
echo '<tr><th>Test</th>';
foreach ($allColumns as $head) {
echo sprintf('<th>%s</th>', $head);
}
echo '</tr>';
?>
</thead>
<tbody>
<?php
foreach ($output as $key => $columns) {
echo sprintf('<tr><td>%s</td>', $key);
foreach ($columns as $column) {
echo sprintf('<td>%s</td>', $column);
}
echo '</tr>';
}
?>
</tbody>
</table>
</body>
</html>

Try the following:
$final_array = array();
$temp_array = array();
foreach ($t1 as $t) {
$isin = 0;
$expression = substr($t, 0, strpos($t, "="));
$expression = str_replace("t1-", "" , $expression)
$value = substr($t, strpos($t, "=") + 1);
for ($i = 0; $i < 3; $i++) {
foreach ($x = 0; $x < 3; $x++) {
if ($expression == "s{$i}-{$x}") {
$isin = 1;
array_push($temp_array, $value);
}
}
}
if ($isin == 0) { array_push($temp_array, "X"); }
}
array_push($final_array, $temp_array);
It's not a great solution because you're choosing to do this in a really odd way but you should see the gist of what how to get what you want from this example.

Related

Sort a flat array in recurring ascending sequences

I am trying to sort it in a repeating, sequential pattern of numerical order with the largest sets first.
Sample array:
$array = [1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3];
In the above array, I have the highest value of 5 which appears twice so the first two sets would 1,2,3,4,5 then it would revert to the second, highest value set etc.
Desired result:
[1,2,3,4,5,1,2,3,4,5,1,2,3,4,1,2,4]
I am pretty sure I can split the array into chunks of the integer values then cherrypick an item from each subarray sequentially until there are no remaining items, but I just feel that this is going to be poor for performance and I don't want to miss a simple trick that PHP can already handle.
Here's my attempt at a very manual loop using process, the idea is to simply sort the numbers into containers for array_unshifting. I'm sure this is terrible and I'd love someone to do this in five lines or less :)
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
// Build the container array
$numbers = array_fill_keys(array_unique($array),array());
// Assignment
foreach( $array as $number )
{
$numbers[ $number ][] = $number;
}
// Worker Loop
$output = array();
while( empty( $numbers ) === false )
{
foreach( $numbers as $outer => $inner )
{
$output[] = array_shift( $numbers[ $outer ] );
if( empty( $numbers[ $outer ] ) )
{
unset( $numbers[ $outer ] );
}
}
}
var_dump( $output );
I think I'd look at this not as a sorting problem, but alternating values from multiple lists, so rather than coming up with sets of distinct numbers I'd make sets of the same number.
Since there's no difference between one 1 and another, all you actually need is to count the number of times each appears. It turns out PHP can do this for you with aaray_count_values.
$sets = array_count_values ($input);
Then we can make sure the sets are in order by sorting by key:
ksort($sets);
Now, we iterate round our sets, counting down how many times we've output each number. Once we've "drained" a set, we remove it from the list, and once we have no sets left, we're all done:
$output = [];
while ( count($sets) > 0 ) {
foreach ( $sets as $number => $count ) {
$output[] = $number;
if ( --$sets[$number] == 0 ) {
unset($sets[$number]);
}
}
}
This algorithm could be adapted for cases where the values are actually distinct but can be put into sets, by having the value of each set be a list rather than a count. Instead of -- you'd use array_shift, and then check if the length of the set was zero.
You can use only linear logic to sort using php functions. Here is optimized way to fill data structures. It can be used for streams, generators or anything else you can iterate and compare.
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
$chunks = [];
$index = [];
foreach($array as $i){
if(!isset($index[$i])){
$index[$i]=0;
}
if(!isset($chunks[$index[$i]])){
$chunks[$index[$i]]=[$i];
} else {
$chunks[$index[$i]][] = $i;
}
$index[$i]++;
}
$result = call_user_func_array('array_merge', $chunks);
print_r($result);
<?php
$array = array(1,1,1,2,3,2,3,4,5,4,4,4,5,1,2,2,3);
sort($array);
while($array) {
$n = 0;
foreach($array as $k => $v) {
if($v>$n) {
$result[] = $n = $v;
unset($array[$k]);
}
}
}
echo implode(',', $result);
Output:
1,2,3,4,5,1,2,3,4,5,1,2,3,4,1,2,4
New, more elegant, more performant, more concise answer:
Create a sorting array where each number gets its own independent counter to increment. Then use array_multisort() to sort by this grouping array, then sort by values ascending.
Code: (Demo)
$encounters = [];
foreach ($array as $v) {
$encounters[] = $e[$v] = ($e[$v] ?? 0) + 1;
}
array_multisort($encounters, $array);
var_export($array);
Or with a functional style with no global variable declarations: (Demo)
array_multisort(
array_map(
function($v) {
static $e;
return $e[$v] = ($e[$v] ?? 0) + 1;
},
$array
),
$array
);
var_export($array);
Old answer:
My advice is functionally identical to #El''s snippet, but is implemented in a more concise/modern/attractive fashion.
After ensuring that the input array is sorted, make only one pass over the array and push each re-encountered value into its next row of values. The $counter variable indicates which row (in $grouped) the current value should be pushed into. When finished looping and grouping, $grouped will have unique values in each row. The final step is to merge/flatten the rows (preserving their order).
Code: (Demo)
$grouped = [];
$counter = [];
sort($array);
foreach ($array as $v) {
$counter[$v] = ($counter[$v] ?? -1) + 1;
$grouped[$counter[$v]][] = $v;
}
var_export(array_merge(...$grouped));

Removing certain array element in a for-loop in PHP

I have inserted some elements (fruit names) queried from mySQL into an array. Now, I would like to remove certain items from the array. I want to remove 'Apple' and 'Orange' if the exist from the array. This is what I tried but I am getting an error message.
Array Example:
Array ( [1] => Orange [2] => Apple)
foreach($terms as $k => $v)
{
if (key($v) == "Apple")
{
unset($terms[$k]);
}
elseif( key($v) == "Orange")
{
unset($terms[$k]);
}
}
>>> Warning: key() expects parameter 1 to be array, string given //same error repeated 4 times
I referred to this link here: How do you remove an array element in a foreach loop?
I would be grateful if anyone can point out what I did wrong.
Have you tried it this way:
foreach($terms as $k => $v)
{
if ($v == "Apple")
{
unset($terms[$k]);
}
elseif($v == "Orange")
{
unset($terms[$k]);
}
}
Explanation:
The $fr is your actual array of all fruits.. and your $rm is another array that contains list of items to be removed from your $fr array.
Using a foreach cycle through the $rm array and see if the element exists on the $fr array , if found, unset() it.
The code...
<?php
$fr = array('Apple','Orange','Pineapple'); //<-- Your actual array
$rm = array('Apple','Orange'); //<--- Elements to be removed
foreach($rm as $v)
{
if(in_array($v,$fr))
{
unset($fr[array_search($v,$fr)]);
}
}
print_r($fr);
OUTPUT :
Array
(
[2] => Pineapple
)
Using array_diff()
print_r(array_diff($fr,$rm));
Code Demonstration

Finding entry in array and putting it first or swapping with the first array entry

I have a PHP array full of arrays and what I want to do is search through this array for the chosen entry and swap it with the first entry in the array if that makes sense...
So for my example below I have chosen Penny so I would like her to either go before Bob or swap places with Bob.
My array looks like this:
$people = array(
array('Bob', 'Wilson'),
array('Jill', 'Thompson'),
array('Penny', 'Smith'),
array('Hugh', 'Carr')
);
I have tried using array_search but I don't think I am doing it correctly.
for ($i = count($array) - 1; $i > 0; $i--) {
if ($array[$i][0] == $first_name) { // Or by whatever you want to search? in_array...?
$searched_array = array_splice($array, $i, 1);
array_unshift($array, $searched_array[0]);
}
}
This is for prepending. If you want to swap, see the answer of #IAmNotProcrastinating
function swap (&$ary,$fromIndex,$toIndex=0)
{
$temp=$ary[$toIndex];
$ary[$toIndex]=$ary[$fromIndex];
$ary[$fromIndex]=$temp;
}
foreach ($elements as $key => $element) {
/* do the search, get the $key and swap */
}

how to skip elements in foreach loop

I want to skip some records in a foreach loop.
For example, there are 68 records in the loop. How can I skip 20 records and start from record #21?
Five solutions come to mind:
Double addressing via array_keys
The problem with for loops is that the keys may be strings or not continues numbers therefore you must use "double addressing" (or "table lookup", call it whatever you want) and access the array via an array of it's keys.
// Initialize 25 items
$array = range( 1, 25, 1);
// You need to get array keys because it may be associative array
// Or it it will contain keys 0,1,2,5,6...
// If you have indexes staring from zero and continuous (eg. from db->fetch_all)
// you can just omit this
$keys = array_keys($array);
for( $i = 21; $i < 25; $i++){
echo $array[ $keys[ $i]] . "\n";
// echo $array[$i] . "\n"; // with continuous numeric keys
}
Skipping records with foreach
I don't believe that this is a good way to do this (except the case that you have LARGE arrays and slicing it or generating array of keys would use large amount of memory, which 68 is definitively not), but maybe it'll work: :)
$i = 0;
foreach( $array as $key => $item){
if( $i++ < 21){
continue;
}
echo $item . "\n";
}
Using array slice to get sub part or array
Just get piece of array and use it in normal foreach loop.
$sub = array_slice( $array, 21, null, true);
foreach( $sub as $key => $item){
echo $item . "\n";
}
Using next()
If you could set up internal array pointer to 21 (let's say in previous foreach loop with break inside, $array[21] doesn't work, I've checked :P) you could do this (won't work if data in array === false):
while( ($row = next( $array)) !== false){
echo $row;
}
btw: I like hakre's answer most.
Using ArrayIterator
Probably studying documentation is the best comment for this one.
// Initialize array iterator
$obj = new ArrayIterator( $array);
$obj->seek(21); // Set to right position
while( $obj->valid()){ // Whether we do have valid offset right now
echo $obj->current() . "\n";
$obj->next(); // Switch to next object
}
$i = 0;
foreach ($query)
{
if ($i++ < 20) continue;
/* php code to execute if record 21+ */
}
if want to skipped some index then make an array with skipped index and check by in_array function inside the foreach loop if match then it will be skip.
Example:
//you have an array like that
$data = array(
'1' => 'Hello world',
'2' => 'Hello world2',
'3' => 'Hello world3',
'4' => 'Hello world4',
'5' => 'Hello world5',// you want to skip this
'6' => 'Hello world6',// you want to skip this
'7' => 'Hello world7',
'8' => 'Hello world8',
'9' => 'Hello world8',
'10' => 'Hello world8',//you want to skip this
);
//Ok Now wi make an array which contain the index wich have to skipped
$skipped = array('5', '6', '10');
foreach($data as $key => $value){
if(in_array($key, $skipped)){
continue;
}
//do your stuf
}
You have not told what "records" actually is, so as I don't know, I assume there is a RecordIterator available (if not, it is likely that there is some other fitting iterator available):
$recordsIterator = new RecordIterator($records);
$limited = new LimitIterator($recordsIterator, 20);
foreach($limited as $record)
{
...
}
The answer here is to use foreach with a LimitIterator.
See as well: How to start a foreach loop at a specific index in PHP
I'm not sure why you would be using a foreach for this goal, and without your code it's hard to say whether this is the best approach. But, assuming there is a good reason to use it, here's the smallest version I can think of off the top of my head:
$count = 0;
foreach( $someArray as $index => $value ){
if( $count++ < 20 ){
continue;
}
// rest of foreach loop goes here
}
The continue causes the foreach to skip back to the beginning and move on to the next element in the array. It's extremely useful for disregarding parts of an array which you don't want to be processed in a foreach loop.
for($i = 20; $i <= 68; $i++){
//do stuff
}
This is better than a foreach loop because it only loops over the elements you want.
Ask if you have any questions
array.forEach(function(element,index){
if(index >= 21){
//Do Something
}
});
Element would be the current value of index.
Index increases with each turn through the loop.
IE 0,1,2,3,4,5;
array[index];

PHP: Getting to a key in mulitdimensional array?

I have an array like
$myArray =array
(
"0"=>array("dogs",98),
"1"=>array("cats",56),
"2"=>array("buffaloes",78)
)
How can I get a key by providing a value?
e.g. if i search for "buffaloes" array_search may return "2".
Thanks
$myArray =array
(
"0"=>array("dogs",98),
"1"=>array("cats",56),
"2"=>array("buffaloes",78)
);
function findInArray($term, $array) {
foreach($array as $key => $val) {
if(in_array($term, $val, true)) {
return $key;
}
}
}
echo findInArray('buffaloes', $myArray); // 2
echo findInArray(78, $myArray); // 2
function asearch($key, $myArray) {
for ($i = 0; $i < sizeof($myArray); $i++) {
if ($myArray[$i][0] == $key) {
return $i;
}
}
return -1; # no match
}
Though, you'd probably want to restructure your array to:
$myarray = array(
'dogs' => 98,
'cats' => 56,
'buffaloes' => 78
);
And just do:
$myArray['buffaloes']; # 78
The only way you can do it is to iterate over every item and preform a Linear Search
$i = -1;
foreach ($myArray as $key => $item){
if ( $item[0] == 'buffaloes' ){
$i = $key;
break;
}
}
//$i now holds the key, or -1 if it doesn't exist
As you can see, it is really really inefficient, as if your array has 20,000 items and 'buffaloes' is the last item, you have to make 20,000 comparisons.
In other words, you need to redesign your data structures so that you can look something up using the key, for example a better way may be to rearrange your array so that you have the string you are searching for as the key, for example:
$myArray['buffaloes'] = 76;
Which is much much faster, as it uses a better data structure so that it only has to at most n log n comparisons (where n is the number of items in the array). This is because an array is in fact an ordered map.
Another option, if you know the exact value of the value you are searching for is to use array_search
I never heard of built in function. If you want something more general then above solutions you shold write your own function and use recursion. maybe array_walk_recursive would be helpful
You can loop over each elements of the array, testing if the first element of each entry is equal to "buffaloes".
For instance :
foreach ($myArray as $key => $value) {
if ($value[0] == "buffaloes") {
echo "The key is : $key";
}
}
Will get you :
The key is : 2
Another idea (more funny ?), if you want to whole entry, might be to work with array_filter and a callback function that returns true for the "bufalloes" entry :
function my_func($val) {
return $val[0] == "buffaloes";
}
$element = array_filter($myArray, 'my_func');
var_dump($element);
Will get you :
array
2 =>
array
0 => string 'buffaloes' (length=9)
1 => int 78
And
var_dump(key($element));
Gves you the 2 you wanted.

Categories