Sometimes, it is common to see that we want to process a list of items into a human readable string. Usually, across different human languages, we join each elements in the list with a comma and a space ",
". There are various algorithms available to do such joining. At here, we're documenting the one that requires least codes and easy to do.
This employs forward thinking by checking the first item check in the list.
If the item is first element in the list, we skip the comma-space appending process and jump straight to registering the item into the string. Otherwise, the comma-space is appended before the item registrations.
The algorithm ends when there is no item left from the list. It looks something as such in Go:
func Join(list [] string) (s string) {
for i, element := range list {
if i != 0 {
s = s + ", " + element
continue
}
s = element
}
return s
}
Let's proceed to join the list of phonetic codes using Go language. The array is as follows:
var (
list = []string{"alpha",
"bravo",
"charlie",
"delta",
"echo",
"foxtrot",
"golf",
"hotel",
"india",
"juliet",
"kilo",
"lima",
"mike",
"november",
"oscar",
"papa",
"quebec",
"romeo",
"sierra",
"tango",
"uniform",
"victor",
"whiskey",
"xray",
"yankee",
"zulu",
}
)
This will yield a result like:
alpha, bravo, charlie, delta, echo, foxtrot, golf, hotel, india, juliet, kilo, lima, mike, november, oscar, papa, quebec, romeo, sierra, tango, uniform, victor, whiskey, xray, yankee, zulu
Assuming the index of the iteration is available (means at a given round, we get to know which number of cycle), a simple crude approach is:
func generalFunction() string {
s := ""
for i, element := range list {
if i > 0 {
s = fmt.Sprintf("%s, ", s)
}
s = fmt.Sprintf("%s%s", s, element)
}
return s
}
By benchmark results, this gives:
BenchmarkGeneralFunction-8 200000 8267 ns/op
BenchmarkGeneralFunction-8 200000 8294 ns/op
BenchmarkGeneralFunction-8 200000 8326 ns/op
BenchmarkGeneralFunction-8 200000 8218 ns/op
BenchmarkGeneralFunction-8 200000 8380 ns/op
Now assuming the index is not available, we got a few option. In this case, to find out the first element, we check the result for its emptiness. This approach yields:
func noIndexFunctionResultant() string {
s := ""
for _, element := range list {
if s != "" {
s = fmt.Sprintf("%s, ", s)
}
s = fmt.Sprintf("%s%s", s, element)
}
return s
}
By benchmark results, this gives:
BenchmarkNoIndexFunctionResultant-8 200000 8322 ns/op
BenchmarkNoIndexFunctionResultant-8 200000 8315 ns/op
BenchmarkNoIndexFunctionResultant-8 200000 8283 ns/op
BenchmarkNoIndexFunctionResultant-8 200000 8270 ns/op
BenchmarkNoIndexFunctionResultant-8 200000 8323 ns/op
In the same scenario where index is not available, we use a flag instead. This yields:
func noIndexFunctionFlag() string {
s := ""
i := false
for _, element := range list {
if i {
s = fmt.Sprintf("%s, ", s)
}
s = fmt.Sprintf("%s%s", s, element)
}
return s
}
By benchmark results, this gives:
BenchmarkNoIndexFunctionFlag-8 200000 8268 ns/op
BenchmarkNoIndexFunctionFlag-8 200000 8306 ns/op
BenchmarkNoIndexFunctionFlag-8 200000 8384 ns/op
BenchmarkNoIndexFunctionFlag-8 200000 8252 ns/op
BenchmarkNoIndexFunctionFlag-8 200000 8365 ns/op
Now that we can see the differences between different style of implementation, let's optimize the algorithm to the language nature. For no index scenario and using flag, this yield:
func optimizedIndexFunction(list [] string) (s string) {
n := false
for _, element := range list {
if i {
s = s + ", " + element
continue
}
s = element
n = true
}
return s
}
By benchmark results, this gives:
BenchmarkOptimizedNoIndexFunction-8 625825 1741 ns/op
BenchmarkOptimizedNoIndexFunction-8 583856 1759 ns/op
BenchmarkOptimizedNoIndexFunction-8 688288 1734 ns/op
BenchmarkOptimizedNoIndexFunction-8 593253 1752 ns/op
BenchmarkOptimizedNoIndexFunction-8 587133 1768 ns/op
As we can see, there is a tremendous performance increases after the optimization.
Now let's optimize the no-index but checking the result emptiness technique, this yields:
func optimizedIndexFunction(list [] string) (s string) {
for _, element := range list {
if s != "" {
s = s + ", " + element
continue
}
s = element
}
return s
}
By benchmark results, this gives:
BenchmarkOptimizedNoIndexFunctionResultant-8 594296 1742 ns/op
BenchmarkOptimizedNoIndexFunctionResultant-8 601329 1741 ns/op
BenchmarkOptimizedNoIndexFunctionResultant-8 687526 1742 ns/op
BenchmarkOptimizedNoIndexFunctionResultant-8 612574 1777 ns/op
BenchmarkOptimizedNoIndexFunctionResultant-8 602308 1733 ns/op
Now let's optimize the index available example, this yields:
func optimizedIndexFunction(list [] string) (s string) {
for i, element := range list {
if i != 0 {
s = s + ", " + element
continue
}
s = element
}
return s
}
By benchmark results, this gives:
BenchmarkOptimizedIndexFunction-8 606129 1737 ns/op
BenchmarkOptimizedIndexFunction-8 607173 1729 ns/op
BenchmarkOptimizedIndexFunction-8 590078 1729 ns/op
BenchmarkOptimizedIndexFunction-8 595992 1756 ns/op
BenchmarkOptimizedIndexFunction-8 594819 1747 ns/op
As we can see, there is a tremendous performance increases after applying language specific optimization.
Based on the benchmark results, there isn't much difference between using flag, index and checking result emptiness to identify the first element after language optimizations. However, from the lines of codes perspectives, the general example is much easy to comprehend and less explicit variables to deal with.
Therefore, we can draw a conclusion that first element checking algorithm provides: