4

I'm very new to ruby at the moment but I came from a PHP background and must say that I enjoy doing ruby, alot. It's a really nice language and the community is strict but helpful.

Today I was looking at stackoverflow and checked one of my answers to a question to generate a random string using PHP. I had actually written a script for this so I thought, why not share it!

This script has some modifiers which allow you to choose wether you want to include the following sets

  1. lowercase a-z
  2. [1] + uppercase a-z
  3. [1, 2] + numbers
  4. [1, 2, 3] + special characters
  5. [1, 2, 3, 4] + some crazy voodooh characters

So in this PHP script I physically typed each set into an array e.g.:

$charSubSets = array(
    'abcdefghijklmnopqrstuvwxyz',
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
    '0123456789',
    '!@#$%^&*()_+{}|:">?<[]\\\';,.`~',
    'µñ©æáßðøäåé®þüúíóö'
);

and this was basically my way of being able to define complexity right there.

Now this looks alright, even in the code but ruby has ranges and ranges are something new and shiny for me to play with so I was thinking of building a random string generator later today just to get some more experience with it.

Now for my question, I know that you can do the following things with a range including:

  1. 'a'..'z'
  2. 'A'..'Z'
  3. 0..9

etc.. But I was thinking, could you also make a range with special characters? as in, only special characters? and if that is possible, would you also be able to do the same for the crazy voodooh?

The reason I'm asking is because there is no example in the docs or anything on SO explaining this.

Community
  • 1
  • 1
SidOfc
  • 4,552
  • 3
  • 27
  • 50
  • 1
    *could you also make a range with special characters? as in, only special characters?* For character Ranges, ruby looks at the ascii codes for the beginning and end of the Range. For the range `'!'..'?'` in Sidney's answer, the start of the range, `!`, has an ascii code of 31, and the end of the range, `?`, has an ascii code of 63, so all the characters with ascii codes between start and end will be included in the range. Take a look at an ascii chart. Ranges also work for non-ascii characters, too. In that case, look at a unicode chart, e.g. `("\u01f1".."\u01fa").to_a` – 7stud Nov 13 '15 at 12:27
  • @7stud that is amazing, I answered this question Q&A style because I just tried it out myself and saw no clear example on the internet so I *tried* to create one here! Ruby and PHP are so different when it comes to convenience too. – SidOfc Nov 13 '15 at 12:37
  • *I answered this question Q&A style.* Ha! I didn't notice. – 7stud Nov 13 '15 at 12:49

2 Answers2

5
  1. Check out Range#to_a which is gotten from Enumerable. Note that on the left hand side of the docs it says that Range includes Enumerable, which means that the methods in Enumerable can be called on Ranges. If you can't find a method in a class, see what modules the docs say are included and click on the link to the included module.

  2. Check out Array#shuffle.

  3. Check out Array#join

  4. Check out Array#[], which will take a range as a subscript, so you can take a slice of an array of random characters.

  5. A two dot Range includes the end. A three dot Range doesn't include the end:

    p (1...5).to_a #=> [1, 2, 3, 4]

Putting it all together:

chars = (0..9).to_a + ('A'..'z').to_a + ('!'..'?').to_a

10.times do 
  puts chars.shuffle[0..5].join
end

