combining these two lua formatting lines into one - lua

is there any way to combine these last two formatting lines of code into one?
str = "1, 2, 3, 4, 5, "
str = str:gsub("%p", {[","] = " >" }) -- replaces ',' with '>'
str = string.sub(str, 1, #str - 2) --removes last whitespace + comma
Thanks in advance :)

str = "1, 2, 3, 4, 5, "
str = str:sub(1, #str-2):gsub("%p", {[","] = " >" })
This will do what you want it to do.
Egor's is a bit more elegant, though:
str = str:gsub(',',' > '):sub(1,-3)

Related

How do I make it look like the picture in Lua

This is my code
local level = 5
for i = 1, level do
local text = ""
for j = 1, i do
text = text..""
end
for j = 1, level-i, 1 do
text = text.." "
end
for j = 1+level, level+(level-i) do
text = text.." "
end
for j = 1, level + i-level do
text = text..""
end
print(text)
end
I want the result to be similar to the one in the picture.
Here is what your code looks like with proper formatting.
local level = 5
for i = 1, level do
local text = ""
for j = 1, i do
text = text..""
end
for j = 1, level-i, 1 do
text = text.." "
end
for j = 1+level, level+(level-i) do
text = text.." "
end
for j = 1, level + i-level do
text = text..""
end
print(text)
end
Your current code prints... well... an empty string. You haven't yet added the characters it's to display to be on par with the image.
The amount of characters per row is 9. So you ideally need 9 characters per row. You will also be incrementing the number once per row. The amount of characters per row also increases by 2; one on each side.
We can use the string.rep(string, number) function to duplicate a 'string' 'number' times. You can feed in your current level into that so it generates 1 2 or 3 depending on the line the number of times. Then you have whitespace to worry about. You can use string.rep again along with a bit of distance math to calculate the amount of whitespace you need from what you take up. Then finally throw everything together concatenated trailing with the first string and print.
local levels = 5
local columns = 9
for i=1, levels do
local str = string.rep(i, i)
local padding = columns - (#str * 2) + 1
print(str .. string.rep(" ", padding) .. str)
end

Parsing and count words from multi lines text in Lua

Say I have the multi-lines text:
str = [[
The lazy dog sleeping on the yard.
While a lazy old man smoking.
The yard never green again.
]]
I can split each words using:
for w in str:gmatch("%S+") do print(w) end
But how I can get results as an example:
The = 3 words, line 1,3
Lazy = 2 words, line 1,2
Dog = 1 word, line 1
..and so on?
Thank you
You could detect the \n using gmatch like you are already to count the words.
The pattern would be something like "[^\n]+" and the code something like this:
local str = [[
The lazy dog sleeping on the yard.
While a lazy old man smoking.
The yard never green again.
]]
local words = {}
local lines = {}
local line_count = 0
for l in str:gmatch("[^\n]+") do
line_count = line_count + 1
for w in l:gmatch("[^%s%p]+") do
w = w:lower()
words[w] = words[w] and words[w] + 1 or 1
lines[w] = lines[w] or {}
if lines[w][#lines[w]] ~= line_count then
lines[w][#lines[w] + 1] = line_count
end
end
end
for w, count in pairs(words) do
local the_lines = ""
for _,line in ipairs(lines[w]) do
the_lines = the_lines .. line .. ','
end
--The = 3 words, line 1,3
print(w .." = " .. count .. " words , lines " .. the_lines)
end
Full output, note i also changed the pattern you used for capturing the words to "[^%s%p]+" i did this to remove the . that was getting attached to smoking, again, and yard.
smoking = 1 words , lines 2,
while = 1 words , lines 2,
green = 1 words , lines 3,
never = 1 words , lines 3,
on = 1 words , lines 1,
lazy = 2 words , lines 1,2,
the = 3 words , lines 1,3,
again = 1 words , lines 3,
man = 1 words , lines 2,
yard = 2 words , lines 1,3,
dog = 1 words , lines 1,
old = 1 words , lines 2,
a = 1 words , lines 2,
sleeping = 1 words , lines 1,

Ruby Parsing an array of string

This is a simple code to parse a simple string in ruby
str = "Amdh#34HB!x"
length = str.length
upper = str.scan(/[A-Z]/).count #count upper chars
lower = str.scan(/[a-z]/).count #count lower chars
digit = str.scan(/[0-9]/).count #count digits
special = str.scan(/[^a-z0-9]/i).count #count special chars
result = "Length : " + length.to_s + " Upper : " + upper.to_s + " Lower : " + lower.to_s + " Digit : " + digit .to_s + " Special : " + special.to_s
The result is given as "Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2"
I want to do the same things to an array of strings
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
and so I can know the details like above of each element of the array
Example :
array[0] = "Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2"
array[1] = "Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0"
array[2] = "Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1"
....
I know the answer seems simple by using each method but didn't find the right way to do it.
The code above is not optimised, if there is any better suggestion, you are welcome!
You need only make a single pass through the string to obtain the needed counts.
def obtain_counts(str)
str.each_char.with_object(Hash.new(0)) do |c,h|
h[case(c)
when /[[:upper:]]/ then :upper
when /[[:lower:]]/ then :lower
when /[[:digit:]]/ then :digit
else :special
end] += 1
end
end
def construct_array(arr)
arr.map! { |str|
"Length : #{str.length} Upper : %d Lower : %d Digit : %d Special : %d" %
obtain_counts(str).values_at(:upper, :lower, :digit, :special) }
end
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
construct_array(array)
#=> ["Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2",
# "Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0",
# "Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1"]
array
#=> ["Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2",
# "Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0",
# "Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1"]
Note that
["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"].map { |str| obtain_counts(str) }
#=> [{:upper=>3, :lower=>4, :special=>2, :digit=>2},
# {:upper=>3, :lower=>3, :digit=>2},
# {:special=>1, :digit=>3, :upper=>1, :lower=>3}]
Notice that the second hash in this array has no key :special (because the second string contained no special characters). That explains why, in obtain_counts, we need Hash.new(0) (empty hash with default 0), rather than simply {}.
You may use simple map to do this:
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
result = array.map do |str|
length = str.length
upper = str.scan(/[A-Z]/).count #count upper chars
lower = str.scan(/[a-z]/).count #count lower chars
digit = str.scan(/[0-9]/).count #count digits
special = str.scan(/[^a-z0-9]/i).count #count special chars
{length: length, upper: upper, lower: lower, digit: digit, special: special}
end
[117] pry(main)> result
=> [{:length=>11, :upper=>3, :lower=>4, :digit=>2, :special=>2},
{:length=>8, :upper=>3, :lower=>3, :digit=>2, :special=>0},
{:length=>8, :upper=>1, :lower=>3, :digit=>3, :special=>1}]
I guess you want to do this:
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
result = []
array.each do |str|
length = str.length
upper = str.scan(/[A-Z]/).count #count upper chars
lower = str.scan(/[a-z]/).count #count lower chars
digit = str.scan(/[0-9]/).count #count digits
special = str.scan(/[^a-z0-9]/i).count #count special chars
result << "Length : " + length.to_s + " Upper : " + upper.to_s + " Lower : " + lower.to_s + " Digit : " + digit .to_s + " Special : " + special.to_s
end
puts result
try this solution:
def string_check(str)
result = str.scan(/([A-Z])|([a-z])|([0-9])|([^a-z0-9])/).inject([0,0,0,0]) do
|sum,arr| sum.map.with_index{|e,i| e+(arr[i].nil? ? 0: 1) if !arr.nil?}
end
"Length : #{str.size} Upper : #{result[0]} Lower : #{result[1]} Digit : #{result[2]} Special : #{result[3]}"
end
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
array.each {|s| puts string_check(s)}
outputs:
Length : 11 Upper : 3 Lower : 4 Digit : 2 Special : 2
Length : 8 Upper : 3 Lower : 3 Digit : 2 Special : 0
Length : 8 Upper : 1 Lower : 3 Digit : 3 Special : 1
More DRY solution:
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"]
formats = { Upper: /[A-Z]/,
Lower: /[a-z]/,
Digit: /[0-9]/,
Special: /[^a-z0-9]/i }
array.map do |e|
"Length: #{e.length}, " +
formats.map {|k, v| "#{k}: #{e.scan(v).count}" }.join(', ')
end
#=> ["Length: 11, Upper: 3, Lower: 4, Digit: 2, Special: 2",
# "Length: 8, Upper: 3, Lower: 3, Digit: 2, Special: 0",
# "Length: 8, Upper: 1, Lower: 3, Digit: 3, Special: 1"]
Here's a start to help you move into a more OO Ruby script:
class Foo
attr_reader :str, :str_len, :upper, :lower, :digit, :punctuation
def initialize(str)
#str = str
#str_len = str.length
#upper, #lower, #digit, #punctuation = %w[upper lower digit punct].map { |re| str.scan(/[[:#{re}:]]/).count }
end
def to_s
('"%s": ' % str) +
[:str_len, :upper, :lower, :digit, :punctuation].map { |s|
'%s: %s' % [s.to_s.upcase, instance_variable_get("##{s}")]
}.join(' ')
end
end
array = ["Amdh#34HB!x", "AzErtY45", "#1A3bhk2"].map { |s| Foo.new(s) }
puts array.map(&:to_s)
Which, when run, outputs:
"Amdh#34HB!x": STR_LEN: 11 UPPER: 3 LOWER: 4 DIGIT: 2 PUNCTUATION: 2
"AzErtY45": STR_LEN: 8 UPPER: 3 LOWER: 3 DIGIT: 2 PUNCTUATION: 0
"#1A3bhk2": STR_LEN: 8 UPPER: 1 LOWER: 3 DIGIT: 3 PUNCTUATION: 1
The regular expression classes like [[:upper:]] are POSIX definitions, which help relieve some of the visual noise of a traditional expression's classes. See the Regexp documentation for more information.
It can be DRYer but that's an exercise for the student. You should be able to coerce this into something closer to what you want.

How do I split a string given an array of split positions?

I'm using Ruby 2.4 and Rails 5. I have an array of indexes within a line
[5, 8, 10]
How do I take the above array, and a string, and form anotehr array of strings that are split by the above indexes? FOr instance, if the string is
abcdefghijklmn
and split it based ont eh above indexes, I would have an array with the following strings
abcde
fgh
ij
klmn
Try this
str = "abcdefghijklmn"
positions = [5, 8, 10]
parts = [0, *positions, str.size].each_cons(2).map { |a, b| str[a...b] }
# => ["abcde", "fgh", "ij", "klmn"]
Or,
If the positions are constant and known ahead of runtime (for example if they were the format for a phone number or credit card) just use a regexp
str.match(/(.....)(...)(..)(.*)/).captures
# => ["abcde", "fgh", "ij", "klmn"]
This will get the Job done
str = "abcdefghijklmn"
arr_1 = [5, 8, 10]
arr_2, prev = [], 0
(arr_1.length + 1).times do |x|
if arr_1[x] == nil then arr_1[x] = str.size end
arr_2 << str[prev..arr_1[x] -1]
prev = arr_1[x]
end
p arr_2
---------------------------------------
Program Run Output
["abcde", "fgh", "ij", "klmn"]
---------------------------------------
I hope this Helps

Using Regex and ruby regular expressions to find values

So I'm currently trying to sort values from a file. I'm stuck on the finding the first attribute, and am not sure why. I'm new to regex and ruby so I'm not sure how to go about the problem. I'm trying to find values of a,b,c,d,e where they are all positive numbers.
Here's what the line will look like
length=<a> begin=(<b>,<c>) end=(<d>,<e>)
Here's what I'm using to find the values
current_line = file.gets
if current_line == nil then return end
while current_line = file.gets do
if line =~ /length=<(\d+)> begin=((\d+),(\d+)) end=((\d+),(\d+))/
length, begin_x, begin_y, end_x, end_y = $1, $2, $3, $4, $5
puts("length:" + length.to_s + " begin:" + begin_x.to_s + "," + begin_y.to_s + " end:" + end_x.to_s + "," + end_y.to_s)
end
end
for some reason it never prints anything out, so I'm assuming it never finds a match
Sample input
length=4 begin=(0,0) end=(3,0)
A line with 0-4 decimals after 2 integers seperated by commas.
So it could be any of these:
2 4 1.3434324,3.543243,4.525324
1 2
18 3.3213,9.3233,1.12231,2.5435
7 9 2.2,1.899990
0 3 2.323
Here is your regex:
r = /length=<(\d+)> begin=((\d+),(\d+)) end=((\d+),(\d+))/
str.scan(r)
#=> nil
First, we need to escape the parenthesis:
r = /length=<(\d+)> begin=\((\d+),(\d+)\) end=\((\d+),(\d+)\)/
Next, add the missing < and > after "begin" and "end".
r = /length=<(\d+)> begin=\(<(\d+)>,<(\d+)>\) end=\(<(\d+)>,<(\d+)>\)/
Now let's try it:
str = "length=<4779> begin=(<21>,<47>) end=(<356>,<17>)"
but first, let's set the mood
str.scan(r)
#=> [["4779", "21", "47", "356", "17"]]
Success!
Lastly (though probably not necessary), we might replace the single spaces with \s+, which permits one or more spaces:
r = /length=<(\d+)>\s+begin=\(<(\d+)>,<(\d+)>\)\send=\(<(\d+)>,<(\d+)>\)/
Addendum
The OP has asked how this would be modified if some of the numeric values were floats. I do not understand precisely what has been requested, but the following could be modified as required. I've assumed all the numbers are non-negative. I've also illustrated one way to "build" a regex, using Regexp#new.
s1 = '<(\d+(?:\.\d+)?)>' # note single parens
#=> "<(\\d+(?:\\.\\d+)?)>"
s2 = "=\\(#{s1},#{s1}\\)"
#=> "=\\(<(\\d+(?:\\.\\d+)?)>,<(\\d+(?:\\.\\d+)?)>\\)"
r = Regexp.new("length=#{s1} begin#{s2} end#{s2}")
#=> /length=<(\d+(?:\.\d+)?)> begin=\(<(\d+(?:\.\d+)?)>,<(\d+(?:\.\d+)?)>\) end=\(<(\d+(?:\.\d+)?)>,<(\d+(?:\.\d+)?)>\)/
str = "length=<47.79> begin=(<21>,<4.7>) end=(<0.356>,<17.999>)"
str.scan(r)
#=> [["47.79", "21", "4.7", "0.356", "17.999"]]
Sample input:
length=4 begin=(0,0) end=(3,0)
data.txt:
length=3 begin=(0,0) end=(3,0)
length=4 begin=(0,1) end=(0,5)
length=2 begin=(1,3) end=(1,5)
Try this:
require 'pp'
Line = Struct.new(
:length,
:begin_x,
:begin_y,
:end_x,
:end_y,
)
lines = []
IO.foreach('data.txt') do |line|
numbers = []
line.scan(/\d+/) do |match|
numbers << match.to_i
end
lines << Line.new(*numbers)
end
pp lines
puts lines[-1].begin_x
--output:--
[#<struct Line length=3, begin_x=0, begin_y=0, end_x=3, end_y=0>,
#<struct Line length=4, begin_x=0, begin_y=1, end_x=0, end_y=5>,
#<struct Line length=2, begin_x=1, begin_y=3, end_x=1, end_y=5>]
1
With this data.txt:
2 4 1.3434324,3.543243,4.525324
1 2
18 3.3213,9.3233,1.12231,2.5435
7 9 2.2,1.899990
0 3 2.323
Try this:
require 'pp'
data = []
IO.foreach('data.txt') do |line|
pieces = line.split
csv_numbers = pieces[-1]
next if not csv_numbers.index('.') #skip the case where there are no floats on a line
floats = csv_numbers.split(',')
data << floats.map(&:to_f)
end
pp data
--output:--
[[1.3434324, 3.543243, 4.525324],
[3.3213, 9.3233, 1.12231, 2.5435],
[2.2, 1.89999],
[2.323]]

Resources