-
Notifications
You must be signed in to change notification settings - Fork 0
/
smart_word_wrap_lazy.sf
145 lines (116 loc) · 3.17 KB
/
smart_word_wrap_lazy.sf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#!/usr/bin/ruby
# Author: Daniel "Trizen" Șuteu
# License: GPLv3
# Date: 15th October 2013
# https://trizenx.blogspot.com
# https://trizenx.blogspot.com/2013/11/smart-word-wrap.html
# Smart word wrap algorithm
# See: https://en.wikipedia.org/wiki/Word_wrap#Minimum_raggedness
class SmartWordWrap {
has width = 80
# This is the ugliest method! It, recursively,
# prepares the words for the combine() function.
method prepare_words(array, depth=0, callback) {
var root = []
var len = 0
var i = -1
var limit = array.end
while (++i <= limit) {
len += (var word_len = array[i].len)
if (len > width) {
if (word_len > width) {
len -= word_len
array.splice(i, 1, array[i].split(width)...)
limit = array.end
--i; next
}
break
}
root << [
array.first(i+1).join(' '),
self.prepare_words(array.slice(i+1), depth+1, callback)
]
if (depth.is_zero) {
callback(root[0])
root = []
}
break if (++len >= width)
}
root
}
# This function combines the
# the parents with the childrens.
method combine(root, path, callback) {
var key = path.shift
path.each { |value|
root << key
if (value.is_empty) {
callback(root)
}
else {
value.each { |item|
self.combine(root, item, callback)
}
}
root.pop
}
}
# This is the main function of the algorithm
# which calls all the other functions and
# returns the best possible wrapped string.
method smart_wrap(text, width) {
self.width = width
var words = (text.kind_of(Array) ? text : text.words)
var best = Hash(
score => Inf,
value => [],
)
self.prepare_words(words, callback: { |path|
self.combine([], path, { |combination|
var score = 0
combination.slice(0, -1).each { |line|
score += (width - line.len -> sqr)
}
if (score < best{:score}) {
best{:score} = score
best{:value} = []+combination
}
})
})
best{:value}.join("\n")
}
}
#
## Usage examples
#
var obj = SmartWordWrap();
var text = 'aaa bb cc ddddd';
var t1 = obj.smart_wrap(text, 6);
say t1;
assert_eq(t1, <<'EOT'.chomp)
aaa
bb cc
ddddd
EOT
say '-'*80;
text = 'As shown in the above phases (or steps), the algorithm does many useless transformations';
var t2 = obj.smart_wrap(text, 20);
say t2;
assert_eq(t2, <<'EOT'.chomp)
As shown in the
above phases
(or steps), the
algorithm does
many useless
transformations
EOT
say '-'*80;
text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
var t3 = obj.smart_wrap(text, 20);
say t3;
assert_eq(t3, <<'EOT'.chomp)
Lorem ipsum
dolor sit amet,
consectetur
adipiscing elit.
EOT