<jupyter_start><jupyter_text>TileSetter v2.0Made by CadyPrefixes + Suffixes adapted from DenamLast updated 9/5/2021 Running This Code**Press Shift + Enter to run each individual code block.**The first several blocks are loading data + defining functions. The actual name constructor is several blocks down.<jupyter_code>###### Import block

import string
import pandas as pd
import numpy as np
import csv
import datetime as dt
import re
#### Initialize, update file paths if needed

# ----> Check for Updates
update_prefixes = False
update_suffixes = False

# ----> Current filepaths
f_sticky  = './data/stuck-strings/stuck-strings-2021-09-03.csv'
f_prefix  = './data/word_segments/from-denam/prefixes.csv'
f_suffix  = './data/word_segments/my-segments/suffixes.csv'
f_webpage = './HTML/webpage.html'
f_css     = 'style.css'

# ----> Load alphabet with and without underscores
abcs = pd.DataFrame(list(string.ascii_lowercase),columns=['Letter'])
abcs_ = abcs.append({'Letter':'~'},ignore_index=True)
# Loading Functions Block


#-! Importing Current Prefixes ---------------------------------#
def updateStuckPrefixes(prefix_path,stucks_path,alphabet=abcs_):
    
    print('Prefixes from file:   ' + prefix_path)
    print('Stickies from file:   ' + stucks_path)
    
    # ----> Load prefixes <----
    pre = pd.read_csv(prefix_path,names=['prefix'])
    pre.prefix = pre.prefix.str.lower()
    pre['L'] = pre.prefix.str.len()
    
    print('4L+ prefixes:         ' + str(len(pre)))
    
    # ----> Load current stuck 3L strings <----
    stuck_strings = pd.read_csv(stucks_path)
    stuck_strings.prefix = stuck_strings.prefix.str.lower()
    stuck_strings.marker = stuck_strings.marker.str.lower()

    pr3,markers = loadStuck3Ls(stuck_strings) 
    print('3L prefixes:          ' + str(len(pr3)))

    # ----> Combine 3L and 4L+ prefixes <----
    pre = pd.concat([pre, pr3],ignore_index=True)
    pre = pre.sort_values(by=['prefix','L']).reset_index(drop=True)
    pre = pre.drop_duplicates().reset_index(drop=True)
    
     
    # ----> Drop inactive prefixes <----
    N1 = len(pre[~pre.prefix.str.slice(stop=3).isin(pre.prefix[pre.L==3])])
    pre = pre[pre.prefix.str.slice(stop=3).isin(pre.prefix[pre.L==3])]
    pre, N2 = applyMarkers(pre,'prefix',markers)
        
    print('Outdated Prefixes:    ' + str(N1+N2))  
    print('Total Prefixes:       '+  str(len(pre)))
    
    # ----> Write CSV <----
    pre.replace('\~','_',regex=True)\
        .to_csv('./word_segments/my-segments/prefixes_current.csv', index=False)
    
    
    pre.replace('\_','~', regex=True)
    return pre,markers

#-! Load all 3L prefixes + markers names -----------------------------#
def loadStuck3Ls(stucks, abcs=abcs_):
    
    stucks = stucks.replace('\_','~',regex=True)
    new_pres = pd.DataFrame(columns=['prefix','L'])
    
    
    for i in stucks.prefix:
        dat = pd.DataFrame(columns=['pr','e'])

        L3 = i[2].lower()
        L3_i = abcs[abcs.Letter == L3].index.tolist()[0]

        dat['e']  = abcs[abcs.index >= L3_i].reset_index(drop=True).squeeze()
        dat['pr'] = dat.assign(pr = i[0:2])

        dat['prefix'] = dat.pr + dat.e
        dat['L'] = 3

        new_pres = pd.concat([new_pres,dat],ignore_index=True)

    new_pres.prefix = new_pres.prefix.str.lower()
    new_pres = new_pres.drop(['pr','e'],axis=1).sort_values(by='prefix').reset_index(drop=True)
    
    return new_pres,stucks

#-! Load Active Marker Names ---------------------------------#
def loadActiveMarkers(stucks_path):
    markers = pd.read_csv(stucks_path)
    markers = markers.replace('\_','~',regex=True)
    markers.prefix = markers.prefix.str.lower()
    markers.marker = markers.marker.str.lower()
    return markers

