14

I got this problem somewhere, and I want to know how to solve this using PHP. Given this text:

$str = '
PHP is a
widely-used

general-purpose
server side

scripting
language
';

How to echo the text vertically like the given below:

      g        
      e        
      n        
      e        
  w   r s      
  i   a e      
  d   l r   s  
P e   - v   c l
H l   p e   r a
P y   u r   i n
  -   r     p g
i u   p s   t u
s s   o i   i a
  e   s d   n g
a d   e e   g e

I will select the simpler and more elegant code as the answer.

flowfree
  • 16,356
  • 12
  • 52
  • 76
  • 2
    why don't you use css to do so? http://stackoverflow.com/questions/1080792/how-to-draw-vertical-text-with-css-cross-browser – nullpointr May 28 '12 at 12:44
  • Doing with CSS is great. But I want to know how this can be solved in PHP. Think like you're writing a command line script and need to output something, like graph :) – flowfree May 28 '12 at 12:50

15 Answers15

9

As others have already demonstrated, array_map is able to do the flip over which is basically the main problem you need to solve. The rest is how you arrange the code. I think your version is very good because it's easy to understand.

If you're looking more to some other extreme, handle with care:

$str = 'PHP is a
widely-used

general-purpose
server side

scripting
language';
    
$eol = "\n";
$turned = function($str) use ($eol) {
    $length = max(
        array_map(
            'strlen',
            $lines = explode($eol, trim($str))
        )
    );
    $each = function($a, $s) use ($length) {
        $a[] = str_split(
            sprintf("%' {$length}s", $s)
        );
        return $a;
    };
    return implode(
        $eol,
        array_map(
            function($v) {
                return implode(' ', $v);
            },
            call_user_func_array(
                'array_map',
                array_reduce($lines, $each, array(NULL))
            )
        )
    );
};
echo $turned($str), $eol;

Gives you:

      g        
      e        
      n        
      e        
  w   r s      
  i   a e      
  d   l r   s  
P e   - v   c l
H l   p e   r a
P y   u r   i n
  -   r     p g
i u   p s   t u
s s   o i   i a
  e   s d   n g
a d   e e   g e

This fixes the output of the other answer, which was incorrect (now fixed).

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
hakre
  • 193,403
  • 52
  • 435
  • 836
  • 1
    Absolutely. Well, I have some other piece of code in another answer for that contest, let me pick it: http://stackoverflow.com/a/7891640/367456 – hakre Jun 10 '12 at 17:32
  • @hakre so at least you don't need to uglify your code anymore - you just write minified code on the fly :) (i've found some unneeded spaces in it tho) – low_rents Feb 13 '15 at 11:14
6

The code below will print $str vertically.

$lines = preg_split("/\r\n/", trim($str));
$nl    = count($lines);
$len   = max(array_map('strlen', $lines));

foreach ($lines as $k => $line) {
  $lines[$k] = str_pad($line, $len, ' ', STR_PAD_LEFT);
}

for ($i = 0; $i < $len; $i++) {
  for ($j = 0; $j < $nl; $j++) {
    echo $lines[$j][$i].($j == $nl-1 ? "\n" : " ");
  }
}
flowfree
  • 16,356
  • 12
  • 52
  • 76
  • 1
    how about $len = max(array_map('strlen', $lines); instead of the first for loop? – allen213 May 28 '12 at 12:51
  • @allen213: Wow great! I never thought about it. I'm using it in my code. I hope you don't mind :) – flowfree May 28 '12 at 12:55
  • Not mine saw it before here > http://stackoverflow.com/questions/1762191/how-to-get-the-length-of-longest-string-in-an-array – allen213 May 28 '12 at 12:59
3

I took some of the code from @bsdnoobz and simplified it. I'm using \n as a line break because I work on a Mac and \r\n doean work here.

$lines = preg_split("/\n/", trim($str));
$len   = max(array_map('strlen', $lines));
$rows  = array_fill(0,$len,'');
foreach ($lines as $k => $line) {
    foreach (str_split(str_pad($line, $len, ' ', STR_PAD_LEFT)) as $row => $char){
          $rows[$row] .= $char;
    }
}
echo implode("\n",$rows)."\n";
Ateszki
  • 2,243
  • 1
  • 16
  • 13