--output:--
I(m,E.
%_;i(3
rb=_ef
kJrA9n
YA`e.K
89qCji
Ba1x3D
acp)=8
2paq3I
U0>Znm

(Shakespeare will appear there eventually.)

7stud
  • 46,922
  • 14
  • 101
  • 127
  • This is a very fine addition to the original question, this helps alot - thanks! – SidOfc Nov 13 '15 at 12:35
  • 2
    `chars.sample(5).join ` – steenslag Nov 13 '15 at 14:32
  • 2
    You could also write `chars = [*0..9, *'A'..'z', *'!'..'?']`. The third time I executed `sizes = Array.new(1 + rand(20)) { 1 + rand(5) }; sizes.map { |n| chars.sample(n).join }.join(' ')`, `sizes` was `[2,5,2,3,4,2,5,4,3]` and `map/join` returned "If music be the food of love, play on.". – Cary Swoveland Nov 13 '15 at 20:44
  • @Cary Swoveland, Darn it. I've got to keep trying! – 7stud Nov 13 '15 at 22:21
3

Yes - this is certainly possible. Fire up your console e.g. irb or pry.

1. for the special characters:

('!'..'?').to_a
# => [
#     "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-",
#     ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":",
#     ";", "<", "=", ">", "?"
# ]

2. for the 'voodooh' characters:

('µ'..'ö').to_a
# => [
#     "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á",
#     "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î",
#     "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö"
# ]

This is trivial to just try tho, the position (and kb index of the key) on your keyboard for the end special character defines what characters come inbetween, if I'd pick a ~ instead of a ? for the end it would look like this:

('!'..'~').to_a
# => [
#     "`", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",",
#     "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
#     ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F",
#     "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
#     "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "a",
#     "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
#     "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{",
#     "|", "}", "~"
# ]

basically if character a is 65 and z is 90 then all characters inbetween like b which is 66 will be included, it works like that for anything you put in a range and since in ruby everything is an object, you can use anything in a range as long as it implements certain methods as explained by the docs!

EDIT (13-11-2015)

After doing some playing around in my console I came to this solution which "mimics" the given PHP example and perhaps even completes it.

def rng(length = 10, complexity = 4)
    subsets = [("a".."z"), ("A".."Z"), (0..9), ("!".."?"), ("µ".."ö")]
    chars = subsets[0..complexity].map { |subset| subset.to_a }.flatten
    # => [
    #     "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
    #     "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
    #     "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
    #     "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
    #     "W", "X", "Y", "Z", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "!", "\"",
    #     "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".",
    #     "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":",
    #     ";", "<", "=", ">", "?", "µ", "¶", "·", "¸", "¹", "º", "»",
    #     "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç",
    #     "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó",
    #     "Ô", "Õ", "Ö"
    #  ]
    chars.sample(length).join
end

Now calling rng will produce results like this:

rng         # => "·boÇE»Ñ¼Á¸"  
rng(10, 2)  # => "nyLYAsxJi9"  
rng(20, 2)  # => "EOcQdjZa0t36xCN8TkoX"

EDIT#2 (14-05-2020)

As pointed out below in the comments, I did not even provide a documentation link to the relevant concept, in Ruby this is called a Range and can be found here (2.5.0).

If you need docs for your specific version, try googling for ruby range [your ruby version]. You can find out what your version is by running ruby -v in the terminal. Happy rubying :D

all dates are in dd-mm-yyyy format

SidOfc
  • 4,552
  • 3
  • 27
  • 50
  • 1
    *note I left out the ` character on purpose as it closed off my code section.* --Yeah, numbered lists don't work well with code, so I avoid them and just use `1), 2), 3)` – 7stud Nov 13 '15 at 12:37
  • @7stud Ahh that was not the reason tho - it is about enclosing inline code with backticks, but a backtick was an actual character within a range I typed so that closed my inline code before it was finished :) I got this code into a numbered list by adding 2 spaces at the end to create a newline within the same paragraph so that the code below it would be in there to :) – SidOfc Nov 13 '15 at 12:39
  • Okay, I looked up: Use double backticks around the outside if there is a literal backtick inside. :) – 7stud Nov 13 '15 at 12:45
  • @7stud Aight! Thanks for that one, I will remember that! – SidOfc Nov 13 '15 at 14:49
  • 1
    `chars.shuffle.sample(4)` A double shuffle? – 7stud Nov 13 '15 at 22:20
  • @SidOfc i know its a long time ago but im new to ruby and i want to know where is the documentation of `('!'..'~')` where i can learn more about its origin – StudentAccount4 May 14 '20 at 01:11
  • Hi @studentaccount4, awesome that you want to learn Ruby, it is still one of my favourite languages to work in today! The concept shown here is called a "Range" in Ruby, here's a link to the docs for Ruby 2.5.0 (mostly will work for newer versions as well): https://docs.ruby-lang.org/en/2.5.0/Range.html. I'll update my answer to include the link as well, good luck! – SidOfc May 14 '20 at 10:55
  • @SidOfc thanks for reaching out I already looked it up before asking you :) but I'm asking where the `('!'..'~')` come from why the `!` and `~` why not other signs?! – StudentAccount4 May 14 '20 at 15:05
  • Ahh right, there is no specific documentation for this, it is based on the `ord` value of `!` and `-`, running `'!'.ord` will return 33, and running `'-'.ord` will return 45, so this range includes all characters within that numeric range :) – SidOfc May 14 '20 at 15:18
  • @studentaccount4 as to why no other signs, it is because these happen to include the most common ones found on a keyboard, run: `('!'..'-').to_a` to see what it includes. If you want to include more, you'd need to find another end for the range with a higher `.ord` value (its not perfect, but for random password generation it was good enough for me at the time hehe) :) – SidOfc May 14 '20 at 15:20
  • @SidOfc I ran it and it contains 94 elements I think but my question is how does ruby knows where to go from after reading the `!` char? does it iterate through the ASCII table or what? – StudentAccount4 May 14 '20 at 15:43
  • @studentaccount4 your question has actually morphed multiple times, initially it was not very clear and I figured a doc link would do, then it became "why these two signs and not others" which I also explained, as for your third unique question, yes, it uses the ASCII table when you feed it character boundaries, this could have been asked as just "does it loop through the ASCII table" from the start. Also, SO doesn't like long threads very much (which this has now grown into) so I hope the "right" question was answered this time :) – SidOfc May 14 '20 at 18:40