#-! Apply Active Marker Names ---------------------------------#
def applyMarkers(namebits,bit_label,markers,abcs_=abcs_):
    # ----> Prepare input name strings <----
    strs = pd.DataFrame( {
        'bits': namebits[bit_label].copy(), 
        'L':    namebits.L.copy()           
    })

    # ----> Prepare 3L sticky strings ('starts') <----
    marky_bits = pd.DataFrame( {
        'bits': markers.prefix.copy(),   
        'L':    1000
    })
    marky_bits.index = 'p' + marky_bits.index.astype(str)
    
    # ----> Prepare marker names ('stops') <----
    marky_marks = pd.DataFrame({
        'bits': markers.marker.copy(),
        'L':    1000
    })  
    marky_marks.index = 'm' + marky_marks.index.astype(str)
    
    # ----> Smoosh the things together <----
    yar = pd.concat([marky_bits,strs,marky_marks])\
            .sort_values(by=['bits','L'])
    
    # ----> Reindex and rename <----
    yar = yar.reset_index()
    yar = yar.rename(columns={'index':'flag'})
    yar.flag = yar.flag.astype(str)
    yar['L'] = yar.bits.str.len()
    
    # ----> Find the start and stop points <----
    starts = yar[yar.flag.str.contains('p')]\
                .index.tolist()
    stops  = yar[yar.flag.str.contains('m')]\
                .index.tolist()
    
    lims = pd.DataFrame({'start': starts, \
                         'stop':  stops})

    # ---> Make List of indicies to drop < ----
    for i in lims.index:     
        yeet = np.arange( lims.start[i], lims.stop[i])
        
        if i == 0:
            yardrop = yeet
        else:
            yardrop = np.insert(yardrop,len(yardrop),yeet)

    yar = yar.copy().drop(yardrop,axis=0) # Drop names
    
    if bit_label == 'Neopet':
        yar[~yar['flag']=='p0'] # Drop marker from already-generated names
    
    yar = yar.drop('flag',1).reset_index(drop=True) # Drop flag column
    yar = yar.rename(columns={'bits': bit_label})
    
    return yar, len(yardrop)



#-! Importing Denam's Suffixes ----------------------------------------------##
def LoadDenamSuffixes():
    suffix = pd.read_csv('./data/word_segments/from-denam/3L.csv',names=['suffix'])
    suffix['L'] = 3

    suf = ['abc','def','ghi','jkl','mno','pqr','stu','vwxyz']
    for i in suf:
        dat = pd.read_csv('./word_segments/4L_'+i+'.csv',names=['suffix'])
        dat['L'] = dat.suffix.str.len()

        suffix = suffix.append(dat, ignore_index=True)
    
    return suffix


# -----> Load Prefixes
if update_prefixes == True:
    pres_,marks = updateStuckPrefixes(f_prefix,f_sticky)
else:
    pres_ = pd.read_csv('./data/word_segments/my-segments/prefixes_current.csv')
    pres_ = pres_.replace('\_','~', regex=True)
    marks = loadActiveMarkers(f_sticky)
    
pres = pres_.copy()[~pres_.prefix.str.contains('~')].reset_index(drop=True)

# -----> Load suffixes    
if update_suffixes == True:
    print('No current function available to update suffixes from new file.')
    print('Please create one.')
else:
    sufs = pd.read_csv(f_suffix)

######### Functions to make Pages ########

#-! Make Webpage --------------------#
def make_page(names,linky='petpage'):
    
    names['Neopet'] = names.Neopet.str.replace('~','_')

    css_tag = '<link rel="stylesheet" type="text/css" '+ \
              'href="' + f_css + '">'

    # ---> generate img + link urls
    names = get_links(names)
    
    # ---> Page build starts here
    page = [css_tag]
    
    # ---> + 1 'letter' div for each name length
    for i in names.L.unique():
        iLs = str(i) + ' Letters'
        page += ['<div class="letter">']
        page += ['<h2>'+str(iLs)+'</h2>']
        
        nameLs = names[names.L == i]
        
        # ---> Generate image+link code for 'pet' divs
        linkme = nameLs.Neopet + '<br>' + img_wrap(nameLs.img)
        divp = div_wrap(a_wrap(linkme,nameLs[linky]))
        
        page += divp.values.tolist() + ['</div><br>']

    # ---> Save webpage code using Panda's .to_csv
    webpage = pd.Series(page)  
    webpage.to_csv(f_webpage,
                    sep=',',header=False,index=False,quoting=csv.QUOTE_NONE)
    return

#-! Get links for names ----------------------------#
def get_links(names):
    names['petpage'] = 'http://neopets.com/~' + names.Neopet
    names['pound']   = 'http://neopets.com/pound/adopt.phtml?search=' + names.Neopet
    names['img'] = 'http://pets.neopets.com/cpn/' + names.Neopet + '/1/1.png'
    return names