3
$a = explode(PHP_EOL, trim($str));
$h = max(array_map('strlen', $a));
$w = count($a);
$m = array_map('str_split', array_map('sprintf', array_fill(0, $w, "%{$h}s"), $a));
$t = call_user_func_array('array_map', array_merge(array(null), $m));
echo implode(PHP_EOL, array_map('implode', array_fill(0, $h, ' '), $t)), PHP_EOL;

PHP_EOL should be replaced with "\n", "\r\n" or '<br/>' where appropriate. The whole code after the first line could easily become one big expression, only its readability would suffer a little bit ;-)

$a is the array of lines, $h is the final height, $w is the final width, $m is the matrix of characters (after padding the strings), $t is the transposed matrix.

The array_fill(0, $h, ' '), on the last line can simply be omitted if the space between columns is not needed. On the other hand, not printing trailing space characters can be achieved like this:

echo implode(PHP_EOL, array_map('rtrim', array_map('implode', array_fill(0, $h, ' '), $t))), PHP_EOL;

I have taken the question as an excercise in avoiding explicit loops (which are usually more expensive than loops inside PHP functions, although in this case the advantage could be lost). The important trick here is the matrix transposition, which I have taken from this answer by Codler

Anyhow, the complexity of the whole algorithm is O(width × height), just like the one of most algorithms on this page, except for those that repeatedly concatenate strings in order to obtain the lines, whose complexity is O(width² × height)

Community
  • 1
  • 1
Walter Tross
  • 12,237
  • 2
  • 40
  • 64
1

I'll give this a shot:

$arrlines = explode(PHP_EOL, trim($str));
$max = max(array_map('strlen', $arrlines));
foreach($arrlines as $val)
    $arrlines = str_pad($val,$max," ",STR_PAD_LEFT);    
for($x=0;$x<$max;$x++){
    for($y=0;$y<count($arrlines);$y++)
        $string .= strlen(trim($arrlines[$y][$x])) > 0 ? $arrlines[$y][$x]."&nbsp;":"&nbsp;&nbsp;";
    $string .= "\n";
    }
echo '<pre>'.$string.'</pre>';

Line numbers:

  1. save the string per line in an array $arrlines separated by the PHP constant PHP_EOL
  2. get the maximum string length and save it to $max
  3. loop for every element of $arrlines, then
  4. add padding to the left of the string to make its string length equal to $max

for lines 5-9, nested loop to save the $arrline[$y][$x] to $string, and line 10 outputs the result.

Result can be found here.

macinville
  • 236
  • 2
  • 9
0

My function to rotate text without any language construct loops:

/**
 * Rotates text
 *
 * @param string $str String to rotate
 *
 * @return string Rotated string
 */
function rotateText($str)
{
    $lines     = explode(PHP_EOL, $str);
    $lengths   = array_map('strlen', $lines);
    $maxLength = max($lengths);
    $lines     = array_map(function ($line) use ($maxLength) {
        return str_pad($line, $maxLength, ' ', STR_PAD_LEFT);
    }, $lines);
    $chars     = array_map('str_split', $lines);
    array_unshift($chars, null);
    $rotatedLines = call_user_func_array('array_map', $chars);
    $rotatedText  = join(PHP_EOL, array_map('join', $rotatedLines));
    return $rotatedText;
}

echo "<pre>", rotateText($str), "</pre>";
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
lisachenko
  • 5,952
  • 3
  • 31
  • 51
  • 1
    array_map('str_split', $lines); //double cycle + array with lots of elements (and you are using array_map 5 times, thats at least 5 cycles). array_map('join', $rotatedLines) would also be lots of cycles (join each array into an array of strings, then join again). – oxygen Jun 01 '12 at 19:35
  • Yes, agree. Your solution is about 40% faster than my one. – lisachenko Jun 03 '12 at 10:58
0

Please note that the length of a string and the number of elements in an array are cached by PHP per variable. Calling strlen or count repeatedly has only the performance penalty of an empty function call (on subsequent calls, the length or count are not measured twice internally by PHP).

This works with a Mac (\r), Linux (\n) or Windows (\r\n) input string, and/or with an empty input string, and/or with a one line input string, and outputs with a prefered line delimiter (default is PHP_EOL):

