Ruby - Map characters to integers in 2d array - ruby

I have a problem I can't for the life of me solve. I'm writing a Ruby app (I've been a PHP developer for 8 years, just starting with Ruby) that sells tickets for a concert hall. Each seat has a row (a...z) and a number (1...x). The database model has row (string) and num (int) for each seat.
How can I convert my array of seats from the database into a 2d array? For example, seat A1 would go into seat[1][1] = "value"; seat C4 would map to seat[3][4] = value. The issue is converting the row string to Ascii and subtracting the offset? Or is there an easier way?
Many thanks

The simplest way is probably to use a hash instead. For example: seat['A'][1] = value
But if you really need an array for some reason, then the method you describe is the simplest. Assuming the row string is a single character 'A' through 'Z', you can do it using row_string[0] - ?A (or row_string[0] - ?A + 1 if you want the index starting at 1 as in your example). For a multi-character version where row AA is after row Z, you can do this in 1.8.7 and newer:
row_num = row_string.bytes.inject(0) {|total, x| total = total * 26 + x - ?A + 1}
You may want to upcase your row string beforehand, just to be on the safe side.
In 1.8.6 and below, String does not have a bytes method. You can accomplish the same thing by doing:
row_num = 0
row_string.each_byte {|x| row_num = row_num * 26 + x - ?A + 1}

Ok, the solution I've come up with that seems to do the trick:
seat_array = Hash.new{|h,k| h[k]=Hash.new(&h.default_proc) }
for seat in self.seats
seat_array[seat.row.downcase][seat.num] = seat
end
return seat_array
Many thanks to everyone for such quick and useful responses. I'll certainly be helping others with PHP!

Well to get the index of a letter you could do something like this
('A'..'Z').to_a.index('C')
which would return 2
I notice in the array examples you gave you started A = 1 instead of 0.

Related

Optimizing Array Memory Usage

I currently have a very large array of permutations, which is currently using a significant amount of RAM. This is the current code I have which SHOULD:
Count all but the occurrences where more than one '1' exists or three '2's exist in a row.
arr = [*1..3].repeated_permutation(30).to_a;
count = 0
arr.each do |x|
if not x.join('').include? '222' and x.count(1) < 2
count += 1
end
end
print count
So basically this results in a 24,360 element array, each of which have 30 elements.
I've tried to run it through Terminal but it literally ate through 14GB of RAM, and didn't move for 15 minutes, so I'm not sure whether the process froze while attempting to access more RAM or if it was still computing.
My question being: is there a faster way of doing this?
Thanks!
I am not sure what problem you try to solve. If your code is just an example for a more complex problem and you really need to check programatically every single permumation, then you might want to experiment with lazy:
[*1..3].repeated_permutation(30).lazy.each do ||
# your condition
end
Or you might want to make the nested iteratior very explicit:
[1,2,3].each do |x1|
[1,2,3].each do |x2|
[1,2,3].each do |x3|
# ...
[1,2,3].each do |x30|
permutation = [x1,x2,x3, ... , x30]
# your condition
end
end
end
end
end
But it feels wrong to me to solve this kind of problem with Ruby enumerables at all. Let's have a look at your strings:
111111111111111111111111111111
111111111111111111111111111112
111111111111111111111111111113
111111111111111111111111111121
111111111111111111111111111122
111111111111111111111111111123
111111111111111111111111111131
...
333333333333333333333333333323
333333333333333333333333333331
333333333333333333333333333332
333333333333333333333333333333
I suggest to just use enumerative combinatorics. Just look at the patterns and analyse (or count) how often your condition can be true. For example there are 28 indexes in your string at which a 222 substring could be place, only 27 for the 2222 substring... If you place a substring how likely is it that there is no 1 in the other parts of the string?
I think your problem is a mathematics problem, not a programming problem.
NB This is an incomplete answer, but I think the idea might give a push to the proper solution.
I can think of a following approach: let’s represent each permutation as a value in ternary number base, padded by zeroes:
1 = 000..00001
2 = 000..00002
3 = 000..00010
4 = 000..00011
5 = 000..00012
...
Now consider we restated the original task, treating zeroes as ones, ones as twos and twos as threes. So far so good.
The whole list of permutations would be represented by:
(1..3**30-1).map { |e| x = e.to_s(3).rjust(30, '0') }
Now we are to apply your conditions:
def do_calc permutation_count
(1..3**permutation_count-1).inject do |memo, e|
x = e.to_s(3).rjust(permutation_count, '0')
!x.include?('111') && x.count('0') < 2 ? memo + 1 : memo
end
Unfortunately, even for permutation_count == 20 it takes more than 5 minutes to calculate, so probably some additional steps are required. I will be thinking of further optimization. Currently I hope this will give you a hint to find the good approach yourself.

Algorithm to rank numeric passcode?

I'm studying a (mobile) app where I need to get a user PIN:
a numeric "passcode" of 6/8 ciphers, input with something like this UI:
So, in a registration step, the user configure his one passcode (as it would be a password).
Let say the passcode must have a fixed size (say 8 ciphers: ********)
My question is related to a possible algorithm to verify/check the number that user choose, giving a bad rank in case of repeated ciphers or standard cipher patterns (12345678, 00001111), easily predicible by a malicious crackers...
Any idea for such an algorithm ?
At firs glance the algorithm could discourage (bad rank) a passcod containing repeated ciphers, simething like:
00117788
88886611
or "usual" ascending/descending patterns as:
12345678
98765432
Or numeric patterns related to personal, by example in my case, I'm born in 02 September 1963, so it could be a bad idea to have as passcode:
02091963
Instead, sequence that appear to me as "good" could be by example these one:
18745098
90574808
07629301
Collateral question: do you think that a numeric passcode of let say 8 ciphers could be an acceptable solution as "password" to validate a payment transaction ?
BTW, I'm coding in Ruby.
thanks for your patience!
giorgio
For your first 2 cases:
Number of repeated consecutive characters in the string:
str = "00117788"
str.chars.each_cons(2).select {|a,b| a == b}.count
#=> 4
Or as #CarySwoveland pointed out this will have the same result
str.size - str.squeeze.size
#=> 4
Number of incremented characters
str = "12345678"
str.chars.map(&:to_i).each_slice(2).select {|a,b| (a + 1) == b || (a - 1) == b }.count
#=> 4
#note this will also return 4 for "12563478"
# you could also use str.chars.map(&:to_i).each_cons(2).select {|a,b| (a + 1) == b || (a - 1) == b }.count
# This will return 7 for "12345678" and still return 4 for "12563478"
You could combine the above 2 as well like
str = "00117788"
str.chars.map(&:to_i).each_cons(2).select {|a,b| (a + 1) == b || (a - 1) == b || a == b }.count
#=> 6
As for the "personal" issue if you have the birth day then something as simple as this should work:
require 'date'
birth_day = Date.new(1963,9,2)
str = "02091963"
str == birth_day.strftime("%d%m%Y")
#=> true
Although for the last one I would suggest comparing multiple formats e.g. %Y%m%d and %m%d%Y etc. you could even do something like
str.chars.sort == birth_day.strftime("%d%m%Y").chars.sort
#=> true
To make sure they don't just use those numbers in some jumbled format.
Hopefully this would get you started since I don't know what your thresholds are for "good" and "bad" these are just suggestions for checking the values. Although it seems the definition for "good" should just be not "bad". Sort of like a validity check.
If I were to suggest a score of < 4 using methods 1 and 2 (or the combination method) && not an assortment of birth_day numbers would probably be sufficient e.g.
def tester(str,birth_date)
return false if ![6,8].include?(str.size)
b_day = birth_date.strftime("%Y%m%d").chars.sort
str.chars.map(&:to_i).each_cons(2).select do |a,b|
(a + 1) == b ||
(a - 1) == b ||
a == b
end.count < 4 && b_day != str.chars.sort
end
tester("00112233",Date.new(1963,9,2))
#=> false
tester("18745098",Date.new(1963,9,2))
#=> true
Seems like it works with your examples
arry = ["00117788","88886611","12345678","98765432","02091963","18745098","90574808","07629301"]
Hash[arry.map{|v| [v,tester(v,Date.new(1963,9,2))]}]
#=>=> {"00117788"=>false, "88886611"=>false,
"12345678"=>false, "98765432"=>false,
"02091963"=>false, "18745098"=>true,
"90574808"=>true, "07629301"=>true}

