smart_word_wrap.rb 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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. @width = 0;
  11. # This is the ugliest method! It, recursively,
  12. # prepares the words for the combine() function.
  13. def prepare_words(array)
  14. root = []
  15. len = 0
  16. i = -1
  17. limit = array.size-1
  18. while ((i+=1) <= limit)
  19. len += (word_len = array[i].size)
  20. if len > @width
  21. if word_len > @width
  22. len -= word_len
  23. value = array[i]
  24. array.delete_at(i)
  25. array.insert(i, *(value.scan(/.{1,#{@width}}/m)))
  26. limit = array.size-1
  27. i -= 1; next
  28. end
  29. break
  30. end
  31. root << [
  32. array[0..i].join(' '),
  33. prepare_words(array[i+1 .. limit])
  34. ]
  35. break if ((len += 1) >= @width)
  36. end
  37. root
  38. end
  39. # This function combines the
  40. # the parents with the childrens.
  41. def combine(root, path)
  42. row = []
  43. key = path.shift
  44. path.each do |value|
  45. root << key
  46. if value.empty?
  47. row = [root + []]
  48. else
  49. value.each do |item|
  50. row += combine(root, item)
  51. end
  52. end
  53. root.pop
  54. end
  55. row
  56. end
  57. # This function finds the best
  58. # combination available and returns it.
  59. def find_best(arrays)
  60. best = {
  61. score: Float::INFINITY,
  62. value: [],
  63. }
  64. arrays.each { |array|
  65. score = 0;
  66. array.each { |line|
  67. score += (@width - line.size)**2
  68. }
  69. if score < best[:score]
  70. best[:score] = score
  71. best[:value] = array
  72. end
  73. }
  74. best[:value]
  75. end
  76. # This is the main function of the algorithm
  77. # which calls all the other functions and
  78. # returns the best possible wrapped string.
  79. def smart_wrap(text, width)
  80. @width = width;
  81. words = text.is_a?(Enumerable) ? text : text.split(' ')
  82. lines = [];
  83. self.prepare_words(words).each { |path|
  84. lines += combine([], path)
  85. }
  86. best = self.find_best(lines)
  87. best == nil and return nil
  88. return best.join("\n")
  89. end
  90. end
  91. #
  92. ## Usage examples
  93. #
  94. text = 'aaa bb cc ddddd'
  95. obj = SmartWordWrap.new
  96. puts obj.smart_wrap(text, 6)
  97. puts '-'*80
  98. text = 'As shown in the above phases (or steps), the algorithm does many useless transformations'
  99. puts obj.smart_wrap(text, 20)
  100. puts '-'*80
  101. text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
  102. puts obj.smart_wrap(text, 20)