Александър обнови решението на 07.11.2018 12:46 (преди 9 месеца)
+package main
+
+type Editor interface {
+ // Insert text starting from given position.
+ Insert(position uint, text string) Editor
+
+ // Delete length items from offset.
+ Delete(offset, length uint) Editor
+
+ // Undo reverts latest change.
+ Undo() Editor
+
+ // Redo re-applies latest undone change.
+ Redo() Editor
+
+ // String returns complete representation of what a file looks
+ // like after all manipulations.
+ String() string
+}
+
+type piece struct {
+ origin bool
+ offset uint
+ length uint
+}
+
+type pieceTable []piece
+
+type editorState struct {
+ pieces *pieceTable
+ prev *editorState
+ next *editorState
+}
+
+// ConcreteEditor is an implementation of the Editor interface
+type ConcreteEditor struct {
+ originBuffer string
+ addBuffer string
+ state *editorState
+}
+
+func (ce *ConcreteEditor) Insert(position uint, text string) Editor {
+ var currentPosition, i uint
+
+ for i < (uint)(len(*ce.state.pieces)) {
+ if currentPosition == position {
+ ce.commitTable(ce.insertBeforePiece(i, text))
+
+ return ce
+ } else if currentPosition < position && position < (currentPosition+(*ce.state.pieces)[i].length) {
+ ce.commitTable(ce.insertWithinPiece(i, position-currentPosition, text))
+
+ return ce
+ }
+
+ currentPosition += (*ce.state.pieces)[i].length
+ i++
+ }
+
+ ce.commitTable(ce.insertAtTheEnd(text))
+
+ return ce
+}
+
+func (ce *ConcreteEditor) insertWithinPiece(pieceNum uint, pieceOffset uint, text string) *pieceTable {
+ newTable := make(pieceTable, 0, len(*ce.state.pieces)+2)
+ toSplit := (*ce.state.pieces)[pieceNum]
+
+ leftP := piece{origin: toSplit.origin, offset: toSplit.offset, length: pieceOffset}
+ midP := piece{origin: false, offset: (uint)(len(ce.addBuffer)), length: (uint)(len(text))}
+ rightP := piece{origin: toSplit.origin, offset: toSplit.offset + pieceOffset, length: toSplit.length - pieceOffset}
+
+ newTable = append(newTable, (*ce.state.pieces)[:pieceNum]...)
+ newTable = append(newTable, leftP, midP, rightP)
+ newTable = append(newTable, (*ce.state.pieces)[pieceNum+1:]...)
+
+ ce.addBuffer += text
+
+ return &newTable
+}
+
+func (ce *ConcreteEditor) insertBeforePiece(pieceNum uint, text string) *pieceTable {
+ newTable := make(pieceTable, 0, len(*ce.state.pieces)+1)
+
+ midP := piece{origin: false, offset: (uint)(len(ce.addBuffer)), length: (uint)(len(text))}
+
+ newTable = append(newTable, (*ce.state.pieces)[:pieceNum]...)
+ newTable = append(newTable, midP)
+ newTable = append(newTable, (*ce.state.pieces)[pieceNum:]...)
+
+ ce.addBuffer += text
+
+ return &newTable
+}
+
+func (ce *ConcreteEditor) insertAtTheEnd(text string) *pieceTable {
+ newTable := make(pieceTable, 0, len(*ce.state.pieces)+1)
+ midP := piece{origin: false, offset: (uint)(len(ce.addBuffer)), length: (uint)(len(text))}
+
+ ce.addBuffer += text
+
+ newTable = append(*ce.state.pieces, midP)
+
+ return &newTable
+}
+
+func (ce *ConcreteEditor) Delete(offset, length uint) Editor {
+ var currentStart, charsToDelete uint
+
+ leftToDelete := length
+ currentOffset := offset
+
+ newTable := make(pieceTable, 0, len(*ce.state.pieces)+1)
+
+ // for i < (uint)(len(*ce.state.pieces)) {
+ for i, currentPiece := range *ce.state.pieces {
+ // currentPiece = (*ce.state.pieces)[i]
+
+ if leftToDelete == 0 || currentOffset < currentStart || currentOffset > currentStart+currentPiece.length {
+ // nothing to do with this piece, just copy it to the new table
+ newTable = append(newTable, currentPiece)
+ } else {
+ rightPieceEmpty := currentPiece.length <= (currentOffset - currentStart + leftToDelete)
+
+ if rightPieceEmpty {
+ charsToDelete = currentPiece.length - currentOffset - currentStart
+ } else {
+ charsToDelete = leftToDelete
+ }
+
+ leftPiece := piece{
+ origin: currentPiece.origin,
+ offset: currentPiece.offset,
+ length: currentOffset - currentStart}
+
+ rightPiece := piece{
+ origin: currentPiece.origin,
+ offset: currentPiece.offset + currentOffset + charsToDelete,
+ length: currentPiece.length - charsToDelete - (currentOffset - currentStart)}
+
+ if leftPiece.length > 0 {
+ newTable = append(newTable, leftPiece)
+ }
+
+ if !rightPieceEmpty {
+ newTable = append(newTable, rightPiece)
+ }
+
+ leftToDelete -= charsToDelete
+ currentOffset += charsToDelete
+ }
+
+ currentStart += currentPiece.length
+ i++
+ }
+
+ ce.commitTable(&newTable)
+
+ return ce
+}
+
+func (ce *ConcreteEditor) commitTable(newTable *pieceTable) {
+ newState := editorState{pieces: newTable}
+
+ ce.state.next = &newState
+ newState.prev = ce.state
+ ce.state = &newState
+}
+
+func (ce *ConcreteEditor) Undo() Editor {
+ if ce.state.prev != nil {
+ ce.state = ce.state.prev
+ }
+
+ return ce
+}
+
+func (ce *ConcreteEditor) Redo() Editor {
+ if ce.state.next != nil {
+ ce.state = ce.state.next
+ }
+
+ return ce
+}
+
+func (ce *ConcreteEditor) String() string {
+ var result = ""
+ for _, piece := range *ce.state.pieces {
+ var buffer *string
+
+ if piece.origin {
+ buffer = &ce.originBuffer
+ } else {
+ buffer = &ce.addBuffer
+ }
+
+ result += (*buffer)[piece.offset:(piece.offset + piece.length)]
+ }
+
+ return result
+}
+
+// NewEditor creates an editor instance from a string
+func NewEditor(s string) ConcreteEditor {
+ pieces := pieceTable{piece{origin: true, offset: 0, length: (uint)(len(s))}}
+ state := editorState{pieces: &pieces}
+
+ return ConcreteEditor{originBuffer: s, addBuffer: "", state: &state}
+}