#-! HTML wrapper functions --------------------------#
def div_wrap(to_wrap,clss='pet'):
    tagopen = '<div class="' + clss + '">'
    tagclosed = '</div>'
    return tagopen + to_wrap + tagclosed

def a_wrap(to_wrap,link_url):
    tagopen0 = '<a href="'
    tagopen1 = '">'
    tagclosed = '</a>'
    return tagopen0 + link_url + tagopen1 + to_wrap + tagclosed

def img_wrap(img_urls):
    tagopen = '<img src="'
    tagclosed = '" />'
    return tagopen + img_urls + tagclosed

# A thing that lets you "build" a name by defining the 'bits' the name is made out of
# 'prefix' bit will sample from prefix data
# 'suffix' bit will sample from suffix data
# any other input string will be used for all names
#     (e.g., 'ay' is first 'bit' --> all names will start with 'Ay'

class nameBuilder:
    
    # ----> Set up 
    def __init__(self,N=300):
        
        self.constructor = pd.DataFrame(columns=['bit','min','max'])
        
        # Set up dataframe variables
        self.N_names = N
        self.segments = pd.DataFrame(index=range(0,N))
        self.names = False   #created in once .construct() is called
        
        self.stats = {'time':'','format':'','name_data':''}

    # ----> adds prefix 'bit' to constructor
    def add_prefix(self,L=False,Lmin=3,Lmax=14):
        
        if L != False:
            Lmin = L
            Lmax = L
        
        if Lmin < 3:
            error('Prefixes must be at least 3 letters long!')
            return
        
        self.constructor = self.constructor.append({
            'bit': 'prefix',
            'min': Lmin,
            'max': Lmax 
            },ignore_index=True)
        
    # ----> adds suffix 'bit' to constructor
    def add_suffix(self, L = False, Lmin=1, Lmax=8):
        
        if L != False:
            Lmin = L
            Lmax = L

        self.constructor = self.constructor.append({
            'bit': 'suffix',
            'min': Lmin,
            'max': Lmax
            }, ignore_index=True)
    
    # ----> adds user-defined string 'bit'
    def add_bit(self,bit):
        self.constructor = self.constructor.append({
            'bit': bit,
            'min': len(bit),
            'max': len(bit) 
            }, ignore_index=True)
    
    # ----> Constructs name from constructor matrix
    def construct(self, pres=pres, sufs=sufs):
        
        # ---> iterate through name 'bits'
        name_format = '|'
        for i in self.constructor.index:
            
            bit = self.constructor['bit'][i]
            Lmin = self.constructor['min'][i]
            Lmax = self.constructor['max'][i]
            
            seggi = bit + '_' + str(i)
            name_format += bit + '|'

            # ---> Choose data to sample
            if bit == 'prefix':
                samply = pres[pres.L <= Lmax]
                samply = samply.rename(columns={'prefix': 'bit'})
                
            elif bit == 'suffix':
                samply = sufs[sufs.L <= Lmax]
                samply = samply.rename(columns={'suffix': 'bit'})
            
            else:
                samply = []
                       
            # ---> Sample from chosen data  
            if bit in ['prefix','suffix']:
                
                # ---> reduce # of generated names if sampling data is too small
                if len(samply) < self.N_names:
                    
                    self.segments = self.segments.drop(\
                                        index=range(len(samply),self.N_names))
                    self.N_names = len(samply)
                    
                self.segments[seggi] = samply.bit.sample(n = self.N_names).tolist() 
            
            # ---> or copy bit for entire column
            else:
                self.segments[seggi] = bit
                
        
        self.stats['format'] = name_format.strip() # updates word stats    
        
        # ---> turn name segments into names
        self.names = pd.DataFrame(index = self.segments.index,
                                  columns=['L','Neopet'])
        self.names.Neopet = ''
        
        for i in self.segments.columns:
            self.names.Neopet +=  self.segments[i]
            
        
        # ---> Get name lengths, sort, and clean up
        self.names.L = self.names.Neopet.str.len()
        self.names = self.names.drop_duplicates()\
                         .sort_values(by=['L','Neopet'])\
                         .reset_index(drop=True)
        
        # ---> Capitalize the names
        self.names.Neopet = self.names.Neopet.str.capitalize()
        
        # ---> Apply webpage function
        self.to_webpage()

    # ---> updates name statistics
    def update_stats(self):
        stats = {}
        for i in self.names.L.unique():
            ind = (str(i) + ' Letters')
            n = len(self.names[self.names.L == i])
            stats.update({ ind : n })
        stats.update({' ':'---',
                      'All Names':len(self.names)
                     })
        
        
        stats = pd.DataFrame.from_dict(stats,orient='index')
        stats.columns = [str('NUM')]
        
        self.stats['name_data'] = stats
        self.stats['time'] = dt.datetime.now().time().strftime("%I:%M:%S %p")
        
        
        self.show_stats()
        
    def show_stats(self):
        
        print('Constructor\n'+'------------------')
        print(self.constructor)
        
        print('------------------')
        print('\n' + 'Name Lengths' + 
              '\n' + '--------------')
        print(self.stats['name_data'])
        print('--------------')
        print('Last Update @ ' + self.stats['time'])
        
    def to_webpage(self,link_to='petpage'):
        make_page(self.names,link_to)
        self.update_stats()
        
