Word wrap

Greedy:

class String {
    method wrap(width) {
        var txt = self.gsub(/\s+/, " ")
        var len = txt.len
        var para = []
        var i = 0
        while (i < len) {
            var j = (i + width)
            while ((j < len) && (txt.char_at(j) != ' ')) { --j }
            para.append(txt.substr(i, j-i))
            i = j+1
        }
        return para.join("\n")
    }
}

var text = 'aaa bb cc ddddd'
say text.wrap(6)

Output:

aaa bb
cc
ddddd

Smart:

class SmartWordWrap {

    has width = 80

    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.ft(i+1), depth+1, callback)
            ]

            if (depth.is_zero) {
                callback(root[0])
                root = []
            }

            break if (++len >= width)
        }

        root
    }

    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
        }
    }

    method 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.ft(0, -2).each { |line|
                    score += (width - line.len -> sqr)
                }

                if (score < best{:score}) {
                    best{:score} = score
                    best{:value} = []+combination
                }
            })
        })

        best{:value}.join("\n")
    }
}

var sww = SmartWordWrap();

var words = %w(aaa bb cc ddddd);
var wrapped = sww.wrap(words, 6);

say wrapped;

Output:

aaa
bb cc
ddddd