smartWordWrap_lazy.pl 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. #!/usr/bin/perl
  2. # Author: Daniel "Trizen" Șuteu
  3. # License: GPLv3
  4. # Date: 15th October 2013
  5. # https://trizenx.blogspot.com
  6. # https://trizenx.blogspot.com/2013/11/smart-word-wrap.html
  7. # Smart word wrap algorithm
  8. # See: https://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
  9. use 5.010;
  10. use strict;
  11. use warnings;
  12. use experimental qw(signatures);
  13. # This is the ugliest method! It, recursively,
  14. # prepares the words for the combine() function.
  15. sub prepare_words ($words, $width, $callback, $depth = 0) {
  16. my @root;
  17. my $len = 0;
  18. my $i = -1;
  19. my $limit = $#{$words};
  20. while (++$i <= $limit) {
  21. $len += (my $word_len = length($words->[$i]));
  22. if ($len > $width) {
  23. if ($word_len > $width) {
  24. $len -= $word_len;
  25. splice(@$words, $i, 1, unpack("(A$width)*", $words->[$i]));
  26. $limit = $#{$words};
  27. --$i;
  28. next;
  29. }
  30. last;
  31. }
  32. #<<<
  33. push @root, [
  34. join(' ', @{$words}[0 .. $i]),
  35. prepare_words([@{$words}[$i + 1 .. $limit]], $width, $callback, $depth + 1),
  36. ];
  37. #>>>
  38. if ($depth == 0) {
  39. $callback->($root[0]);
  40. @root = ();
  41. }
  42. last if (++$len > $width);
  43. }
  44. \@root;
  45. }
  46. # This function combines the
  47. # the parents with the children.
  48. sub combine ($path, $callback, $root = []) {
  49. my $key = shift(@$path);
  50. foreach my $value (@$path) {
  51. push @$root, $key;
  52. if (@$value) {
  53. foreach my $item (@$value) {
  54. combine($item, $callback, $root);
  55. }
  56. }
  57. else {
  58. $callback->($root);
  59. }
  60. pop @$root;
  61. }
  62. }
  63. # This is the main function of the algorithm
  64. # which calls all the other functions and
  65. # returns the best possible wrapped string.
  66. sub smart_wrap ($text, $width) {
  67. my @words = (
  68. ref($text) eq 'ARRAY'
  69. ? @{$text}
  70. : split(' ', $text)
  71. );
  72. my %best = (
  73. score => 'inf',
  74. value => [],
  75. );
  76. prepare_words(
  77. \@words,
  78. $width,
  79. sub ($path) {
  80. combine(
  81. $path,
  82. sub ($combination) {
  83. my $score = 0;
  84. foreach my $line (@{$combination}[0 .. $#{$combination} - 1]) {
  85. $score += ($width - length($line))**2;
  86. }
  87. if ($score < $best{score}) {
  88. $best{score} = $score;
  89. $best{value} = [@$combination];
  90. }
  91. }
  92. );
  93. }
  94. );
  95. join("\n", @{$best{value}});
  96. }
  97. #
  98. ## Usage examples
  99. #
  100. my $text = 'aaa bb cc ddddd';
  101. say smart_wrap($text, 6);
  102. say '-' x 80;
  103. $text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
  104. say smart_wrap($text, 20);
  105. say '-' x 80;
  106. $text = "Lorem ipsum dolor ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ amet, consectetur adipiscing elit.";
  107. say smart_wrap($text, 20);
  108. say '-' x 80;
  109. $text = 'As shown in the above phases (or steps), the algorithm does many useless transformations';
  110. say smart_wrap($text, 20);
  111. say '-' x 80;
  112. $text = 'Will Perl6 also be pre-installed on future Mac/Linux operating systems? ... I can\'t predict the success of the project';
  113. say smart_wrap($text, 20);
  114. say '-' x 80;
  115. say smart_wrap(['a' .. 'n'], 5);