Home > OS >  How'd i loop through every string.gmatch in a string then replacing the matches with a bytecode
How'd i loop through every string.gmatch in a string then replacing the matches with a bytecode

Time:11-27

How'd I change everything that it matches in the string without changing the non matches?

local a = "\" Hello World! I want to replace this with a bytecoded version of this!\" but not this!"

for i in string.gmatch(a, "\".*\"") do
    print(i)
end

For example I want "\"Hello World!\" Don't Replace this!" to "\"\72\101\108\108\111\32\87\111\114\108\100\33\" Don't Replace this!"

CodePudding user response:

Your question is a little bit tricky because it could involve:

First things first, if you need to implement Lua patterns, please know that there is a very handy Lua syntax which is very appropriate for dealing with quoted strings. With this syntax, instead of opening/closing a string with a double-quote, you do it with the characters [[ and ]]. The key difference is that between these markers, you don't have to escape the quoted strings anymore!

String = [["Hello World!" Don't Replace this!]]

Then, we need to build the proper Lua pattern, a possibility could be to match a double-quote (") and then match all the characters which are not a double-quote ("), this gives us the following pattern:

[["([^"] )"]]
  | ****  |
  |   \-> the expression to match
  |       |
 quote   quote

Then if we study the function string.gsub, we can learn that the function can call a callback when a pattern is matched, the matched string will be replaced by the return value of the callback.

function ConvertToByteString (MatchedString)
  local ByteStrings = {}
  local Len         = #MatchedString
  
  ByteStrings[#ByteStrings 1] = [[\"]]
  
  for Index = 1, Len do
    local Byte = MatchedString:byte(Index)
    ByteStrings[#ByteStrings 1] = string.format([[\%d]], Byte)
  end
  
  ByteStrings[#ByteStrings 1] = [[\"]]
  
  return table.concat(ByteStrings)
end

In this function, we iterate through all the characters of the matched string. Then for each of the characters, we extract its byte value with the function string.byte and convert it to a string using the string.format function. We put this string in a temporary array that we will concatenate at the end of the function.

The function to concatenate the sub-strings into a larger string is table.concat. This is a very convenient function which could be used as follow:

> table.concat({ [[\10]], [[\11]], [[\12]] })
\10\11\12

The remaining thing we need to do is to test this outstanding function:

> String = [["Hello World!" Don't Replace this!]]
> NewString = String:gsub([["([^"] )"]], ConvertToByteString)
> NewString
\"\72\101\108\108\111\32\87\111\114\108\100\33\" Don't Replace this!

Edit: I got some remarks regarding code performances, I personally don't focus much on performances, I focus on getting the code correct & simple. In order to address the performance question, I wrote a micro-benchmark to compare the versions:

function SOLUTION_DarkWiiPlayer (String)
  local result = String:gsub('"[^"]*"', function(str)
                          return str:gsub('[^"]', function(char)
                                            return "\\" .. char:byte()
                          end)
  end)
  return result
end

function SOLUTION_Robert (String)
  local function ConvertToByteString (MatchedString)
    local ByteStrings = {}
    local Len         = #MatchedString
    
    ByteStrings[#ByteStrings 1] = [[\"]]
    
    for Index = 1, Len do
      local Byte = MatchedString:byte(Index)
      ByteStrings[#ByteStrings 1] = string.format([[\%d]], Byte)
    end
    
    ByteStrings[#ByteStrings 1] = [[\"]]
    
    return table.concat(ByteStrings)
  end
  local Result = String:gsub([["([^"] )"]], ConvertToByteString)
  return Result
end

function SOLUTION_Piglet (String)
  return String:gsub('%b""' , function (match)
                       local ret = ""
                       for _,v in ipairs{match:byte(1, -1)} do
                         ret = ret .. string.format("\\%d", v)
                       end
                       return ret
  end)
end

function SOLUTION_Renshaw (String)
  local function convert(str)
    local byte_str = ""
    for i = 1, #str do
      byte_str = byte_str .. "\\" .. tostring(string.byte(str, i))
    end
    return byte_str
  end
  local Result = string.gsub(String, "\"(.*)\"", function(matched_str)
                               return "\"" .. convert(matched_str) .. "\""
  end)
  return Result
end

String = "\"Hello World!\" Don't Replace this!"

print("INITIAL REQUIREMENT FROM OP ", [[\"\72\101\108\108\111\32\87\111\114\108\100\33\" Don't Replace this!]])
print("TEST SOLUTION_Robert:       ", SOLUTION_Robert(String))
print("TEST SOLUTION_DarkWiiPlayer:", SOLUTION_DarkWiiPlayer(String))
print("TEST SOLUTION_Piglet:       ", SOLUTION_Piglet(String))
print("TEST SOLUTION_Renshaw:      ", SOLUTION_Renshaw(String))

The results show that only one answer fulfill 100% of OP's requirements. The other answers doesn't handle the first and ending double-quotes " properly.

INITIAL REQUIREMENT FROM OP     \"\72\101\108\108\111\32\87\111\114\108\100\33\" Don't Replace this!
TEST SOLUTION_Robert:           \"\72\101\108\108\111\32\87\111\114\108\100\33\" Don't Replace this!
TEST SOLUTION_DarkWiiPlayer:    "\72\101\108\108\111\32\87\111\114\108\100\33" Don't Replace this!
TEST SOLUTION_Piglet:           \34\72\101\108\108\111\32\87\111\114\108\100\33\34 Don't Replace this!  1
TEST SOLUTION_Renshaw:          "\72\101\108\108\111\32\87\111\114\108\100\33" Don't Replace this!

To finalize this post, one could dive a little deeper and check the code performances with a micro-benchmark which could be copy/paste directly in a Lua interpreter.

function SOLUTION_DarkWiiPlayer (String)
  local result = String:gsub('"[^"]*"', function(str)
                          return str:gsub('[^"]', function(char)
                                            return "\\" .. char:byte()
                          end)
  end)
  return result
end

function SOLUTION_Robert (String)
  local function ConvertToByteString (MatchedString)
    local ByteStrings = {}
    local Len         = #MatchedString
    
    ByteStrings[#ByteStrings 1] = [[\"]]
    
    for Index = 1, Len do
      local Byte = MatchedString:byte(Index)
      ByteStrings[#ByteStrings 1] = string.format([[\%d]], Byte)
    end
    
    ByteStrings[#ByteStrings 1] = [[\"]]
    
    return table.concat(ByteStrings)
  end
  local Result = String:gsub([["([^"] )"]], ConvertToByteString)
  return Result
end

function SOLUTION_Piglet (String)
  return String:gsub('%b""' , function (match)
                       local ret = ""
                       for _,v in ipairs{match:byte(1, -1)} do
                         ret = ret .. string.format("\\%d", v)
                       end
                       return ret
  end)
end

function SOLUTION_Renshaw (String)
  local function convert(str)
    local byte_str = ""
    for i = 1, #str do
      byte_str = byte_str .. "\\" .. tostring(string.byte(str, i))
    end
    return byte_str
  end
  local Result = string.gsub(String, "\"(.*)\"", function(matched_str)
                               return "\"" .. convert(matched_str) .. "\""
  end)
  return Result
end

---
--- Micro-benchmark environment
---

COUNT = 600000

function TEST_Function (Name, Function, String, Count)
  local TimerStart = os.clock()
  for Index = 1, Count do
    Function(String)
  end
  local ElapsedSeconds = (os.clock() - TimerStart)
  print(string.format("[%.25s] %f sec", Name, ElapsedSeconds))
end

String = "\"Hello World!\" Don't Replace this!"

TEST_Function("SOLUTION_DarkWiiPlayer", SOLUTION_DarkWiiPlayer, String, COUNT)
TEST_Function("SOLUTION_Robert",        SOLUTION_Robert,        String, COUNT)
TEST_Function("SOLUTION_Piglet",        SOLUTION_Piglet,        String, COUNT)
TEST_Function("SOLUTION_Renshaw",       SOLUTION_Renshaw,       String, COUNT)

The results shows that @DarkWiiPlayer's answer is the fastest one.

[   SOLUTION_DarkWiiPlayer] 6.363000 sec
[          SOLUTION_Robert] 9.605000 sec
[          SOLUTION_Piglet] 7.943000 sec
[         SOLUTION_Renshaw] 8.875000 sec

CodePudding user response:

you need string.gsub.

local a = "\"Hello World!\" Don't Replace this!"
local function convert(str)
  local byte_str = ""
  for i = 1, #str do
    byte_str = byte_str .. "\\" .. tostring(string.byte(str, i))
  end
  return byte_str
end
a = string.gsub(a, "\"(.*)\"", function(matched_str)
  return "\"" .. convert(matched_str) .. "\""
end)
print(a)


CodePudding user response:

local a = "\"Hello World!\" but not this!"

print(a:gsub('"[^"]*"', function(str)
  return str:gsub('[^"]', function(char)
    return "\\" .. char:byte()
  end)
end))

CodePudding user response:

local a = "\" Hello World! I want to replace this with a bytecoded version of this!\" but not this!"

print((a:gsub('%b""' , function (match)
  local ret = ""
  for _,v in ipairs{match:byte(1, -1)} do
    ret = ret .. string.format("\\%d", v)
  end
  return ret
end)))
  • Related