Exploring Human vs AI in Algorithmic Programming: A Case Study
Written on
Chapter 1: Introduction
Is ChatGPT a companion or a competitor? This section delves into a coding experiment where I attempt to develop a simple algorithm to generate random numbers that sum to 10, and compare my approach with that of ChatGPT.
Chapter 2: The Coding Challenge
The Software Landscape
I aimed to create a function that outputs a series of random numbers which, when added together, equal 10. The potential outputs could be:
- [5, 5]
- [4, 5, 1]
- [1, 2, 3, 4]
- [1, 2, 1, 1, 3, 2]
I decided to code this manually first and then see how ChatGPT would tackle the same problem. Would my coding skills yield a better algorithm, or would ChatGPT outperform me? This inquiry raises questions about the reliability of AI in programming tasks.
Before proceeding, ensure that you refer to the interface outlined below, allowing for easier comparison of the algorithms.
The Interface
To facilitate a fair comparison, I utilized the following interface:
fun generateRandomSequence(
sumTarget: Int,
minCount: Int = 1,
maxCount: Int = sumTarget
): List<Int>
Here, sumTarget represents the total sum that the generated sequence should equal, minCount is the minimum number of elements in the sequence, and maxCount defines the upper limit of elements.
The Testing Phase
To verify that both my code and ChatGPT's output adhered to the specified criteria, I created a test that iterates through all possible combinations of minCount and maxCount. This involved 30 iterations for each combination to ensure thorough testing and debugging.
@org.junit.Test
fun generateRandomSequence() {
val numbersGenerator = NumbersGenerator()
val target = 10
val sequences = mutableListOf<List<Int>>()
(1..target).forEach { minCount ->
(minCount..target).forEach { maxCount ->
repeat(30) {
print("min = $minCount, max = $maxCount ~~~ ")
val sequence = numbersGenerator.generateRandomSequence(target, minCount, maxCount)
println(sequence)
assert(sequence.sum() == target) { println("Sum should equal target") }
assert(sequence.size >= minCount) { println("Size should be equal or larger than minCount") }
assert(sequence.size <= maxCount) { println("Size should be equal or smaller than maxCount") }
sequences.add(sequence)
}
}
}
println("Summary")
val summary = sequences.groupBy { it.size }
summary.forEach {
println("${it.key} ${it.value.size}")}
println("Detail")
sequences.sortedBy { it.size }.asReversed().forEach {
println("${it.size} : $it")}
}
At this point, feel free to attempt coding the algorithm based on the provided details. Once done, compare your solution with both mine and ChatGPT's. If you discover a more efficient approach, please share it—I am always eager to learn.
My Approach
My initial thought process was to generate each number in sequence. Assuming the sumTarget is 10, here’s how my algorithm functions:
- Start with a random integer between 1 and 10. For instance, I might generate a 4.
- The next number will be a random integer between 1 and (10 - 4) = 6. Let’s say I generate a 1.
- The next number will be generated between 1 and (6 - 1) = 5. If I generate a 5, the sequence [4, 1, 5] sums to 10.
fun generateRandomSequence(
sumTarget: Int,
minCount: Int = 1,
maxCount: Int = sumTarget
): List<Int> {
val random = Random
val sequence = mutableListOf<Int>()
var currentSum = 0
while (currentSum < sumTarget) {
val randomTop = sumTarget - currentSum
val randomNumber = random.nextInt(1, randomTop + 1)
sequence.add(randomNumber)
currentSum += randomNumber
}
return sequence
}
Handling Maximum Count
The above method works if the random sequence can always be between 1 and the sumTarget. However, if the maxCount is set to less than the sumTarget, for instance, a maximum of 2 with a target of 10, the algorithm may fall short.
To address this, I modified the logic to terminate the generation process if the maximum count is reached and the sum target has not been satisfied.
fun generateRandomSequence(
sumTarget: Int,
minCount: Int = 1,
maxCount: Int = sumTarget
): List<Int> {
val random = Random
val sequence = mutableListOf<Int>()
var currentSum = 0
while (currentSum < sumTarget) {
if (sequence.size == maxCount - 1) {
val lastElement = sumTarget - currentSum
sequence.add(lastElement)
break
}
val randomTop = sumTarget - currentSum
val randomNumber = random.nextInt(1, randomTop + 1)
sequence.add(randomNumber)
currentSum += randomNumber
}
return sequence
}
Handling Minimum Count
When considering a minimum count greater than 1, the initial random range must be adjusted. For example, if the minimum count is 2 and the target is 10, starting with a random range of 1 to 10 could lead to generating 10, making it impossible to add more numbers.
To rectify this, I set the initial range to accommodate the minimum count. The adjusted logic is as follows:
fun generateRandomSequence(
sumTarget: Int,
minCount: Int = 1,
maxCount: Int = sumTarget
): List<Int> {
val random = Random
val sequence = mutableListOf<Int>()
var currentSum = 0
var minRemaining = minCount
while (currentSum < sumTarget) {
if (sequence.size == maxCount - 1) {
val lastElement = sumTarget - currentSum
sequence.add(lastElement)
break
}
val randomTop = sumTarget - currentSum - (minRemaining - 1)
val randomNumber = random.nextInt(1, randomTop + 1)
sequence.add(randomNumber)
currentSum += randomNumber
if (minRemaining > 1) minRemaining--
}
return sequence
}
Additional Considerations
The aforementioned algorithm functions as intended; however, the randomness of the generated numbers decreases with the number of elements. To enhance the randomness, I opted to shuffle the final output:
fun generateRandomSequence(
sumTarget: Int,
minCount: Int = 1,
maxCount: Int = sumTarget
): List<Int> {
require(maxCount <= sumTarget)
require(minCount <= maxCount)
require(minCount >= 1)
val random = Random
val sequence = mutableListOf<Int>()
var currentSum = 0
var minRemaining = minCount
while (currentSum < sumTarget) {
if (sequence.size == maxCount - 1) {
val lastElement = sumTarget - currentSum
sequence.add(lastElement)
break
}
val randomTop = sumTarget - currentSum - (minRemaining - 1)
require((randomTop >= 1) && (randomTop <= sumTarget))
val randomNumber = random.nextInt(1, randomTop + 1)
sequence.add(randomNumber)
currentSum += randomNumber
if (minRemaining > 1) minRemaining--
}
return sequence.shuffled()
}
Now that I’ve outlined my approach, let’s see how ChatGPT handles this problem.
ChatGPT's Algorithm
ChatGPT, known for its robust capabilities, generates code in response to specific instructions. I first asked it to create a Kotlin function to achieve the same goal. Its initial attempt produced a functional but flawed output.
First Attempt:
fun generateRandomSequence(
sumTarget: Int,
minCount: Int,
maxCount: Int
): List<Int> {
require(minCount <= maxCount) { "minCount must be less than or equal to maxCount" }
val count = if (minCount == maxCount) minCount else Random.nextInt(minCount, maxCount + 1)
val sequence = mutableListOf<Int>()
repeat(count - 1) {
val randomNumber = Random.nextInt(1, sumTarget - sequence.sum())
sequence.add(randomNumber)
}
sequence.add(sumTarget - sequence.sum())
return sequence
}
While this code functioned, it contained a significant flaw that could lead to runtime exceptions.
Second Attempt:
I alerted ChatGPT to the issue, and it generated the following:
fun generateRandomSequence(
sumTarget: Int,
minCount: Int,
maxCount: Int
): List<Int> {
require(minCount <= maxCount) { "minCount must be less than or equal to maxCount" }
val count = if (minCount == maxCount) minCount else Random.nextInt(minCount, maxCount + 1)
val sequence = mutableListOf<Int>()
repeat(count - 1) {
val remainingSum = sumTarget - sequence.sum()
val randomNumber = if (remainingSum > 0) Random.nextInt(1, remainingSum) else 0
sequence.add(randomNumber)
}
sequence.add(sumTarget - sequence.sum())
return sequence
}
Unfortunately, this code still had the potential to generate 0 or negative numbers.
Final Attempt:
After several iterations, ChatGPT refined the code to meet the original requirements:
fun generateRandomSequence(
sumTarget: Int,
minCount: Int,
maxCount: Int
): List<Int> {
require(minCount <= maxCount) { "minCount must be less than or equal to maxCount" }
val count = if (minCount == maxCount) minCount else Random.nextInt(minCount, maxCount + 1)
val sequence = mutableListOf<Int>()
repeat(count - 1) {
val remainingSum = sumTarget - sequence.sum()
val randomNumber = if (remainingSum > 1) Random.nextInt(1, remainingSum) else 1
sequence.add(randomNumber)
}
val lastNumber = sumTarget - sequence.sum()
sequence.add(if (lastNumber > 0) lastNumber else 1)
return sequence
}
This final version met the constraints I had set.
Comparison of Results
Both algorithms successfully met the requirements, but they differed in several aspects:
- Ease of Understanding: ChatGPT's approach was more intuitive and easier to understand.
- Distribution of Element Count: My algorithm tended to yield more elements, whereas ChatGPT's output was more evenly spread.
- Randomness of Generated Numbers: My method decreased randomness with the number of elements, while ChatGPT maintained a consistent distribution.
- Performance: Both algorithms performed comparably under test conditions.
Overall, while I initially believed my solution was superior, I gained valuable insights from ChatGPT's approach. It not only provided a different algorithmic perspective but also highlighted areas where my initial requirements could have been clearer.
In conclusion, this experience affirmed that ChatGPT is more of an ally than an adversary in the realm of algorithmic programming.