#########################################################
print('Done loading and ready to use!')<jupyter_output>Done loading and ready to use!<jupyter_text>Name BuilderThis is where the magic happens! How to build a nameThe nameBuilder works by adding 'bits' of words together.* **Prefix** 'bits' sample from the prefix data* **Suffix** 'bits' sample from the suffix data* **string** 'bits' given by the user Example nameBuilder: pre_bob_sufThis example nameBuilder below makes names with:* a random prefix 3 letters or less* the user-defined string 'bob'* a random suffix with 4 letters or lessNames using this `nameBuilder` will look kind of like 'Prebobsuff', 'Xxxbobxxxx', 'Abcbobdefg', etc.<jupyter_code># Create nameBuilder
pre_bob_suf = nameBuilder()

# add in pieces
pre_bob_suf.add_prefix(Lmax=3)
pre_bob_suf.add_bit('bob')
pre_bob_suf.add_suffix(Lmax=4)

# now your nameBuilder is ready to be used!<jupyter_output><empty_output><jupyter_text>Make the namesOnce the nameBuilder is created, to get a list of names in that format, run the builder's `myBuilder.construct()` function. For this example, it looks like this:```pre_bob_suf.construct()```It will randomly choose prefixes & suffixes and update the webpage, and will also show you some stats:* the 'constructor' matrix* the number of names generated of each length* the number of names overall* the last time the constructor was run**You only need to construct the nameBuilder once!** In order to get a new set of names in that format, simply run the `.construct()` function again; it will choose a *new* set of prefixes & suffixes and update the webpage.The webpage is at `HTML/webpage.html`. The `HTML` directory is located in the same folder as this jupyter notebook.<jupyter_code># use nameBuilder
pre_bob_suf.construct()<jupyter_output>Constructor
------------------
      bit min max
0  prefix   3   3
1     bob   3   3
2  suffix   1   4
------------------

Name Lengths
--------------
            NUM
8 Letters     2
9 Letters    11
10 Letters  287
            ---
All Names   300
--------------
Last Update @ 01:40:32 PM<jupyter_text>nameBuilder Options Bit LengthsLimit prefix/suffix bit to a specific length:```myBuilder.add_prefix(L = 3)```Allow a range of lengths:```myBuilder.add_suffix( Lmin=2, Lmax=5 )``` Use UnderscoresPython alphabetizes underscores ('\_') at the *top* of the alphabet, unlike Neopets, which alphebatizes them at the bottom.To get around this, I use tildes ('~'), and the code is set up to convert tildes into underscores right before writing files.Use underscores in user-defined 'bits:```myBuilder.add_bit('~x~')```Sample from stuck prefixes w/ underscores:```myBuilder.construct( pres = pres_ )``` Other constructors to tryUser-defined prefix, random suffix```bob_suff = nameBuilder()bob_suff.add_bit('bob')bob_suff.add_suffix()```User-defined suffix, random prefix```pre_bob = nameBuilder()pre_bob.add_prefix()pre_bob.add_bit('bob')```Random prefix+suffix```pre_suf = nameBuilder()pre_suf.add_prefix()pre_suf.add_suffix()```  Stop HereThe code below this is older and no longer used, but I keep it for my own reference (and yours!) It's helpful to learn from, and maybe you can work it into your own functions, but you don't need to run any of it for the name builder to work properly.<jupyter_code>############### Name Generator Fuctions Block ##############
### I don't use these anymore, but you may want to
### not writing documentation, srry fam

