OpenAI Assistance with Python code interpreter
Table of Contents
Preamble
The purpose of this article is to test the dexterity of the OpenAI Assistant in solving programming tasks based on their descriptions in English and the associated test suite requirements.
Prerequisites
OpenAI Assistant
with code_interpreter
as the tool and
instructions:
You are an experienced Python programmer proficient in test-driven development.
Functional Programming
A primitive task
Description
Write a function get_vowels_count that returns the number (count) of vowels in the given string. We will consider a, e, i, o, and u as vowels.
get_vowels_count must pass this assertion:
assert (get_vowels_count('John the dully boy write this book') == 9)
Answer
def get_vowels_count(s): return sum(1 for char in s.lower() if char in 'aeiou') # Assertion to validate the function assert (get_vowels_count('John the dully boy write this book') == 9)
A solution requiring nuanced comprehension
Task description
Write a function last_digit that takes in two numbers a and b and returns the last decimal digit of a^b. Note that a and b may be very large!
For example, the last decimal digit of 97 is 9, since 97 = 4782969. The last decimal digit of (2200)(2300), which has over 1092 decimal digits, is 6.
The inputs to your function will always be non-negative integers.
last_digit must pass successfully through all of these assertions:
assert(last_digit(4, 1) == 4) assert(last_digit(4, 2) == 6) assert(last_digit(9, 7) == 9) assert(last_digit(10, 10 ** 10) == 0) assert(last_digit(2 ** 200, 2 ** 300) == 6)
Answer
def last_digit(a, b): if b == 0: return 1 return pow(a, b, 10) # Assertions to validate the function assert(last_digit(4, 1) == 4) assert(last_digit(4, 2) == 6) assert(last_digit(9, 7) == 9) assert(last_digit(10, 10 ** 10) == 0) assert(last_digit(2 ** 200, 2 ** 300) == 6)
Validation
# Just a test print(pow(2**200, 2**300, 10))
6
Conclusion
Not bad at all!!!
Tony
Raw and obscure description
Test the ability to infer by submitting a poorly formatted task.
Description
Consider a sequence u where u is defined as follows:
The number u(0) = 1 is the first one in u. For each x in u, then y = 2 * x + 1 and z = 3 * x + 1 must be in u too. There are no other numbers in u. Ex: u = [1, 3, 4, 7, 9, 10, 13, 15, 19, 21, 22, 27, …]
1 gives 3 and 4, then 3 gives 7 and 10, 4 gives 9 and 13, then 7 gives 15 and 22 and so on…
Task:
Given parameter n the function dbl_linear (or dblLinear…) returns the element u(n) of the ordered (with <) sequence u.
Example
:
dbl_linear(10) should return 22
Note:
Focus attention on efficiency
Answer
from heapq import heappop, heappush def dbl_linear(n): u = [1] seen = {1} for _ in range(n): x = heappop(u) for y in [2 * x + 1, 3 * x + 1]: if y not in seen: seen.add(y) heappush(u, y) return heappop(u) # Example assertion to validate the function assert(dbl_linear(10) == 22)
A more complex task
Description
For a given chemical formula represented by a string, count the number of atoms of each element contained in the molecule and return an object.
For example:
water = 'H2O' parse_molecule(water) # return {H: 2, O: 1}
magnesium_hydroxide = 'Mg(OH)2' parse_molecule(magnesium_hydroxide) # return {Mg: 1, O: 2, H: 2}
var fremy_salt = 'K4[ON(SO3)2]2' parse_molecule(fremySalt) # return {K: 4, O: 14, N: 2, S: 4}
As you can see, some formulas have brackets in them. The index outside the brackets tells you that you have to multiply count of each atom inside the bracket on this index. For example, in Fe(NO3)2 you have one iron atom, two nitrogen atoms and six oxygen atoms.
Note that brackets may be round, square or curly and can also be nested. Index after the braces is optional.
Answer
import re from collections import defaultdict def parse_molecule(formula): def multiply_dict(d, n): return {k: v * n for k, v in d.items()} def parse_section(section): atoms = defaultdict(int) i = 0 while i < len(section): if section[i].isalpha(): j = i + 1 while j < len(section) and section[j].islower(): j += 1 element = section[i:j] k = j while k < len(section) and section[k].isdigit(): k += 1 count = int(section[j:k] or 1) atoms[element] += count i = k elif section[i] in '([{': stack = [] brackets = {'(': ')', '[': ']', '{': '}'} close_bracket = brackets[section[i]] for j in range(i, len(section)): if section[j] == section[i]: stack.append(section[i]) elif section[j] == close_bracket: stack.pop() if not stack: break content = parse_section(section[i + 1: j]) k = j + 1 while k < len(section) and section[k].isdigit(): k += 1 multiplier = int(section[j + 1: k] or 1) content = multiply_dict(content, multiplier) for key, val in content.items(): atoms[key] += val i = k return atoms return parse_section(formula) # Example assertions to validate the function if (parse_molecule('H2O') == {'H': 2, 'O': 1}): print("Water!") assert parse_molecule('Mg(OH)2') == {'Mg': 1, 'O': 2, 'H': 2} assert parse_molecule('K4[ON(SO3)2]2') == {'K': 4, 'O': 14, 'N': 2, 'S': 4}
Water!
Sum up
The assistant even wrote a set of assertions to prove the correctness of his code.
Awesome!
Another check of Assistant's dexterity
Task description
You have to create a function that takes a positive integer number and returns the next bigger number formed by the same digits:
next_bigger(12)==21 next_bigger(513)==531 next_bigger(2017)==2071
If no bigger number can be composed using those digits, return -1:
next_bigger(9)==-1 next_bigger(111)==-1 next_bigger(531)==-1
Answer
def next_bigger(n): digits = list(str(n)) # Find the rightmost digit that is smaller than the digit next to it for i in range(len(digits) - 2, -1, -1): if digits[i] < digits[i + 1]: break else: return -1 # Find the smallest digit on the right that is larger than digits[i] for j in range(len(digits) - 1, i, -1): if digits[j] > digits[i]: break # Swap the two digits digits[i], digits[j] = digits[j], digits[i] # Reverse the digits to the right of i digits = digits[:i + 1] + sorted(digits[i + 1:]) return int(''.join(digits)) # Example assertions to validate the function assert next_bigger(12) == 21 assert next_bigger(513) == 531 assert next_bigger(2017) == 2071 assert next_bigger(9) == -1 assert next_bigger(111) == -1 assert next_bigger(531) == -1 # my own test to be sure: if next_bigger(123456789) == 123456798: print("Whoa!")
Whoa!
Thread-specific question
To test the aptitude to make contextual (conversation Thread related) suggestions.
Task
Write the function parse_molecule
without using the standard library
modules re
and collections
.
Solution
def parse_molecule(formula): def multiply_dict(d, n): for k in d: d[k] *= n return d def parse_section(section): atoms = {} stack = [] multipliers = [1] n = len(section) i = 0 while i < n: if section[i].isalpha(): start = i i += 1 while i < n and section[i].islower(): i += 1 element = section[start:i] start = i while i < n and section[i].isdigit(): i += 1 count = int(section[start:i]) if start != i else 1 atoms[element] = atoms.get(element, 0) + count * multipliers[-1] elif section[i] in '([{': stack.append(atoms) atoms = {} multipliers.append(multipliers[-1]) i += 1 elif section[i] in ')]}': i += 1 start = i while i < n and section[i].isdigit(): i += 1 multiplier = int(section[start:i]) if start != i else 1 atoms = multiply_dict(atoms, multiplier) if stack: prev_atoms = stack.pop() for k, v in atoms.items(): prev_atoms[k] = prev_atoms.get(k, 0) + v atoms = prev_atoms multipliers.pop() else: i += 1 return atoms return parse_section(formula) # Example assertions to validate the function assert parse_molecule('H2O') == {'H': 2, 'O': 1} assert parse_molecule('Mg(OH)2') == {'Mg': 1, 'O': 2, 'H': 2} assert parse_molecule('K4[ON(SO3)2]2') == {'K': 4, 'O': 14, 'N': 2, 'S': 4} # my own check-up: if parse_molecule('H2O2') == {'H': 2, 'O': 2}: print("It is a hydrogen peroxide")
It is a hydrogen peroxide
The Snag
The balance between task intricacy and ambiguity of description.
Question
Optimize the function parse_molecule
:
import re from collections import defaultdict def parse_molecule(formula): def multiply_dict(d, n): return {k: v * n for k, v in d.items()} def parse_section(section): atoms = defaultdict(int) i = 0 while i < len(section): if section[i].isalpha(): j = i + 1 while j < len(section) and section[j].islower(): j += 1 element = section[i:j] k = j while k < len(section) and section[k].isdigit(): k += 1 count = int(section[j:k] or 1) atoms[element] += count i = k elif section[i] in '([{': stack = [] brackets = {'(': ')', '[': ']', '{': '}'} close_bracket = brackets[section[i]] for j in range(i, len(section)): if section[j] == section[i]: stack.append(section[i]) elif section[j] == close_bracket: stack.pop() if not stack: break content = parse_section(section[i + 1: j]) k = j + 1 while k < len(section) and section[k].isdigit(): k += 1 multiplier = int(section[j + 1: k] or 1) content = multiply_dict(content, multiplier) for key, val in content.items(): atoms[key] += val i = k return atoms return parse_section(formula)
it must successfully pass through this set of assertions:
assert parse_molecule('H2O') == {'H': 2, 'O': 1} assert parse_molecule('Mg(OH)2') == {'Mg': 1, 'O': 2, 'H': 2} assert parse_molecule('K4[ON(SO3)2]2') == {'K': 4, 'O': 14, 'N': 2, 'S': 4}
Answer
def parse_molecule(formula): def multiply_dict(d, n): for k in d: d[k] *= n return d def parse_section(section, multiplier=1): atoms = {} stack = [] multipliers = [multiplier] n = len(section) i = 0 while i < n: if section[i].isalpha(): start = i i += 1 while i < n and section[i].islower(): i += 1 element = section[start:i] start = i while i < n and section[i].isdigit(): i += 1 count = int(section[start:i]) if start != i else 1 atoms[element] = atoms.get(element, 0) + count * multipliers[-1] elif section[i] in '([{': stack.append((atoms, multipliers.pop())) atoms, multipliers[-1] = {}, 1 multipliers.append(multiplier) i += 1 elif section[i] in ')]}': i += 1 start = i while i < n and section[i].isdigit(): i += 1 sub_multiplier = int(section[start:i]) if start != i else 1 if stack: atoms = multiply_dict(atoms, sub_multiplier) prev_atoms, prev_multiplier = stack.pop() for k, v in atoms.items(): prev_atoms[k] = prev_atoms.get(k, 0) + v * prev_multiplier atoms = prev_atoms multipliers.pop() else: i += 1 return atoms return parse_section(formula) # Example assertions to validate the function assert parse_molecule('H2O') == {'H': 2, 'O': 1} assert parse_molecule('Mg(OH)2') == {'Mg': 1, 'O': 2, 'H': 2} assert parse_molecule('K4[ON(SO3)2]2') == {'K': 4, 'O': 14, 'N': 2, 'S': 4}
It is the limit!
Object-Oriented Programming
Double Linked List
Write the OOP implementation of double-linked list, and the test suite to check up its main features:
- add node;
- delete node;
- etc.
Implementation
Here's the implementation of a double-linked list in Python with a basic test suite to verify its main functionalities.
class Node: def __init__(self, value): self.value = value self.next = None self.prev = None class DoubleLinkedList: def __init__(self): self.head = None self.tail = None def add_node(self, value): new_node = Node(value) if not self.head: self.head = self.tail = new_node else: self.tail.next = new_node new_node.prev = self.tail self.tail = new_node def delete_node(self, value): current = self.head while current: if current.value == value: if current.prev: current.prev.next = current.next else: self.head = current.next if current.next: current.next.prev = current.prev else: self.tail = current.prev return True current = current.next return False def find_node(self, value): current = self.head while current: if current.value == value: return current current = current.next return None def to_list(self): result = [] current = self.head while current: result.append(current.value) current = current.next return result def test_double_linked_list(): dll = DoubleLinkedList() # Test add_node dll.add_node(1) dll.add_node(2) dll.add_node(3) assert dll.to_list() == [1, 2, 3], f"Expected: [1, 2, 3], Got: {dll.to_list()}" # Test find_node node = dll.find_node(2) assert node is not None and node.value == 2, f"Expected: 2, Got: {node.value if node else None}" node = dll.find_node(4) assert node is None, f"Expected: None, Got: {node.value if node else None}" # Test delete_node assert dll.delete_node(2) == True, "Node with value 2 should be found and deleted" assert dll.to_list() == [1, 3], f"Expected: [1, 3], Got: {dll.to_list()}" assert dll.delete_node(2) == False, "Node with value 2 should not be found" assert dll.delete_node(1) == True, "Node with value 1 should be found and deleted" assert dll.to_list() == [3], f"Expected: [3], Got: {dll.to_list()}" assert dll.delete_node(3) == True, "Node with value 3 should be found and deleted" assert dll.to_list() == [], f"Expected: [], Got: {dll.to_list()}" assert dll.delete_node(3) == False, "Node with value 3 should not be found" print("All tests are passed successfully")
Run tests
test_double_linked_list()
All tests are passed successfully
You can run the test_doubly_linked_list
function to verify that the
doubly-linked list implementation works as expected. This test suite
checks the main features such as adding nodes, deleting nodes, finding
nodes, and converting the list to a Python list.
Modification
Task
Modify class DoubleLinkedList
by converting method to_list
into
__repr__
and the test suite accordingly with this modification.
Resolution
Here's the modified implementation of the DoublyLinkedList
class,
where the to_list
method has been converted into the __repr__
method. The test suite has also been updated accordingly.
class Node: def __init__(self, value): self.value = value self.next = None self.prev = None class DoubleLinkedList: def __init__(self): self.head = None self.tail = None def add_node(self, value): new_node = Node(value) if not self.head: self.head = self.tail = new_node else: self.tail.next = new_node new_node.prev = self.tail self.tail = new_node def delete_node(self, value): current = self.head while current: if current.value == value: if current.prev: current.prev.next = current.next else: self.head = current.next if current.next: current.next.prev = current.prev else: self.tail = current.prev return True current = current.next return False def find_node(self, value): current = self.head while current: if current.value == value: return current current = current.next return None def __repr__(self): result = [] current = self.head while current: result.append(current.value) current = current.next return str(result) # Modified Test Suite for Double-Linked List def test_double_linked_list(): dll = DoubleLinkedList() # Test add_node dll.add_node(1) dll.add_node(2) dll.add_node(3) assert repr(dll) == str([1, 2, 3]), f"Expected: [1, 2, 3], Got: {repr(dll)}" # Test find_node node = dll.find_node(2) assert node is not None and node.value == 2, f"Expected: 2, Got: {node.value if node else None}" node = dll.find_node(4) assert node is None, f"Expected: None, Got: {node.value if node else None}" # Test delete_node assert dll.delete_node(2) == True, "Node with value 2 should be found and deleted" assert repr(dll) == str([1, 3]), f"Expected: [1, 3], Got: {repr(dll)}" assert dll.delete_node(2) == False, "Node with value 2 should not be found" assert dll.delete_node(1) == True, "Node with value 1 should be found and deleted" assert repr(dll) == str([3]), f"Expected: [3], Got: {repr(dll)}" assert dll.delete_node(3) == True, "Node with value 3 should be found and deleted" assert repr(dll) == str([]), f"Expected: [], Got: {repr(dll)}" assert dll.delete_node(3) == False, "Node with value 3 should not be found" # Run tests test_double_linked_list() print("All tests passed!")
All tests passed!
You can run the test_double_linked_list
function to verify that the
double-linked list implementation works as expected. This test suite
checks the main features such as adding nodes, deleting nodes, finding
nodes, and uses the __repr__
method to convert the list to a string
representation for comparison.
Extension
Task
The name of class is not DoublyLinkedList
but DoubleLinkedList
,
bear it in your mind, please.
Add the feature to prevent same nodes inclusion into the
DoubleLinkedList. Each node must have the unique value. The attempt to
insert the node with not unique value must throw the Exception
:
"This value is already in the list!"
Answer
Here's the updated implementation of the DoubleLinkedList
class with
the feature to ensure unique node values. If an attempt is made to
insert a node with a non-unique value, an Exception
is raised with
the message "This value is already in the list!".
class Node: def __init__(self, value): self.value = value self.next = None self.prev = None class DoubleLinkedList: def __init__(self): self.head = None self.tail = None self.values = set() def add_node(self, value): if value in self.values: raise Exception("This value is already in the list!") new_node = Node(value) if not self.head: self.head = self.tail = new_node else: self.tail.next = new_node new_node.prev = self.tail self.tail = new_node self.values.add(value) def delete_node(self, value): current = self.head while current: if current.value == value: self.values.remove(value) if current.prev: current.prev.next = current.next else: self.head = current.next if current.next: current.next.prev = current.prev else: self.tail = current.prev return True current = current.next return False def find_node(self, value): current = self.head while current: if current.value == value: return current current = current.next return None def __repr__(self): result = [] current = self.head while current: result.append(current.value) current = current.next return str(result) # >> Updated Test Suite for Double-Linked List def test_double_linked_list(): dll = DoubleLinkedList() # Test add_node dll.add_node(1) dll.add_node(2) dll.add_node(3) assert repr(dll) == str([1, 2, 3]), f"Expected: [1, 2, 3], Got: {repr(dll)}" # Test add_node with duplicate value try: dll.add_node(2) assert False, "Expected an exception for duplicate value" except Exception as e: assert str(e) == "This value is already in the list!", f"Expected: 'This value is already in the list!', Got: {str(e)}" # Test find_node node = dll.find_node(2) assert node is not None and node.value == 2, f"Expected: 2, Got: {node.value if node else None}" node = dll.find_node(4) assert node is None, f"Expected: None, Got: {node.value if node else None}" # Test delete_node assert dll.delete_node(2) == True, "Node with value 2 should be found and deleted" assert repr(dll) == str([1, 3]), f"Expected: [1, 3], Got: {repr(dll)}" assert dll.delete_node(2) == False, "Node with value 2 should not be found" assert dll.delete_node(1) == True, "Node with value 1 should be found and deleted" assert repr(dll) == str([3]), f"Expected: [3], Got: {repr(dll)}" assert dll.delete_node(3) == True, "Node with value 3 should be found and deleted" assert repr(dll) == str([]), f"Expected: [], Got: {repr(dll)}" assert dll.delete_node(3) == False, "Node with value 3 should not be found" # Run tests test_double_linked_list() print("All tests passed!")
All tests passed!
You can run the test_double_linked_list
function to verify that the
DoubleLinkedList
class works as expected. The test suite checks that
duplicate values are not allowed and that the main functionalities
such as adding nodes, deleting nodes, and finding nodes work
correctly.
Customization
Question
Rewrite test_double_linked_list
in unittest
style, please.
Answer
Here's the modified test suite for the DoubleLinkedList
class using
the unittest
framework.
import unittest class TestDoubleLinkedList(unittest.TestCase): def setUp(self): self.dll = DoubleLinkedList() def test_add_node(self): self.dll.add_node(1) self.dll.add_node(2) self.dll.add_node(3) self.assertEqual(repr(self.dll), str([1, 2, 3])) def test_add_duplicate_node(self): self.dll.add_node(1) self.dll.add_node(2) with self.assertRaises(Exception) as context: self.dll.add_node(2) self.assertEqual(str(context.exception), "This value is already in the list!") def test_find_node(self): self.dll.add_node(1) self.dll.add_node(2) self.dll.add_node(3) node = self.dll.find_node(2) self.assertIsNotNone(node) self.assertEqual(node.value, 2) node = self.dll.find_node(4) self.assertIsNone(node) def test_delete_node(self): self.dll.add_node(1) self.dll.add_node(2) self.dll.add_node(3) self.assertTrue(self.dll.delete_node(2)) self.assertEqual(repr(self.dll), str([1, 3])) self.assertFalse(self.dll.delete_node(2)) self.assertTrue(self.dll.delete_node(1)) self.assertEqual(repr(self.dll), str([3])) self.assertTrue(self.dll.delete_node(3)) self.assertEqual(repr(self.dll), str([])) self.assertFalse(self.dll.delete_node(3)) unittest.main(exit=False) # Emacs' Babel unittest run approach
.... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OK
This unittest
test suite includes the following tests:
- test_add_node: Tests adding nodes to the list.
- test_add_duplicate_node: Tests that adding a duplicate node raises an exception.
- test_find_node: Tests finding existing and non-existing nodes in the list.
- test_delete_node: Tests deleting nodes from the list.
To run the tests, save the code in a file and execute the file. The
unittest
framework will discover and run all the test methods.
Clarification
Question
Rewrite class TestDoubleLinkedList
:
The chunks like
self.dll.add_node(1) self.dll.add_node(2) self.dll.add_node(3)
are redundant and unnecessary repetition. Put them into setUp
method.
Answer
Sure, here's the updated test suite for the DoubleLinkedList
, where
the repetitive setup code has been moved into the setUp
method.
import unittest class DoubleLinkedList: def __init__(self): self.head = None self.tail = None self.values = set() def add_node(self, value): if value in self.values: raise Exception("This value is already in the list!") new_node = Node(value) if not self.head: self.head = self.tail = new_node else: self.tail.next = new_node new_node.prev = self.tail self.tail = new_node self.values.add(value) def delete_node(self, value): current = self.head while current: if current.value == value: self.values.remove(value) if current.prev: current.prev.next = current.next else: self.head = current.next if current.next: current.next.prev = current.prev else: self.tail = current.prev return True current = current.next return False def find_node(self, value): current = self.head while current: if current.value == value: return current current = current.next return None def __repr__(self): result = [] current = self.head while current: result.append(current.value) current = current.next return str(result) class Node: def __init__(self, value): self.value = value self.next = None self.prev = None class TestDoubleLinkedList(unittest.TestCase): def setUp(self): self.dll = DoubleLinkedList() self.dll.add_node(1) self.dll.add_node(2) self.dll.add_node(3) def test_add_node(self): self.assertEqual(repr(self.dll), str([1, 2, 3])) def test_add_duplicate_node(self): with self.assertRaises(Exception) as context: self.dll.add_node(2) self.assertEqual(str(context.exception), "This value is already in the list!") def test_find_node(self): node = self.dll.find_node(2) self.assertIsNotNone(node) self.assertEqual(node.value, 2) node = self.dll.find_node(4) self.assertIsNone(node) def test_delete_node(self): self.assertTrue(self.dll.delete_node(2)) self.assertEqual(repr(self.dll), str([1, 3])) self.assertFalse(self.dll.delete_node(2)) self.assertTrue(self.dll.delete_node(1)) self.assertEqual(repr(self.dll), str([3])) self.assertTrue(self.dll.delete_node(3)) self.assertEqual(repr(self.dll), str([])) self.assertFalse(self.dll.delete_node(3)) unittest.main(exit=False)
.... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OK
- The
setUp
method now initializes the list and adds the nodes1
,2
, and3
for each test method. - Each test now assumes the list has these three elements from the start, reducing redundancy and unnecessary repetition.
- The
DoubleLinkedList
andNode
classes are now included in the same file for completeness.
Corollary
Question
What is your opinion about writing Python code using the OpenAI Assistant equipped with the code interpreter tool? I see it as a killer feature, beyond a doubt. What are your thoughts on including test suites for the generated code? Is it necessary to provide the test suites beforehand to simplify and clarify the task of Python code generation?
Answer
Using an AI assistant equipped with a code interpreter tool is indeed a powerful feature that can greatly enhance productivity and code quality by providing quick feedback and validation of the generated code. Including test suites for the generated code is not only beneficial but practically necessary for ensuring the correctness and robustness of the implementations. Providing the test suites beforehand can indeed simplify and clarify the task of Python code generation, as it sets clear expectations and constraints, enabling more focused and accurate responses.
An Acid Note
Despite the powerful OpenAI
algorithms, the cornerstone of success
in augmented Python development is prompt clarity. A vague,
uncertain, and convoluted task description results in incorrect
answers.
The human factors remain the weakest link in the code development chain.