function matrix_vertical_align_bottom($str, $strEOL=PHP_EOL)
{
    $arrLines=explode("\n", $str);

    $nMaxLength=max(array_map("strlen", $arrLines));
    $nRotatedWidth=count($arrLines)+strlen($strEOL);

    //allocate once
    $strRotated=str_pad(
        "", 
        $nRotatedWidth*$nMaxLength-strlen($strEOL), 
        str_repeat(" ", count($arrLines)).$strEOL
    );

    foreach($arrLines as $nRotatedColumnIndex=>$strLine)
    {
        $nRotatedVerticalPadding=$nMaxLength-strlen($strLine);
        for($nColumnIndex=strlen($strLine)-1; $nColumnIndex>=0; $nColumnIndex--)
            $strRotated[$nRotatedWidth*($nColumnIndex+$nRotatedVerticalPadding)+$nRotatedColumnIndex]=$strLine[$nColumnIndex];
    }

    return $strRotated;
}

echo matrix_vertical_align_bottom(preg_replace("/(\r\n|\r|\n)/", "\n", trim($str)));

The performance is pretty good, the above algorithm is just translating coordinates while rotating the string by 90 degrees. There is no memory re-allocation because of string expanding provoked by padding individual lines (for input strings with lots of lines, this would be a performance hit).

HTML output is not assumed, nl2br(htmlspecialchars( )) when outputting should do the trick + a mono spaced font.

oxygen
  • 5,891
  • 6
  • 37
  • 69
0

Here is simple code any one can understand this code

    $str = '
    PHP is a
    widely-used

    general-purpose
    server side

    scripting
    language
    ';

    $lines = array();

    $sentences = explode("\n\n",$str);
    foreach($sentences as $sentence){
        if($sentence != ""){
            $each_lines = explode("\n",$sentence);
            foreach($each_lines as $each_line){
                if($each_line != ""){
                    $lines[] = $each_line;
                }
            }
            $lines[] = "\t";
        }
    }
    $before_sort = $lines;
    usort($lines, "cmp_len");
    $big_length = strlen($lines[0]);
    foreach($before_sort as $key=>$arr){
        $this_length = strlen($arr);
        if($this_length < $big_length){
            $no_of_letters = $big_length - $this_length;
            $text = "";
            for($i=0;$i<$no_of_letters;$i++){
                $text .= " ";
            }
            $text .= $before_sort[$key];
            $before_sort[$key] = $text;
        }
    }

    $temp = array();
    echo "<table>";
    for($i=0;$i<$big_length;$i++){
      echo "<tr>";
        foreach($before_sort as $arr){
            echo "<td>";
                echo str_replace("\t","&nbsp;&nbsp;&nbsp;&nbsp;",$arr[$i]);
            echo "</td>";
        }
      echo "</tr>";
    }
    echo "</table>";





    function cmp_len($a, $b){
       return (strlen($a)  < strlen($b));
    }
Miqdad Ali
  • 6,129
  • 7
  • 31
  • 50
  • It is unnecessary to sort all of the lines by length when you only need the max value. PHP8 is barking at your sorting return type. https://3v4l.org/Zm95k – mickmackusa Sep 14 '21 at 04:01
0

Check this answer:

<?php

$str = 'PHP is a
widely-used

general-purpose
server side

scripting
language';


function getVerticalString($str){
    $str = preg_split('/\r|\n/',$str);

    $maxlength = 0;

    $totalstr = count($str);

    for($i=0;$i<$totalstr;$i++){
        if($maxlength<strlen($str[$i])){
            $maxlength = strlen($str[$i]);
        }
    }

    $strings = array();

    for($i=0;$i<$maxlength;$i++){
        $strings[$i] = "";
        for($j=0;$j<$totalstr;$j++){
            $temp = strlen($str[$j])-($maxlength-$i);
            if($temp>=0){
                $strings[$i] .= substr($str[$j],$temp,1);
            }
            else{
                $strings[$i] .= " ";
            }
        }
    }

    return implode("\r",$strings);
}

echo "<pre>".getVerticalString($str)."</pre>";

