smart_word_wrap.sf 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/ruby
  2. # Author: Daniel "Trizen" Șuteu
  3. # License: GPLv3
  4. # Date: 15th October 2013
  5. # Translated to Sidef in 06th September 2014
  6. # https://trizenx.blogspot.com
  7. # Smart word wrap algorithm
  8. # See: https://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
  9. class SmartWordWrap(WIDTH=80) {
  10. ## This is the ugliest function! It, recursively,
  11. ## prepares the words for the make_paths() function.
  12. method prepare_words(array) {
  13. var root = [];
  14. var len = 0;
  15. for (var i = 0 ; i <= array.end ; i++) {
  16. var wordLen = array[i].len;
  17. len += wordLen;
  18. len > self.WIDTH && (
  19. wordLen > self.WIDTH && (
  20. len -= wordLen;
  21. array.splice(i, 1, array[i].split(self.WIDTH)...);
  22. i--, next;
  23. );
  24. break;
  25. );
  26. root.append(array.slice(0, i+1) + __METHOD__(self, array.slice(i + 1, array.end)));
  27. (++len >= self.WIDTH) && break;
  28. }
  29. root.len && return root;
  30. array.len && return [array];
  31. return [];
  32. }
  33. ## This function creates all the
  34. ## avaible paths, for further processing.
  35. method make_paths(array) {
  36. var head = [];
  37. while (array.len) {
  38. array[0].is_a(Array) && break;
  39. head.push(array.shift);
  40. }
  41. var row = [];
  42. array.each { |path|
  43. row.push(Hash.new(head.join(' ') => __METHOD__(self, path)));
  44. }
  45. return(row.len ? row : head.join(' '));
  46. }
  47. ## This function combines the
  48. ## the parents with the childrens.
  49. method combine(root, hash) {
  50. var row = [];
  51. hash.each_pair { |key, value|
  52. root.append(key);
  53. if (value.is_a(Array)) {
  54. value.each { |item|
  55. row.append(__METHOD__(self, root, item));
  56. }
  57. }
  58. else { row.append(root..., value) };
  59. root.pop;
  60. };
  61. return row;
  62. }
  63. ## This function normalizez the combinations.
  64. ## Example: [[["abc"]]] is normalized to ["abc"];
  65. method normalize(array_ref) {
  66. var strings = [];
  67. array_ref.each { |item|
  68. if (item.is_a(Array)) {
  69. strings += __METHOD__(self, item);
  70. }
  71. else {
  72. strings.append(array_ref);
  73. break;
  74. }
  75. }
  76. return strings;
  77. }
  78. ## This function finds the best
  79. ## combination avaiable and returns it.
  80. method find_best(arrays) {
  81. var best = Hash.new('score' => Inf);
  82. arrays.each { |array_ref|
  83. var score = 0;
  84. array_ref.each { |string|
  85. score += ::pow(self.WIDTH - string.len, 2);
  86. }
  87. score < best{:score} && (
  88. best{:score} = score;
  89. best{:value} = array_ref;
  90. );
  91. }
  92. best.exists(:value) ? best{:value} : '';
  93. }
  94. ## This is the main function of the algorithm
  95. ## which calls all the other functions and
  96. ## returns the best possible wrapped string.
  97. method wrap(text, width=WIDTH) {
  98. # Temporarily modify the width
  99. local WIDTH = width;
  100. # Split the text into words
  101. text.is_a(String) && (
  102. text = text.words;
  103. );
  104. # Prepare words
  105. var pwords = self.prepare_words(text);
  106. # Make the paths
  107. var paths = [];
  108. pwords.each { |group|
  109. paths.append(self.make_paths(group));
  110. };
  111. # Create the combinations
  112. var combinations = [];
  113. while(paths.len) {
  114. if (paths[0].is_a(Array)) {
  115. paths += paths.shift;
  116. next;
  117. }
  118. var path = paths.shift;
  119. combinations.append(path.is_a(Hash) ? [self.combine([], path)] : [path]);
  120. }
  121. # Return the best result
  122. self.find_best(self.normalize(combinations)).join("\n");
  123. }
  124. }
  125. var sww = SmartWordWrap(6);
  126. var words = %w(aaa bb cc ddddd);
  127. var wrapped = sww.wrap(words);
  128. say wrapped;
  129. assert_eq(wrapped, "aaa\nbb cc\nddddd")