This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
InputUsername's profile picture

Modifying A Word Wrapping Function

Started by InputUsername, 31 October 2013 - 02:35 PM
InputUsername #1
Posted 31 October 2013 - 03:35 PM
Hello everyone, I haven't asked anything in quite a while, but while programming yesterday, I ran into a (small) problem.

I need to modify the following function (found on http://lua-users.org...i/StringRecipes):

function wrap(str, limit)
  limit = limit or 72
  local here = 1

  -- the "".. is there because :gsub returns multiple values
  return ""..str:gsub("(%s+)()(%S+)()",
  function(sp, st, word, fi)
	if fi-here > limit then
	  here = st
	  return "\n"..word
	end
  end)
end

Currently all the function does is it returns a string with words wrapped at a given margin. But what I'm trying to achieve is that every line is put into a table (ie lines with the words wrapped).

SpoilerIf you don't understand what I mean by 'word wrapping', consider the following piece of text:
Derp was a man who lived in a forest. He lived peacefully, until he died. The end.

Let's say I can only print 20 characters on a line. To divide the piece of text equally, I could just do this:
Derp was a man who liv (20 character limit)
ed in a forest. He li
… (you get what I mean).

But what I want to happen is this:
Derp was a man who
lived in a forest. He
lived …

I have already tried the following but it doesn't work:

Lines = { }

function wrap(str, limit)
  limit = limit or 72
  local here = 1
  return ""..str:gsub("(%s+)()(%S+)()",
  function(sp, st, word, fi)
	if fi-here > limit then
	  here = st
	  Lines[#Lines+1] = "\n"..word -- this does not work
	  return "\n"..word
	end
  end)
end
Doing this gives the following result: http://repl.it/MDj

Any help/tips would be greatly appreciated.

Thanks.
Bomb Bloke #2
Posted 31 October 2013 - 05:54 PM
The original code creates a new string, appending one word from the original at a time along with the occasional line break. This then gets returned.

Your code puts the occasional word into your table (where the line breaks would be), while building the same string the original code made. You then return that new string instead of your table.

I'm not sure if it's the most efficient way to do it (and would be interested it seeing improved if possible), but the below should give the output you're after.

Spoiler
local function wrap(str, limit)
  local Lines, here, limit = {}, 1, limit or 72
  Lines[1] = string.sub(str,1,str:find("(%s+)()(%S+)()")-1)  -- Put the first word of the string in the first index of the table.

  str:gsub("(%s+)()(%S+)()",
	function(sp, st, word, fi)  -- Function gets called once for every space found.
	  if fi-here > limit then
		here = st
		Lines[#Lines+1] = word						   -- If at the end of a line, start a new table index...
	  else Lines[#Lines] = Lines[#Lines].." "..word end  -- ... otherwise add to the current table index.
	end)

  return Lines
end

local myText = "THIS IS A REALLY LONG TEXT THAT I WOULD LIKE TO WRAP AT A CERTAIN MARGIN."

local myTable = wrap(myText,20)

for k,v in pairs(myTable) do print(k.." = "..v) end
sens #3
Posted 31 October 2013 - 06:20 PM
Edit: BombBloke beat me to it (well done mate!)… here was my solution anyhow:


function wrap(str, limit)
  limit = limit or 72
  local here = 1
  local buf = ""
  local t = {}
  str:gsub("(%s*)()(%S+)()",
  function(sp, st, word, fi)
	if fi-here > limit then
	   --# Break the line
	   here = st
	   table.insert(t, buf)
	   buf = word
	else
	   buf = buf..sp..word  --# Append
	end
  end)
  --# Tack on any leftovers
  if(buf ~= "") then
	table.insert(t, buf)
  end
  return t
end

By the way, repl.it seems very handy for collaboration, I wonder if the forum could integrate with it somehow.
InputUsername #4
Posted 01 November 2013 - 10:34 AM
The original code creates a new string, appending one word from the original at a time along with the occasional line break. This then gets returned.

Your code puts the occasional word into your table (where the line breaks would be), while building the same string the original code made. You then return that new string instead of your table.

I'm not sure if it's the most efficient way to do it (and would be interested it seeing improved if possible), but the below should give the output you're after.

Spoiler
local function wrap(str, limit)
  local Lines, here, limit = {}, 1, limit or 72
  Lines[1] = string.sub(str,1,str:find("(%s+)()(%S+)()")-1)  -- Put the first word of the string in the first index of the table.

  str:gsub("(%s+)()(%S+)()",
	function(sp, st, word, fi)  -- Function gets called once for every space found.
	  if fi-here > limit then
		here = st
		Lines[#Lines+1] = word						   -- If at the end of a line, start a new table index...
	  else Lines[#Lines] = Lines[#Lines].." "..word end  -- ... otherwise add to the current table index.
	end)

  return Lines
end

local myText = "THIS IS A REALLY LONG TEXT THAT I WOULD LIKE TO WRAP AT A CERTAIN MARGIN."

local myTable = wrap(myText,20)

for k,v in pairs(myTable) do print(k.." = "..v) end

Edit: BombBloke beat me to it (well done mate!)… here was my solution anyhow:

(code)

By the way, repl.it seems very handy for collaboration, I wonder if the forum could integrate with it somehow.

Thanks for the quick response, both of you.

And yes, repl.it seems very useful. If only it had Git integration of some kind.
Goof #5
Posted 30 October 2014 - 11:28 AM
Sorry for bump, but i've thought about if you make the "certain" margin fx. 5, and type a 10 letter large word, then it would crash..
Whats going to be a solution for that?

is it just going to need to check twice? ( one for length, and one for every word )

Thanks in Advance

EDIT: This is based on Bomb Bloke's example
Edited on 30 October 2014 - 10:30 AM
Bomb Bloke #6
Posted 30 October 2014 - 11:55 AM
It won't crash if a word is longer than the line limit, but it isn't rigged to cut such words in half, either. Instead you'd end up with one of the lines in the table being longer than the specified limit (though it'd only contain one word).

So yeah, to fix that put a loop in place that, when starting a new line, checks to see if the first word on that line is oversized then loops through distributing it over however many lines it needs.

I suppose it could also use a check to see if the string passed to it completely lacks spaces.
Goof #7
Posted 30 October 2014 - 02:54 PM
It won't crash if a word is longer than the line limit, but it isn't rigged to cut such words in half, either. Instead you'd end up with one of the lines in the table being longer than the specified limit (though it'd only contain one word).
Well.. i wont agree.. ( Using this string as the example:
local myText = "ThisTextIsVeryLong(LongerThan20Characters)"
)

Since it searches through the string for Spaces, it encounters that problem, i guess.

So yeah, to fix that put a loop in place that, when starting a new line, checks to see if the first word on that line is oversized then loops through distributing it over however many lines it needs.

I suppose it could also use a check to see if the string passed to it completely lacks spaces.
Well.. im not that familar with string:gsub, so I dont know where to start.
Is an example possible? :rolleyes:/>

Thanks in Advance ^_^/>
Edited on 30 October 2014 - 01:57 PM
Bomb Bloke #8
Posted 30 October 2014 - 07:41 PM
Again, it's not complaining about the length of the word. In the case of that string, the problem is triggered by the lack of spaces.

Here's a version which implements the two fixes I mentioned:

Spoiler
local function splitWords(Lines, limit)
    while #Lines[#Lines] > limit do
        Lines[#Lines+1] = Lines[#Lines]:sub(limit+1)
        Lines[#Lines-1] = Lines[#Lines-1]:sub(1,limit)
    end
end

local function wrap(str, limit)
    local Lines, here, limit, found = {}, 1, limit or 72, str:find("(%s+)()(%S+)()")

    if found then
        Lines[1] = string.sub(str,1,found-1)  -- Put the first word of the string in the first index of the table.
    else Lines[1] = str end

    str:gsub("(%s+)()(%S+)()",
        function(sp, st, word, fi)  -- Function gets called once for every space found.
            splitWords(Lines, limit)

            if fi-here > limit then
                here = st
                Lines[#Lines+1] = word                                             -- If at the end of a line, start a new table index...
            else Lines[#Lines] = Lines[#Lines].." "..word end  -- ... otherwise add to the current table index.
        end)

    splitWords(Lines, limit)

    return Lines
end

local myText = "THIS IS A REALLY LONG TEXT THAT I WOULD LIKE TO WRAP AT A CERTAIN MARGIN."

local myTable = wrap(myText,20)

for k,v in pairs(myTable) do print(k.." = "..v) end
Goof #9
Posted 30 October 2014 - 09:19 PM
Again, it's not complaining about the length of the word. In the case of that string, the problem is triggered by the lack of spaces.

Here's a version which implements the two fixes I mentioned:

Spoiler
local function splitWords(Lines, limit)
	while #Lines[#Lines] > limit do
		Lines[#Lines+1] = Lines[#Lines]:sub(limit+1)
		Lines[#Lines-1] = Lines[#Lines-1]:sub(1,limit)
	end
end

local function wrap(str, limit)
	local Lines, here, limit, found = {}, 1, limit or 72, str:find("(%s+)()(%S+)()")

	if found then
		Lines[1] = string.sub(str,1,found-1)  -- Put the first word of the string in the first index of the table.
	else Lines[1] = str end

	str:gsub("(%s+)()(%S+)()",
		function(sp, st, word, fi)  -- Function gets called once for every space found.
			splitWords(Lines, limit)

			if fi-here > limit then
				here = st
				Lines[#Lines+1] = word											 -- If at the end of a line, start a new table index...
			else Lines[#Lines] = Lines[#Lines].." "..word end  -- ... otherwise add to the current table index.
		end)

	splitWords(Lines, limit)

	return Lines
end

local myText = "THIS IS A REALLY LONG TEXT THAT I WOULD LIKE TO WRAP AT A CERTAIN MARGIN."

local myTable = wrap(myText,20)

for k,v in pairs(myTable) do print(k.." = "..v) end

Oah.. Thank you so much :)/>

Its working like a charm :D/>