#-! Names from user-specified string -----------------------------------#
def namesByString( vcn_, pre_suf, link_to = 'petpage', max_L=10, N=300,\
                   suffix = sufs, prefix = pres, alphabet = abcs):

    L_left = max_L - len(vcn_)
    names = pd.DataFrame(columns=['prefix','suffix','Neopet'])


    if pre_suf == 'suffix' and L_left >= 3:

        names['prefix'] = prefix[prefix.L <= L_left].prefix.sample(n=N,replace=True).reset_index(drop=True)

        names['suffix'] = vcn_


    elif pre_suf == 'prefix' and L_left >=1:
        names['suffix'] = suffix[suffix.L <= L_left].suffix.sample(n=N,replace=True).reset_index(drop=True)
        names['prefix'] = vcn_

    else:
        return 'Given string is too long for max_L. Increase max_L or decrease input string length.'

    names['Neopet'] = names.prefix.str.capitalize() + names.suffix.str.lower()

    names = names.drop_duplicates().sort_values(by='Neopet').reset_index(drop=True)

    webpage = make_page(names,linky=link_to)

    update = 'Names generated :' + str(len(names)) + '\n' + \
             pre_suf.capitalize() + ':      ' + vcn_ + '\n' + \
             'Shortest Name: ' + str(names.Neopet.str.len().min()) + '\n' + \
             'Longest Name:  ' + str(names.Neopet.str.len().max()) + '\n' + \
             'Timestamp:     ' + dt.datetime.now().time().strftime("%I:%M:%S %p")
    
    print(update)
    print(webpage)
    return 

#-! Names by Length -----------------------------------------------------#
def namesByL(L, N=300, link_to='petpage', \
             pre = pres, suffix = sufs, alphabet = abcs):

    names = pre[pre.L <= L].sample(n=N).reset_index(drop=True)
    names['Ls'] = L - names.L
    names['suffix'] = L - names.L

    for i in range(names.Ls.min(),names.Ls.max()+1):
        if i >=3:
            suf_samp = suffix[suffix.L == i].reset_index(drop=True)
            suf_samp = suf_samp.sample(n=len(names)).reset_index(drop=True)
            suf_samp = suf_samp.suffix.tolist()

            names.suffix = names.suffix.mask(names.Ls==i,suf_samp)

        elif i == 2:
            l1 = alphabet.Letter.sample(n = len(names),replace=True).reset_index()
            l2 = alphabet.Letter.sample(n = len(names),replace=True).reset_index()
            ls = l1+l2
            names.suffix = names.suffix.mask(names.Ls == i, ls.Letter.tolist())

        elif i == 1:
            l1 = alphabet.Letter.sample(n = len(names),replace=True).tolist()
            names.suffix = names.suffix.mask(names.Ls == i, l1)

        else:
            names.suffix = names.suffix.mask(names.Ls == i,'')


    names['Neopet'] = names.prefix.str.capitalize() + names.suffix
    names = names.drop_duplicates().sort_values(by='Neopet').reset_index(drop=True)

    webpage = make_page(names,linky=link_to)
    update = 'Names generated :' + str(len(names)) + '\n' + \
             'Timestamp:     ' + dt.datetime.now().time().strftime("%I:%M:%S %p")
    print(update)
    print(webpage)
    return

#-! Generates page -----------------------------------------------------#
######
# def make_page(names,linky='petpage'):
    
#     names['Neopet'] = names.Neopet.str.replace('~','_')

            
#     css_tag = '<link rel="stylesheet" type="text/css" '+ \
#               'href="/style.css">'

#     html0 = '<div class="pet"><a href="'
#     html1 = '">'
#     html2 = '<br><img src="http://pets.neopets.com/cpn/'
#     html3 = '/1/1.png"></a></div>'

#     page = pd.DataFrame(columns=['Neopet','html0','link','html1','html2','html3','html'])
#     page.Neopet = names.Neopet
#     page.html0 = html0
#     page.url = pet_url
#     page.html1 = html1
#     page.html2 = html2
#     page.html3 = html3
#     page.html = page.html0 + page.url    + \
#                 page.Neopet + page.html1 + \
#                 page.Neopet + page.html2 + \
#                 page.Neopet + page.html3

#     webpage = pd.Series(([css_tag]+list(page.html.values)))
#     webpage.to_csv('',
#                           sep=',',header=False,index=False,quoting=csv.QUOTE_NONE)
#     return webpage


# Name Generators!
# A bit outdated

# N = 300
# L = 6
# link = 'pound'

# name_bit = 'cuddle'
# bit_label = 'prefix'

# namesByL(L,N,link_to=link)
# #namesByString(name_bit,bit_label,max_L=L,link_to=link)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.17553142541639827, 'fraction_numerical': 0.007453759087144566, 'mean_word_length': 2.2271714922048997, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 76, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '167323', 'license': 'CC0-1.0', 'n_tokens_mistral': 7261, 'n_tokens_neox': 6944, 'n_words': 1549, 'path': 'tileset-functions.ipynb', 'repo_name': 'fizzacyst/tileSetting', 'size': '21734'}