One problem five solutions — Zip Function
Hi everyone, it’s been so long since I wrote a technical article, and today's one will be focused on the one problem five solutions series where we will be implementing the zip function.
The zip function is a function that wraps two lists and returns a third list of tuples of two elements, and the zip function is well-known by Haskell programmers due to being a function present in the Haskell prelude.
And in practice, a zip function looks like this in Haskell:
zip [1, 2, 3] [4, 5, 6]
-- outputs [(1,4),(2,5),(3,6)]zip [1, 2, 3] [4, 5]
-- outputs [(1,4),(2,5)]zip [1, 2] [4, 5, 6]
-- outputs [(1,4),(2,5)]
I was recently coding in a language that didn't support a zip function by default, so I had to implement it myself, which generated the topic of today's article
With the proper introduction given, let's implement our solutions
Haskell
As I mentioned Haskell has a zip function, but I believe that it will be very didactic for this article if we start by implementing a zip function from scratch in Haskell, just for learning purposes
myzip [] _ = []
myzip _ [] = []
myzip (x:xs) (y:ys) = (x, y):myzip xs ys
In just three lines of code, using pattern matching we implement our zip function, which basically consists of checking:
- if the first list is empty we can’t pair it in a tuple, so we return an empty list
- if the second list is empty, we still can't have a pair in a tuple and return the empty list
- if both lists still have elements we get the first element x of the first list and the first element y of the second list, add them to a tuple and append them to the array with the ‘:’ symbol and call recursively the ‘myzip’ function with the rest of both lists
Elixir
Keep going with the functional languages, the second solution, will be the Elixir one:
defmodule Zip do
def zip(_, []), do: []
def zip([], _), do: []
def zip([x | xs], [y | ys]), do: [{x, y} | zip(xs, ys)]
end
The solution is very related to the Haskell solution, with the difference of using the ‘|’ symbol to connect a head element to a list that will be built recursively
JavaScript
Now it's the turn of the building block of the web, let's have a look at the zip JavaScript function:
const zip = (ls1, ls2) => {
const zips = []
for (let i = 0; i < ls1.length; i++) {
if (!ls2[i])
break
zips.push([ls1[i], ls2[i]])
}
return zips
}
JavaScript doesn't support recursion very well and has tail call issues, so the more common approach to a zip function is by using a loop that goes through the first list and in the body of the loop checks if the element is present at index ‘i’ in the ‘ls2’ array, if yes push another pair of elements to the zips array, if not break the loop and return the zips
Also, notice that JavaScript doesn't have a tuple datatype, and because of that, an array was chosen instead.
Python
Similar to JavaScript, python is an interpreted language, but the solution is slightly different:
def zip(ls1, ls2):
zips = []
max_index = min(len(ls1), len(ls2))
for i in range(0, max_index):
zips.append((ls1[i], ls2[i])) return zips
In the python solution, I append the tuple to a zips array, but for the solution to work I had to get the length of the shorter array, create a range starting at zero that ends in the max_index-1, and after running the loop I just return the tuple pairs
Vlang
The last solution is the Vlang solution, V is a typed language with a taste of GO and immutability, and for that, the solution has its own V way of implementing:
import math
pub fn zip<T>(ls1 []T, ls2 []T) [][]T {
total := math.min(ls1.len, ls2.len)
return [][]T{len: total, init: [ls1[it], ls2[it]]}
}
First, we need to import the math module to be able to use the min function to get the total number of pairs we will get in the array
In the function and parameters declaration, we had to use the ‘T’ to inform that the function will be generic for multiple types
After getting the total elements we return an array of ‘[]T’ populated with pairs of ‘[ ls1[it], ls2[it]]’, also notice that ‘it’ works like an index of the array positions that start in zero and ends in total-1.
Editor’s solution improvement
After reviewing this solution on 21th April 2023, a new solution to work with multiple types was elaborated in Vlang, turning it more similar to the Haskell solution, by building the tuple type:
struct Tuple<T, V> {
x T
y V
}
fn zip<T, V>(ls1 []T, ls2 []V) []Tuple<T, V> {
total := math.min(ls1.len, ls2.len)
return []Tuple<T, V>{len: total, init: Tuple<T, V>{x: ls1[it], y: ls2[it]}}
}
By creating a tuple struct, we can define x and y elements with different type variables and have a zip function able to work with different types in lists like:
zip([1, 2, 3], [true, false, true])
generates an output like:
[Tuple[int, bool]{
x: 1
y: true
}
And to access the data:
zip([1, 2, 3], [true, false, true])[0].x //1
zip([1, 2, 3], [true, false, true])[1].y // false
And that's our solutions showcase, I really liked implementing the zip function problem in multiple languages, because each solution in this article was very unique, and it was so fun to discover each language's way to implement the zip function.
Thanks for reading until here, and if there's a problem you would like me to implement in this series, feel comfortable to let me a comment below.