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.
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
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.)
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
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.
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 _.
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.
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;
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.
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
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
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
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
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