Reading time: 30 minutes | Coding time: 10 minutes
Gap buffer is a data structure used to edit array of text in an efficient manner which is currently being edited. Correct example to understand the concept of Gap Buffer would be text editors. In text editors, you can insert the text instantly at the position of your cursor. This process uses the concept of gap buffers. Lets look at it with an example.
Operations like insertion and deletion takes place in constant time and hence, performs well and can be direct alternative to Rope Data Structure.
Understanding Gap Buffer
Consider the empty buffer below with size of
Note, '_' (underscore) means buffer gap element
Now we insert "open" in this buffer at 0, our array would now look like this,
inserting "-" at 4, and our array would reflect as,
And finally inserting "genus" at 5. Our array would look like this,
Lets insert "welcome-" at 5, but for that we don't have any space left on the buffer. So we wont be able to add anything without deleting the existing array as it is contrained. With using Gap Buffers, we can produce any number of blank spaces starting from given index and then we can add our "welcome-" string.
Add welcome- at 5 operation would be executed as below,
- go to 5
gap on the right>
length of the string
- If true add the string
- else grow the array and add string to it.
Note: This is very high level idea.
Now lets delete 5th element, the operation would look like
- go to 5
- replace the element with the gap.
Basic Operations in Gap Buffer
There are number of coined operations in gap buffer and also till now you might have figured out some already.
As the name suggest, this operation will insert the element at the specified location of the buffer. Buffer contain the information of the gap, such as information about the starting point and ending point of the gap. Now when we call insert and pass the position to which we want to insert, this gap will move in such a way that it will position left gap point at the "to be inserted" position. Now the gap will be filled it will reduced in size.
Lets see the algorithmic steps
insert(string str, position p) if left_gap != p then move_gap_to(p) for char in string buffer[left_gap] = char left_gap++
The increase in left gap value will decrease the length of gap as right gap is not being change.
This operation will grow the gap from the specified location. How much should be grow? I would say it depends on the programmer. Some prefer fixed length and some on the input string length. For the sake of this article, we would stick to the fixed length.
Lets say we fixed the growth size to
10. During insert operation, our gap reduces every time. We should call
grow() operation when left gap index is equal to the right gap index.
grow() is called the index which succeeds the gap index are to be shift to +10 indexes as we have fixed the growth size. And we will create gap in next 10 indexes.
Lets see the algorithmic steps
grow(position p) buffer = buffer[:p]+ ['_']*10 + buffer[p:] left_gap = p right_gap = p+10
The first line adds gap starting to buffer from
This operation will move the gap left from the current position. This operation will transfer left element from gap to right element.
move_left(pos): while(left_gap != pos): left_gap = left_gap - 1 right_gap = right_gap - 1 main_array[right_gap+1] = main_array[left_gap] main_array[left_gap] = '_'
This operation will move the gap right from the current position. This operation will transfer right element from the gap to left element.
move_right(pos): while(left_gap != pos): left_gap = left_gap + 1 right_gap = right_gap + 1 main_array[left_gap-1] = main_array[right_gap] main_array[right_gap] = '_'
This operation will delete the specified element in the buffer. As insert reduced gap by increasing only left gap, delete operation reduced gap by reducing right gap only.
delete( pos): if left_gap != pos: move_gap(pos) right_gap = right_gap + 1 main_array[right_gap] = '_'
class GapBuffer: def __init__(self): self.main_array = ['_']*50 self.buffer_length = 10 self.left_gap = 0 self.right_gap = 9 def print_buffer(self): for x in range(self.buffer_length): print(self.main_array[x], end="") print("\n") # print(self.main_array) return def grow(self, pos): temp_main_array = self.main_array[:self.buffer_length] for x in range(pos): self.main_array[x] = temp_main_array[x] for x in range(10): self.main_array[pos+x] = '_' for x in range(pos, len(temp_main_array)): self.main_array[x+10] = temp_main_array[x] self.right_gap = self.right_gap + 10 self.buffer_length = self.buffer_length + 10 return def move_left(self, pos): while(self.left_gap != pos): self.left_gap = self.left_gap - 1 self.right_gap = self.right_gap - 1 self.main_array[self.right_gap+1] = self.main_array[self.left_gap] self.main_array[self.left_gap] = '_' return def move_right(self, pos): while(self.left_gap != pos): self.left_gap = self.left_gap + 1 self.right_gap = self.right_gap + 1 self.main_array[self.left_gap-1] = self.main_array[self.right_gap] self.main_array[self.right_gap] = '_' return def move_gap(self, pos): if pos < self.left_gap: self.move_left(pos) else: self.move_right(pos) return def insert(self, in_string, pos): if self.left_gap != pos: self.move_gap(pos) for x in range(len(in_string)): if (self.left_gap == self.right_gap): self.grow(pos+x) self.main_array[pos+x] = in_string[x] self.left_gap = self.left_gap + 1 def delete(self, pos): if self.left_gap != pos: self.move_gap(pos) self.right_gap = self.right_gap + 1 self.main_array[self.right_gap] = '_' return if __name__ == '__main__': gp = GapBuffer() gp.print_buffer() gp.insert("open-genus", 0) gp.print_buffer() gp.insert("welcome-", 5) gp.print_buffer() gp.delete(5) gp.print_buffer()
The insert or insertion operation takes
O(1) time but growing operation takes
O(n) cause we need to shift the values as the buffer size increases. But comparing to other options, it is quit faster than Ropes.
Due to this reason, this data type is the first choice for text editors.
Following table summarizes the time complexity: