Generate permutations with symbols

Goal: Create a combination of emails based from inputted first name, last name, middle name, and a domain. Add in common separators. Then I'll check which one is correct with the rapportive API. This is the first part of the bigger script.

If you are given the string variables

{fn}
{ln}
{fi}
{li}
{mi}
{mn}


How would you create the following?

{fn}
{ln}
{fn}{ln}
{fn}.{ln}
{fi}{ln}
{fi}.{ln}
{fn}{li}
{fn}.{li}
{fi}{li}
{fi}.{li}
{ln}{fn}
{ln}.{fn}
{ln}{fi}
{ln}.{fi}
{li}{fn}
{li}.{fn}
{li}{fi}
{li}.{fi}
{fi}{mi}{ln}
{fi}{mi}.{ln}
{fn}{mi}{ln}
{fn}.{mi}.{ln}
{fn}{mn}{ln}
{fn}.{mn}.{ln}
{fn}-{ln}
{fi}-{ln}
{fn}-{li}
{fi}-{li}
{ln}-{fn}
{ln}-{fi}
{li}-{fn}
{li}-{fi}
{fi}{mi}-{ln}
{fn}-{mi}-{ln}
{fn}-{mn}-{ln}
{fn}_{ln}
{fi}_{ln}
{fn}_{li}
{fi}_{li}
{ln}_{fn}
{ln}_{fi}
{li}_{fn}
{li}_{fi}
{fi}{mi}_{ln}
{fn}_{mi}_{ln}
{fn}_{mn}_{ln}


at the moment I am solving it by creating an array for each permutation

fi_perms = [fi].product ['_' + li,
'_' + ln,
'-' +li,
'-' ln,
'.' + li,
'.' + ln,
li,
ln,
mi '_' ln,
mi '-' ln,
mi + '.' + ln,
mi + ln]
fn_perms = [fn].product ['_' + li,
'_' + ln,
'_' + mi '_' + ln,
'_' + mn '_' + ln,
'-' + li,
'-' + ln,
'-' + mi + '-' + ln,
'-' + mn + '-' ln,
'.' + li,
'.' + ln,
'.' + mi '.' +ln,
'.' + mn '.' ln,
li,
ln,
mi + ln,
ln,
mi + ln,
mn + ln]
li_perms = [li].product ['_' + fi,
'_' + fn,
'-' + fi,
'-' + fn,
'.' + fi,
'.' + fn,
fi,
fn]
ln_perms = [ln].product [ln,
'_' + fi,
'_' + fn,
'-' + fi,
'-' + fn,
'.' + fi,
'.' + fn,
fi,
fn]


because I will be using it later by adding it to another array like so

perms = li_perms + ln_perms + fi_perms + fn_perms
permutations = []

perms.count.times do |i|
perms.each do |perm|
permutations[i] = perm.join
end
end

permutations[0] = ['fn.mn.ln']


Is there a better of doing this?

• Is there any pattern in the expected permutations? – tokland Mar 4 '14 at 9:12
• shouldn't the second {fn}{mi}{ln} be {fn}.{mi}{ln}? and is the order required in that way exactly? – Vogel612 Mar 4 '14 at 10:32
• @Vogel612 it does not have to be exact. – Patrick Mar 4 '14 at 17:38
• Wild guess here — first name, last name, first initial, last initial, middle initial, middle name? What are you really trying to accomplish, and why don't you ask that instead? – 200_success Mar 4 '14 at 17:59
• @200_success Create a combination of emails based from inputted first name, last name, middle name, and a domain. Add in common separators. Then I'll check which one is correct with the rapportive API. This is the first part of the bigger script. – Patrick Mar 4 '14 at 18:23

Since your list is not "all permutation", but is painstakingly built by hand, I would not suggest using array's permutation API or something like that, but keep the curated mode you are using.

I would suggest building it in a more readable way. In your way of [fi].product[...] it is very hard to follow which permutation exists, and which doesn't. The first list you show is more readable, and if you name your atoms correctly (first_name instead of fn), it makes it trivial to understand what you are trying to do. I would suggest building your permutation table as a string like this:

name_permutations = <<PERMS
{last_initial}{first_name}
{last_initial}.{first_name}
{last_initial}{first_initial}
{last_initial}.{first_initial}
{first_initial}{middle_initial}{last_name}
{first_initial}{middle_initial}.{last_name}
{first_name}{middle_initial}{last_name}
{first_name}.{middle_initial}.{last_name}
{first_name}{middle_name}{last_name}
{first_name}.{middle_name}.{last_name}
{first_name}-{last_name}
{first_initial}-{last_name}
{first_name}-{last_initial}
{first_initial}-{last_initial}
{last_name}-{first_name}
{last_name}-{first_initial}
{last_initial}-{first_name}
{last_initial}-{first_initial}
...
PERMS


And then use substitutions to get all permutations:

name_permutations.gsub('{first_name}', first_name)
.gsub('{last_name}', last_name)
.gsub('{middle_name}', middle_name)
.gsub('{first_initial}', first_initial)
.gsub('{middle_initial}', middle_initial)
.gsub('{last_initial}', last_initial)
.split(\$/)

• I like this approach for the question posed, although I think the all possible permutations of names, initials, and separators is a more interesting problem. – Jonah Mar 8 '14 at 6:55
• @Jonah - For that you could (1..9).map{ |i| [fi,fn,mi,mn,li,ln,'.','-','_'].permutations(i).map(&:join) }.flatten (ruby-doc.org/core-2.1.1/Array.html#method-i-permutation) – Uri Agassi Mar 8 '14 at 7:29
• Good point that's clever – Jonah Mar 8 '14 at 14:34
• @UriAgassi just a minor error that permutations should be singular permutation – Patrick Mar 10 '14 at 3:35

You could just create a few simple helper methods. Here's an example, based on the assumption that ordering is not important. Rather than the usual approach of presenting the code and then showing how it is used, I have reversed those steps, as the code is so simple that most readers will be able to glean it merely from its application.

There are two helper methods, gen and middle_with_seps, The variables fn, mn and ln refer to "first name", "middle name" and "last name". The first, middle and last initials are: fi = fn[0], mi = mn[0] and li = ln[0]. The constants should be self-explanatory. (The application of the code is best appreciated when one of these is playing in the background.)

Application

fn, mn, ln = 'Wild', 'Bill', 'Hickok'

gen([fn, fi], DASH_USCORE                                    , [ln, li] ) +
gen([ln, li], DASH_USCORE                                    , [fn, fi] ) +
gen(li      , DOT                                            , fn       ) +
gen(li      , NOSPACE_DOT                                    , fi       ) +
gen(fn      , middle_with_seps(mi, DASH_USCORE + NOSPACE_DOT), ln       ) +
gen(fn      , middle_with_seps(mn, DASH_USCORE + NOSPACE_DOT), ln       ) +
gen(fi      , middle_with_seps(mi, DASH_USCORE + NOSPACE)    , ln       ) +
gen(fi      , middle_with_seps(mi+'.', NOSPACE)              , ln       )

#=> ["Wild_Hickok", "Wild_H", "Wild-Hickok", "Wild-H",
#      "W_Hickok", "W_H", "W-Hickok", "W-H",
#    "Hickok_Wild", "Hickok_W", "Hickok-Wild", "Hickok-W",
#      "H_Wild", "H_W", "H-Wild", "H-W",
#    "H.Wild", "HW", "H.W",
#    "Wild_B_Hickok", "Wild-B-Hickok", "WildBHickok", "Wild.B.Hickok",
#    "Wild_Bill_Hickok", "Wild-Bill-Hickok","WildBillHickok","Wild.Bill.Hickok",
#    "W_B_Hickok", "W-B-Hickok", "WBHickok",
#    "WB.Hickok"]


Code

NOSPACE     = ['']
DOT         = ['.']
NOSPACE_DOT = NOSPACE + DOT
DASH_USCORE = ['-', '_']

fi, mi, li = fn[0], mn[0], ln[0]

def combine_strings(s1, s2, s3) "#{s1}#{s2}#{s3}" end

def gen(left_strings, seps, right_strings)
left_strings  = [left_strings].flatten
seps          = [seps].flatten
right_strings = [right_strings].flatten
left_strings.each_with_object([]) { |l,a| seps.each { |sep|
right_strings.each { |r| a << combine_strings(l, sep, r) } } }
end

def middle_with_seps(m, seps)
seps.each_with_object([]) { |s,a| a << combine_strings(s, m, s) }
end


Explanation

gen()'s arguments are as follows:

• left_strings : an array of strings for the left end of the string
• seps : an array of separators (e.g., '', '.', '-', etc.)
• right_strings: an array of strings for the right end of the string

If left_strings, seps or right_strings is entered as a string, rather than an array of strings, it is converted to an array containing itself. gen constructs an array of strings, one for each combination of strings taken from left_strings, seps and right_strings.

middle_with_seps's arguments are as follows:

• m is the middle name or initial
• seps is the same as for the method gen

middle_with_seps creates an array of separators that is passed as the argument seps in gen() when the middle name or initial is to be included. Each element of that array is the (string) value of m bracketed by a separator (except in one case where the initial 'B' is converted to 'B.' ('WB.Hickok'). For example,

middle_with_seps(mi, DASH_USCORE + NOSPACE) #=> ["_B_", "-B-", "B"]

• Why are you giving such unreadable names to your variables and functions? Your code is very hard to read, and you need to tell us the la is an array for the left end of the string, instead of calling it left_end_strings or something more meaningful - this is code-review, after all... – Uri Agassi Mar 16 '14 at 7:08
• @Uri, there was a method to my madness, even if I have miscalculated, as I appear to have done (for the author of code is usually not the best one to judge its readability). My intent was for the lines beginning gen([fn, fi], ['_', '-']... to tell the reader at a glance what the two helpers did. That wouldn't have worked if I had generate_combinations([first_name, last_name], ['_', '-']... as lines would have wrapped or required horizontal scrolling, defeating the purpose. I've made some changes to my answer. I welcome suggestions for further improvement. Thanks. – Cary Swoveland Mar 16 '14 at 18:54