smart_word_wrap_lazy.sf 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. #!/usr/bin/ruby
  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. class SmartWordWrap {
  10. has width = 80
  11. # This is the ugliest method! It, recursively,
  12. # prepares the words for the combine() function.
  13. method prepare_words(array, depth=0, callback) {
  14. var root = []
  15. var len = 0
  16. var i = -1
  17. var limit = array.end
  18. while (++i <= limit) {
  19. len += (var word_len = array[i].len)
  20. if (len > width) {
  21. if (word_len > width) {
  22. len -= word_len
  23. array.splice(i, 1, array[i].split(width)...)
  24. limit = array.end
  25. --i; next
  26. }
  27. break
  28. }
  29. root << [
  30. array.first(i+1).join(' '),
  31. self.prepare_words(array.slice(i+1), depth+1, callback)
  32. ]
  33. if (depth.is_zero) {
  34. callback(root[0])
  35. root = []
  36. }
  37. break if (++len >= width)
  38. }
  39. root
  40. }
  41. # This function combines the
  42. # the parents with the childrens.
  43. method combine(root, path, callback) {
  44. var key = path.shift
  45. path.each { |value|
  46. root << key
  47. if (value.is_empty) {
  48. callback(root)
  49. }
  50. else {
  51. value.each { |item|
  52. self.combine(root, item, callback)
  53. }
  54. }
  55. root.pop
  56. }
  57. }
  58. # This is the main function of the algorithm
  59. # which calls all the other functions and
  60. # returns the best possible wrapped string.
  61. method smart_wrap(text, width) {
  62. self.width = width
  63. var words = (text.kind_of(Array) ? text : text.words)
  64. var best = Hash(
  65. score => Inf,
  66. value => [],
  67. )
  68. self.prepare_words(words, callback: { |path|
  69. self.combine([], path, { |combination|
  70. var score = 0
  71. combination.slice(0, -1).each { |line|
  72. score += (width - line.len -> sqr)
  73. }
  74. if (score < best{:score}) {
  75. best{:score} = score
  76. best{:value} = []+combination
  77. }
  78. })
  79. })
  80. best{:value}.join("\n")
  81. }
  82. }
  83. #
  84. ## Usage examples
  85. #
  86. var obj = SmartWordWrap();
  87. var text = 'aaa bb cc ddddd';
  88. var t1 = obj.smart_wrap(text, 6);
  89. say t1;
  90. assert_eq(t1, <<'EOT'.chomp)
  91. aaa
  92. bb cc
  93. ddddd
  94. EOT
  95. say '-'*80;
  96. text = 'As shown in the above phases (or steps), the algorithm does many useless transformations';
  97. var t2 = obj.smart_wrap(text, 20);
  98. say t2;
  99. assert_eq(t2, <<'EOT'.chomp)
  100. As shown in the
  101. above phases
  102. (or steps), the
  103. algorithm does
  104. many useless
  105. transformations
  106. EOT
  107. say '-'*80;
  108. text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
  109. var t3 = obj.smart_wrap(text, 20);
  110. say t3;
  111. assert_eq(t3, <<'EOT'.chomp)
  112. Lorem ipsum
  113. dolor sit amet,
  114. consectetur
  115. adipiscing elit.
  116. EOT