this outputs:

   g    
   e    
   n    
   e    
 w rs   
 i ae   
 d lr s 
Pe -v cl
Hl pe ra
Py ur in
 - r  pg
iu ps tu
ss oi ia
 e sd ng
ad ee ge

As per your requirements. :)

Vipin Jain
  • 1,382
  • 1
  • 10
  • 19
0

I wouldn't really call it shorter, but line count is less ;-)

$nr_lines = count($lines = preg_split('/\r?\n/', trim($str)));
$maxlen = max($lengths = array_map('strlen', $lines)); // get line lengthts and maximum
$line_ptrs = array_fill(0, $nr_lines, 0); // last character position per line

for ($j = 0; $j != $maxlen; ++$j) {
    for ($i = 0; $i != $nr_lines; ++$i) {
        // $maxlen - $lengths[$i] indicates where printing start for this line
        echo $j >= $maxlen - $lengths[$i] ? $lines[$i][$line_ptrs[$i]++] : ' ';
    }
    echo "\n";
}

It does the padding and printing in the same inner loop, aided by the $line_ptrs and $lengths array to keep track of which character to print next.

Benchmark

Based on 10,000 iterations, this code performs 19% better than the first answer.

Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
0

It's like the old saying goes:

If it can be done without regex, try it anyway.

Some people, when confronted with a problem, think “I know, I'll use regular expressions.” Now they have zero problems.

"I took the [road] less traveled by" - Robert Frost

With so many similar techniques on this page, I thought I'd offer a fresh perspective. Here is a technique by which the input string is consumed within a loop and the last character (if it exists) of each line is added to the output array as a space-delimited string. The loop is broken when the entire input string consists of whitespace characters. I did not benchmark it, but I would be curious to hear any comparisons if anyone is inclined to provide their findings.

Code: (Demo)

$result = [];
while (!ctype_space($string)) {
    $line = [];
    $string = preg_replace_callback(
        "~.$|^$~m",
        function($m) use(&$line) {
            $line[] = $m[0] === '' ? ' ' : $m[0];
            return '';
        },
        $string,
    );
    array_unshift($result, implode(' ', $line));
}
echo implode("\n", $result);
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
-1

Here's my shot at it. Couldn't figure out how to do it without the nested loop.

$lines = array_map('strrev', explode("\r\n", trim($str)));
$new = array();
foreach(range(0, max(array_map('strlen', $lines))) as $i){ 
    foreach($lines as $line){
        $new[$i] .= (!empty($line[$i]) ? $line[$i] . ' ' : '  ');
    }
}
echo implode("\r\n", (array_slice(array_reverse($new), 1)));
Gohn67
  • 10,608
  • 2
  • 29
  • 35
  • This looks promising. Unfortunately the result is 95% matches. There are extra whitespaces in the output string. – flowfree May 31 '12 at 07:11
  • Yeah, you're right. Didn't notice the end row had no extra space. And looks like I missed the white space on line sixteen. I'll try again later. – Gohn67 May 31 '12 at 15:10
  • This answer appears to be very broken. https://3v4l.org/nGbqv – mickmackusa Sep 14 '21 at 03:54
-1

Try this one,

$str = 'PHP is a
widely-used

general-purpose
server side

scripting
language';

$strArr = explode("\r\n", $str);
$max =max(array_map('strlen', $strArr));

for($i=0; $i< $max;$i++)
{
    for($x=0;$x < count($strArr); $x++)
    {
        $strVal = $strArr[$x];
        $y = $i -($max -  strlen($strVal));
        $vertical .= strlen(trim($strVal[$y]))<> 0 ? $strVal[$y]." " : "  ";
    } 
    $vertical .="\n";
}
echo "<pre>".$vertical;
sephoy08
  • 1,104
  • 7
  • 16
-1

The best and simplest way :)

<style type="text/css">
#heading{
    /* vertical text css */
    width:1em;
    text-transform:uppercase;
}
</style>

<h1 align="center" id="heading">h e l l o</h1>

DEMO

Farhan Ahmad
  • 5,148
  • 6
  • 40
  • 69
-1
$a= 'HelloWorld!';
$b=strlen($a);
for($i=0;$i<$b;$i++){
echo $c=substr($a,$i,1).'<br>';  
}