3
\$\begingroup\$

I am learning Python 3. One thing I am doing to get some repetition is going through checkio. It is a website with coding exercises. One of the exercises gives an input string as follows:

str_to_nbrs("(2,2),(6,2),(2,6)")

str_to_nbrs("(3,7),(6,10),(9,7)")

I need the output of this code to produce a list of sub lists with the pairs of numbers so I can put them into variables to use for calculations.

This exercise actually has to do with x and y coordinates and circles. I am not sure why this input was in the form of a string. But, it gives me a chance to learn how to use the tools to convert a string like this to numbers that I can use in mathematical processes.

I figured out how to accomplish what I need. I am relatively new to Python 3. But, from what I have seen already, it is a well-thought-out and efficient language. I would bet there is a better way to do it with less code.

Please note that I added the big obvious comments to explain what the code is doing. They are not part of my regular coding style.

One more thing: I am still trying to come up with a convention for variable names. I have started lists with "l_". I try to give things meaningful names, but also try to keep them relatively short. I know "l_work" isn't a good name. But, I just wanted to finish that section of code, and used that...err...unimaginative name.

import re

# str_to_nbrs("(2,2),(6,2),(2,6)")

def str_to_nbrs(data):
    print("String input:")
    print(data)
    print()
    new_data = re.sub(r"\(","",data)
    new_data = new_data + ","

    print("Remove left parenthesis and add a comma a the end for split method:")
    print(new_data) 
    print()

    l_data = new_data.split("),")
    l_data.pop()
    print("Reformatted string data in a list:")
    print(l_data)
    print()

    l_work = []
    l_nbrs = []
    l_all = []

    for i in l_data:
        l_work = i.split(",")
        for j in l_work:
            l_nbrs.append(int(j))
        l_all.append(l_nbrs)
        l_nbrs = []
    print("Integers in sub lists:")
    print(l_all)
    print()
    print("---------------------")
    print()


str_to_nbrs("(2,2),(6,2),(2,6)")
str_to_nbrs("(3,7),(6,10),(9,7)")
\$\endgroup\$
3
\$\begingroup\$

re.sub(r"\(","",data) is not a very good use of regular expressions. You could have more easily achieved the same result with data.replace("(", "").

But since you are already using regular expressions, why not use them to do more:

>>> re.findall(r'\d+,\d+', "(2,2),(6,2),(2,6)")
['2,2', '6,2', '2,6']

This regular expression finds all digits (\d) (allows also multiple digits using +), which are separated only by a comma. Since different pairs also have parenthesis around them, this just gives you a list of the pairs.

By adding parenthesis around the digits, this captures each of them and splits each pair:

>>> re.findall(r'(\d+),(\d+)', "(2,2),(6,2),(2,6)")
[('2', '2'), ('6', '2'), ('2', '6')]

Now all that is left to do is convert them to integers and put it in a function:

def str_to_nbrs(data):
    return [list(map(int, p)) for p in re.findall(r'(\d+),(\d+)', data)]
\$\endgroup\$
  • 1
    \$\begingroup\$ Yes - string.replace(). I was looking at the TutorialsPoint RegExp page. I should have looked at the String page. I had no idea that re.findall() existed. I need to look at the Python 3 Documentation more. I bookmarked the RegExp page at section 6.2. I am still learning where to go for reference/documentation. The official Python Documentation is so big. But, I need to learn to find things there. \$\endgroup\$ – Gina Aug 22 '18 at 20:55
  • \$\begingroup\$ @gina try devdocs.io/python~3.6 \$\endgroup\$ – hjpotter92 Aug 23 '18 at 21:03
2
\$\begingroup\$

The Abstract Syntax Trees module is perfect for converting string literals into python values, such as lists and tuples. Specifically, the ast.literal_eval() method in AST Helpers.

def str_to_nbrs(data):
    return ast.literal_eval("[" + data + "]")

>>> str_to_nbrs("(2,2),(6,2),(2,6)")

[(2, 2), (6, 2), (2, 6)]

Surrounding the string "(2,2),(6,2),(2,6)" with [ and ] characters will turn it into the literal string for a list, which is necessary to ensure the result is a nested list of tuples.

ast.literal_eval("[(2,2),(6,2),(2,6)]")  # [(2,2),(6,2),(2,6)]  list of tuples
ast.literal_eval("(2,2),(6,2),(2,6)")    # ((2,2),(6,2),(2,6))  tuple of tuples
ast.literal_eval("[(2,2)]")              # [(2,2)]              list of 1 tuple
ast.literal_eval("(2,2)")                # (2,2)                Oops! Not a tuple of tuples!

Note: You can surround the string with with ( ), but "((2,2))" still evaluates as the tuple (2,2). You need the trailing comma "((2,2),)" in order to convert to the nested tuple of exactly 1 tuple: ((2,2),)

Note: Using ast.literal_eval() is about 4 times slower than Graipher's method, but it handles general python literals, including strings, and dictionaries in addition to lists of tuples. With great power comes reduced speed.


Code review comments on your implementation

(Excluding what Graipher has already mentioned)

You can use slices to get selected portions of lists. Consider l_data. After .split()-ing, you use .pop() to get rid of the last element.

l_data = new_data.split("),")
l_data.pop()

You can split the data and get rid of the last element in one statement, using the slice [:-1]. The slice :-1 translates to "all elements excluding the last one".

l_data = new_data.split("),")[:-1]

The statement l_work = [] is unnecessary. l_work = i.split(",") will overwrite the initialized list.

Initializing l_nbrs = [] outside your loop is unnecessary, and requires that you reinitialize it at the end of the loop. Instead, move it inside the loop, at the top:

for i in l_data:
    l_work = i.split(",")
    l_nbrs = []            # Initialized here
    for j in l_work:
        l_nbrs.append(int(j))
    l_all.append(l_nbrs)

This is referred to lately as keeping your code DRY (Don't Repeat Yourself), in contrast to WET (Write Everything Twice) code.

List comprehension is a powerful tool. The inner loop can be replaced with the following single statement:

    l_nbrs = [ int(j) for j in l_work ]

Once you understand that, the code can be reduced further by avoiding the single use l_work and l_nbrs variables::

for i in l_data:
    l_all.append( [ int(j) for j in i.split(",") ] )
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.