Coding Challenge #4: Non-repeating character

Coding Challenge #4

Welcome back, to another coding challenge! Sorry for the long wait. Expect these challenges to be more frequent! Probably once every two days.

As usual, same requirements:

  • Has to be written in Lua 5.1 (the version roblox uses)
  • The program has to be compatible with Roblox Studio, meaning you can’t use features vanilla Lua has but rbxlua disabled
  • You can’t use httpservice to cheat the challenge

Introduce a function NonRepeatingChar(str), given a string str of only lowercase letters, return the first non-repeating character. If there is no non-repeating character simply return an underscore _.

Here are some examples

"aaaabcc" --> b
"bbaccdb" --> a
"aacbbdf" --> c
"aababd" --> d
"aabababd" --> d
"aabbcc" --> _

Goodluck! You can find a list of all the challenges here or a github version here.

Capture

Answer!

My code is quite messy and not efficient, you can definitely come up with something better! Basically what I do is each determine the next character, and see if #string.split(str, character) is 2, beacuse if the character was repeated only once, string.split will return 2 portions.

local str = "aaaab"

local function NonRepeatingChar(str)
   local start = 1
   local check = string.sub(str,1,1)
   while string.find(str, "%l", start) do
       if #string.split(str, check) == 2 then
           return check 
       else
           start = select(2,string.find(str,check.."+",start))+1
           check = string.sub(str, start, start)
       end
   end
   return "_"
end
22 Likes

This is a much simpler challenge than the other ones. Here’s my approach:

code
local function NonRepeatingChar(str)
	for i = 1, #str do
		local char = str:sub(i, i)
		if (not str:find(char, i + 1)) and str:find(char) == i then
			-- if the char can't be found after this pos,
			-- and the char's first position is at this pos
			return char
		end
	end

	return "_"
end

(At first I returned nil instead of an underscore, whoops. Fixed it.)

5 Likes

oof. It took me a while to do this because I havent done something like this before, so this is my first try. I am still not good at manipulating strings.

The code is terrible, sorry for that but it works so ¯_(ツ)_/¯

click
function NoRepeatingChar(str)
local word = str
local len = string.len(word)

local num1 = 0
local num2 = 0

local caughtChar

for i = 1, len do
	local char1 = string.sub(word,num1, num2)
	num1 = num1 + 1
	num2 = num2 + 1
	local char2 = string.sub(word, num1, num2)


	if (char1 ~= char2) and (char1 ~= "" and char2 ~= "") then
		caughtChar = char2
		return caughtChar
	end
end


if not caughtChar then
	return "_"
end
end

local test = NoRepeatingChar("abbc")
print(test)

I would love some feedback if I am doing any bad practice or something wrong

1 Like

Thank you for your contribution as usual! Is ‘much simpler’ bad or good? Because I’m gonna honestly start aiming to make more of these instead of the hard ones which will surely attract more people.

2 Likes

I wouldn’t say it’s bad, but it definitely took a lot less time for me to write up a solution. There’s nothing wrong with making it more accessible, though.

@FastAsFlash_Dev Your sample didn’t work for me, I tried it with “abbc” and it returned _.

3 Likes

Thank you for participating! Your code seems to be partially not work. If I inputed ""eeeeeedsd" it would return d even though d is repeated, it should return s. Also as posatta stated, if I inputed ""abbc" it would return _ even though it’s supposed to return a. I think the reason is you’re only checking if the current character and the next character are not the same, meaning if you had dddcdc it will get to dddcdcd and find out that "c" ~= "d" and return d even though there is another d ahead.

Also you have a lot of little uneeded parts. For
example,

local word = str 
local len = string.len(word)  

No need to save these, use str and string.len(str) (or even #str straight away.

num1 = num1 + 1 	
num2 = num2 + 1 	
local char1 = string.sub(word, num1, num2)
num1 = num1 + 1 	
num2 = num2 + 1
local char2 = string.sub(word, num1, num2)

This can simply be replaced using i

local char1 = string.sub(str, i, i)
locla char2 = string.sub(str, i+1, i+1)
1 Like

I found the error :slight_smile: I will update my code

1 Like

I fixed it by removing the line where it adds 1 to both num1 and num2 and it works now
image

Thank you for the coding challenge!

OH, I misunderstood the problem and thought we had to only check the current character and the next character.

1 Like

I’ll have a go. It collects the string to see if it’s the repeated, and find the next non-repeated string.

Here's the code
function NonRepeatingChar(str)
	local i = 1;
	local r = '';
	while i do
	    if not str:find(str:sub(i, i), i + 1) then
	        return str:sub(i, i);
	    end;
	    r = r .. str:sub(i, i);
	    i = str:find(('[^%s]'):format(r), i);
	end;
	return '_';
end;
1 Like

Well, you didn’t impose any limits on memory consumption. Using string.gsub is costly due to the immutability of strings in Lua, but it makes counting a piece of cake. Although, it might actually be less than your string.split solution because yours creates an array of strings for every character. Not sure which is actually worse. They’re both not great.

local function FirstNonRepeatingChar(Input)
  for i=1,#Input do
    local Char = string.sub(Input,i,i)
    local _, Count = string.gsub(Input,Char,"")
    if Count == 1 then
      return Char
    end
  end
  return "_"
end

local Tests = {
  aaaabcc = "b";
  bbaccdb = "a";
  aacbbdf = "c";
  aababd = "d";
  aabababd = "d";
  aabbcc = "_";
}

for Str,Result in pairs(Tests) do
  if FirstNonRepeatingChar(Str)==Result then
    print(Str..": passed")
  else
    print(Str..": failed")
  end
end

This code traverses the input string, and counts each character’s repetitions using the second return of a global substitution. By the nature of iterative traversing, it’ll return from the first non-repeating character, and it’ll return “_” if the iteration completed without returning anything.

Feedback on the challenge thread itself

It would make it much easier and faster for people to hop in if you start them with a boilerplate, like so:

local function FirstNonRepeatingChar(Input)
    -- Your algorithm here
end

local Tests = {
  aaaabcc = "b";
  bbaccdb = "a";
  aacbbdf = "c";
  aababd = "d";
  aabababd = "d";
  aabbcc = "_";
}

for Str,Result in pairs(Tests) do
  if FirstNonRepeatingChar(Str)==Result then
    print(Str..": passed")
  else
    print(Str..": failed")
  end
end

This allows us to get right to the juicy part instead of writing up the tests and basic run logic. Some people in the topic didn’t properly test their code, and I think having this template would have helped them along greatly.

5 Likes

Method #1: I’d probably go about it like this:

local function NonRepeatingChar(str)
    local Counts = {}
    
    for _, char in next, string.split(str, "") do
        Counts[char] = Counts[char] and Counts[char] + 1 or 1
    end
    
    for char, count in next, Counts do
        if (count == 1) then return char end 
    end
    
    return "_"
end

Method #2:

local function NonRepeatingChar(str)
    for char in str:gmatch(".") do
        if (({ str:gsub(char, "") })[2] == 1) then
            return char
        end
    end
    
    return "_"
end
Bonus: horrible, messy and pretty much unreadable
local function NonRepeatingChar(str)
    local ch, cc = {}, {}; for c in str:gmatch(".") do ch[table.concat(ch, ""):find(c) or #ch + 1] = c
    cc[table.concat(ch, ""):find(c)] = cc[table.concat(ch, ""):find(c)] and cc[table.concat(ch, ""):find(c)] + 1 or 1; end
    return (math.min(table.unpack(cc)) == 1 and ch[table.concat(cc):find(math.min(table.unpack(cc)))] or "_")
end
2 Likes

My approach was to first go over the string to build up information on character sighting count and order, stored in two tables. A first sighting creates adds it to the end of the order table. Further sightings just increase its sighting count. Then to guarantee that a winning character is the first one, the earliest order-table value that was only seen once is looked for.

local function NonRepeatingChar(str)
	local sighting_count = {}
	local first_sighting_order = {}

	for char in str:gmatch(".") do
		if sighting_count[char] then
			sighting_count[char] = sighting_count[char] + 1
		else
			table.insert(first_sighting_order, char)
			sighting_count[char] = 1
		end
	end
	
	for _, char in ipairs(first_sighting_order) do
		if sighting_count[char] == 1 then
			return char
		end
	end
	
	return "_"
end
1 Like

Out of the box method, turn the string into the bytes it has and see if it doesn’t repeat twice.

local function NonRepeatingChar(String)
    local Bytes = {};
    local CheckedBytes = {};

    for Character in string.gmatch(".") do
        table.insert(Bytes  string.byte(Character));
    end


    for i, Byte in ipairs(Bytes) do
        if (not table.find(CheckedBytes, Byte) and not table.find(Bytes, Byte, i) then
            return string.char(Byte);
        end

        table.insert(CheckefBytes, Byte);
    end

    return "_";
end
1 Like

string.byte only returns the first character of a string. You’d have to loop through all characters of the string first to convert it.

2 Likes

Hashes are useful

function NonRepeatingChar(str)
	local alphabet={}
	
	for i=1, #str do
		if alphabet[str:sub(i,i)] then
			continue
		end
		
		if (str:find(str:sub(i,i), i+1, true)) then
			alphabet[str:sub(i,i)] = 1
		else
			return str:sub(i,i)
		end
	end
	
	return '_'
end
1 Like

This one was definitely easier compared to your second one!

 
 
  local function NonRepeatingChar(str)
    
        if #str == 1 then return "_" end 



        local temp = {};
       
        local strTable = {}; 
        for i = 1, #str do table.insert(strTable, str:sub(i,i)) end         
 
        for i = 1, #str do  -- for every character, 
        local count = 0
        for _, s in ipairs(strTable) do 
                 if s == str:sub(i,i) then -- if matches were found , 
                 count = count + 1
                 temp[i] = s.."."..count
                 
                 end
             end
        end
 
  
        local allOne = {}

        for _, v in ipairs(temp) do 
            local split = v:split(".") 
            if tonumber(split[2]) == 1 then table.insert(allOne, split[1])
        end
        end

        return allOne[1] or "_" end
  
     
 

if there’s more than 1 unique character such as in this string:

 local str = "lollstr" -- only one s, one t, one r, one o
 print(NonRepeatingChar(str))
 -- outputs o

only one non repeating character:

print(NonRepeatingChar("aaadcacc"))
--> prints d

--as in posatta's case
print(NonRepeatingChar("abbc")) --> a
-- or 
 print(NonRepeatingChar("abc")) -- none repeat, so a is printed
3 Likes

A code-golf oriented solution:

local function nonRepeatingChar(str)
	for chr in str:gmatch(".") do 
		if #str - #str:gsub(chr, "") < 2 then
			return chr
		end
	end
	return "_"
end

Golfed:
function q(s)for c in s:gmatch"."do if#s-#s:gsub(c,"")<2 then return c end end return"_"end

4 Likes

A code-golf oriented solution using recursion:

local function NonRepeatingChar(str)
  local c = s:match("(.).*%1")
  return c and NonRepeatingChar(s:gsub(c, "")) or (s .. "_"):sub(1, 1)
end

Golfed:

function q(s)c=s:match"(.).*%1"return c and q(s:gsub(c,""))or(s.."_"):sub(1,1)end

Edit: removed parenthesis in match thanks to @sircfenner

4 Likes

roblox studio crashed two times, and i went insane 0/10

Sorry for that, perhaps use an online lua compiler next time.