Sequential Generation from an Index Value

I needed to be able to generate a sequence of letters from a specific index value. Basically, I wanted to loop through a sequence of values and retrieve the corresponding string value. For example, 0 would be A; 4 would be E; 36 would be AK; etc.

0 = A
1 = B
2 = C
3 = D
4 = E
5 = F
21 = V
22 = W
23 = X
24 = Y
25 = Z
26 = AA
27 = AB
28 = AC
36 = AK
37 = AL
38 = AM
39 = AN

I have created a small python script as a proof of concept that does the calculation using two different methods. Method 1, uses a recursive function to build up the correct sequence from the index value. Essentially the code is treating the index integer as a value that has the index into a large 2D are encoded within it. We use the modulus method to extract the index for the particular row. We then subtract the difference from the index to see if there are any more letters stored in the encoded index. Method 1 returns the value of the sequence in the correct order.

Method 1:

def get_name(index):
    """
    Translate the index to a set of letters in sequential order.
    """

    #termination criteria
    if index < 0:
        return ''

    #Take the modulus of the current index and translate it into one of the
    #characters stored in the letters string.
    selected_index = index % lettersCount

    return letters[selected_index] + get_name(math.floor((index - lettersCount)/lettersCount))

Method 2 works in a similar manor, yet simpler, to Method 1. However it calculates the sequence in reverse order. So in order for the result to be useful, the sequence must be reversed.

Method 2:

def get_name2(index, values = []):
    """
    Translate the index to a set of letters in sequential order, but reversed.
    """

    if not values:
        values = []

    #termination criteria
    if index < 0:
        return values

    #Take the modulus of the current index and translate it into one of the
    #characters stored in the letters string.
    selected_index = index % lettersCount
    values.append(letters[selected_index])

    return get_name2(math.floor((index - lettersCount)/lettersCount), values)

Here is a script to demonstrate the methods:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-

"""

    index = 0, value = A
    index = 1, value = B
    index = 2, value = C
    index = 3, value = D
    index = 4, value = E
    index = 5, value = F
    index = 6, value = G
    index = 7, value = H
    index = 8, value = I
    index = 9, value = J
    index = 10, value = K
    index = 11, value = L
    index = 12, value = M
    index = 13, value = N
    index = 14, value = O
    index = 15, value = P
    index = 16, value = Q
    index = 17, value = R
    index = 18, value = S
    index = 19, value = T
    index = 20, value = U
    index = 21, value = V
    index = 22, value = W
    index = 23, value = X
    index = 24, value = Y
    index = 25, value = Z
    index = 26, value = AA
    index = 27, value = AB
    index = 28, value = AC
    index = 29, value = AD
    index = 30, value = AE
    index = 31, value = AF
    index = 32, value = AG
    index = 33, value = AH
    index = 34, value = AI
    index = 35, value = AJ
    index = 36, value = AK
    index = 37, value = AL
    index = 38, value = AM
    index = 39, value = AN
"""

import math
import sys

letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
# letters = 'ABCDEF'
lettersCount = len(letters)

def get_name(index):
    """
    Translate the index to a set of letters in sequential order.
    """

    #termination criteria
    if index < 0:
        return ''

    #Take the modulus of the current index and translate it into one of the
    #characters stored in the letters string.
    selected_index = index % lettersCount

    return letters[selected_index] + get_name(math.floor((index - lettersCount)/lettersCount))

def test1():
    print(letters)
    print('index = 8: ', get_name(8)[::-1])
    print('index = 15: ',get_name(15)[::-1])
    print('index = 17: ',get_name(17)[::-1])
    print('index = 18: ',get_name(18)[::-1])
    print('index = 75: ',get_name(75)[::-1])
    print('index = 1000: ',get_name(1000)[::-1])
    print()

    for i in range(100):
        print('index = {}, value = {}'.format(i, get_name(i)[::-1]))

def get_name2(index, values = []):
    """
    Translate the index to a set of letters in sequential order, but reversed.
    """

    if not values:
        values = []

    #termination criteria
    if index < 0:
        return values

    #Take the modulus of the current index and translate it into one of the
    #characters stored in the letters string.
    selected_index = index % lettersCount
    values.append(letters[selected_index])

    return get_name2(math.floor((index - lettersCount)/lettersCount), values)

def test2():
    print(letters)

    for i in range(1000):
        print('index = {}, value = {}'.format(i,
                                              ''.join(reversed(get_name2(i)))))

def main():
    """
    This runs the rest of the functions in this module
    """

    print('test1')
    test1()

    print('test2')
    test2()

    return 0 # success

if __name__ == '__main__':
    status = main()
    sys.exit(status)

Mercurial Push/Pull and Update scripts

I like mercurial as a version control system. It has a number of advantages over more traditional systems such as subversion. I won’t go into details, they are easy to find on the internet. What I have found with mercurial is that I organize all of my repos under a root directory. I also use TortoiseHG as a graphical client that manages the commits and push/pull cycles. It works well for a single repository. Unfortunately it doesn’t work as well for a large number of repositories, that is it can’t do batch push/pull or updates.

I put together a couple of python scripts (conversion of windows batch files that I was using) to batch pull/push and update the repositories.

The scripts all share a common module that holds common methods. It is called shared.py and it contains a method that iterates through all of the directories in a given root directory and returns the directories that match the dir_to_find criteria. In our case we are looking for directories that contain the mercurial ‘.hg’ directory. The shared.py module also contains a method that executes a shell statement within a particular directory.

shared.py:

#! /usr/bin/env python

import os

"""Contains shared methods used between the python mercurial scripts"""

def findDirectories(root, dir_to_find):

	"""Walks through the directory structure from the root and finds the directories that match dir_to_find"""

	foundDirs = []

	for root, dirs, files in os.walk(root):

		for d in dirs:

			if d.upper() == dir_to_find.upper():

				foundDirs.append(os.path.join(root, d))

	return foundDirs

def executeHGStatement(statement, dir):

	"""Takes a string and executes it in the passed in directory."""

	os.chdir(dir)

	os.system(statement)

In order to pull changes from the remote repositories you use the pull.py script. Starting from the directory the script is located in it searches recursively for all the ‘.hg’ directories. From there it takes the parents of the ‘.hg’ directories and executes the ‘hg pull’ command in them.

pull.py:

#! /usr/bin/env python
import os
import sys
import shared

"""This module will recurse through the current folder and discover the mercurial repositories. It then executes the hg pull command on each repository pulling the changes from the remote repository to the local one."""

if __name__ == '__main__':
	for item in shared.findDirectories(sys.path[0], '.hg'):
		repo = os.path.dirname(item)
		print 'Repository: ' + repo
		shared.executeHGStatement('hg pull',repo)

In addition to pulling changes, changes can also be pushed to remote repositories using the push.py. It works identical to the pull.py except it issues the ‘hg push’ command.

push.py:

#! /usr/bin/env python
import os
import sys
import shared

"""This module will recurse through the current folder and discover the mercurial repositories. It then executes the hg pull command on each repository pushing the changes to the remote repository."""

if __name__ == '__main__':
	for item in shared.findDirectories(sys.path[0], '.hg'):
		repo = os.path.dirname(item)
		print 'Repository: ' + repo
		shared.executeHGStatement('hg push',repo)

Typically after pulling changes using the pull.py script, the changes need to be applied to the repository. This is where the update.py script comes into play. It works identically to the other two scripts except it executes ‘hg update’.

update.py:

#! /usr/bin/env python
import os
import sys
import shared

"""This module will recurse through the current folder and discover the mercurial repositories. It then executes the hg pull command on each repository updating to the changes from the remote repository."""

if __name__ == '__main__':
	for item in shared.findDirectories(sys.path[0], '.hg'):
		repo = os.path.dirname(item)
		print 'Repository: ' + repo
		shared.executeHGStatement('hg update',repo)

The nice thing about this scripts is that they can be placed at the root of any repository folder and they will automatically find any new repositories that are added at a later time. That is also the drawback. They need to be copied to every root folder.

The scripts could be modified to read the repositories paths from a file. That way the scripts could be moved to a more standard area such as /usr/local/scripts and be shared among the users of the computer.

In addition the scripts could probably be combined into one file and a command line option passed to indicate what action to perform. Personally, I think the separate scripts make it easier to work with in nautilus (or what ever graphical file display you may be working on). You would have to create some sort of shortcut or launcher with the proper command line. Executing that single script from the command line would be much easier with the command line switches in place. I may create a new script that in the near future that does just that.

Cheers,

Troy