Ruby: turn each into collect

Each lecture has a stars representing its rating, from 1 to 5, and I want to select the sub_lectures with stars >= 5. Here is what I've done:
sub_lectures = []
lectures.each do |lec|
sub_lectures << lec if lec[:stars] >= 5
end
#lectures = sub_lectures
But I think this is quite inelegant. I know there is a collect method, which could return array by default.
How can I use collect to simplify my code?
I think you're looking for the select method.
#lectures = lectures.select {|l| l[:stars} >= 5}
Solution using collect:
#lectures = lectures.collect { |lec| lec if lec[:stars] >= 5 }.compact
Without calling compact the result array would contain nil values for the lectures that does not satisfy the condition. The solution using select is the actually the best one.

How do I convert a spreadsheet "letternamed" column coordinate to an integer?

In spreadsheets I have cells named like "F14", "BE5" or "ALL1". I have the first part, the column coordinate, in a variable and I want to convert it to a 0-based integer column index.
How do I do it, preferably in an elegant way, in Ruby?
I can do it using a brute-force method: I can imagine loopping through all letters, converting them to ASCII and adding to a result, but I feel there should be something more elegant/straightforward.
Edit: Example: To simplify I do only speak about the column coordinate (letters). Therefore in the first case (F14) I have "F" as the input and I expect the result to be 5. In the second case I have "BE" as input and I expect getting 56, for "ALL" I want to get 999.
Not sure if this is any clearer than the code you already have, but it does have the advantage of handling an arbitrary number of letters:
class String
def upcase_letters
self.upcase.split(//)
end
end
module Enumerable
def reverse_with_index
self.map.with_index.to_a.reverse
end
def sum
self.reduce(0, :+)
end
end
def indexFromColumnName(column_str)
start = 'A'.ord - 1
column_str.upcase_letters.map do |c|
c.ord - start
end.reverse_with_index.map do |value, digit_position|
value * (26 ** digit_position)
end.sum - 1
end
I've added some methods to String and Enumerable because I thought it made the code more readable, but you could inline these or define them elsewhere if you don't like that sort of thing.
We can use modulo and the length of the input. The last character will
be used to calculate the exact "position", and the remainders to count
how many "laps" we did in the alphabet, e.g.
def column_to_integer(column_name)
letters = /[A-Z]+/.match(column_name).to_s.split("")
laps = (letters.length - 1) * 26
position = ((letters.last.ord - 'A'.ord) % 26)
laps + position
end
Using decimal representation (ord) and the math tricks seems a neat
solution at first, but it has some pain points regarding the
implementation. We have magic numbers, 26, and constants 'A'.ord all
over.
One solution is to give our code better knowlegde about our domain, i.e.
the alphabet. In that case, we can switch the modulo with the position of
the last character in the alphabet (because it's already sorted in a zero-based array), e.g.
ALPHABET = ('A'..'Z').to_a
def column_to_integer(column_name)
letters = /[A-Z]+/.match(column_name).to_s.split("")
laps = (letters.length - 1) * ALPHABET.size
position = ALPHABET.index(letters.last)
laps + position
end
The final result:
> column_to_integer('F5')
=> 5
> column_to_integer('AK14')
=> 36
HTH. Best!
I have found particularly neat way to do this conversion:
def index_from_column_name(colname)
s=colname.size
(colname.to_i(36)-(36**s-1).div(3.5)).to_s(36).to_i(26)+(26**s-1)/25-1
end
Explanation why it works
(warning spoiler ;) ahead). Basically we are doing this
(colname.to_i(36)-('A'*colname.size).to_i(36)).to_s(36).to_i(26)+('1'*colname.size).to_i(26)-1
which in plain English means, that we are interpreting colname as 26-base number. Before we can do it we need to interpret all A's as 1, B's as 2 etc. If only this is needed than it would be even simpler, namely
(colname.to_i(36) - '9'*colname.size).to_i(36)).to_s(36).to_i(26)-1
unfortunately there are Z characters present which would need to be interpreted as 10(base 26) so we need a little trick. We shift every digit 1 more then needed and than add it at the end (to every digit in original colname)
`

Apply function to each element in array and store result in an array

I have a function toWords which converts a integer into a word
e.g. toWords(500, tableWords) gives fivehundred
I have an array of numbers h = (1..999).to_a, and I want to go through this array and convert each number into a word and store it in a new array. My current attempt to do this is:
h = (1..999).to_a
Lh = h.each do |i| toWords(i, tableWords) end
However, the contents of Lh is simply the integers from 1 to 999 and not the output of my toWords function. How do I do this? I'm thinking of something along the lines of sapply in R.
Even better is if my new array Lh can have two columns, the first column containing the integers in number format, and the second column would be the corresponding number in words.
Thank you!
To get your two columns, you can do the following
(1..999).map {|x| [x, toWords(x, tableWords)]}
As per Cicada's comment, the answer is:
Lh = h.map{|x| toWords(x, tableWords)}

Resources