Safe HaskellNone

HW02

Contents

Description

Second assignment for IB016, semester spring 2019.

Task overview

Your task is to implement an extended rope data structure and tests for it.

Rope data structure is used to efficiently store and manipulate very long strings. For example, a text editor may use a rope to represent the text. A rope provides efficient insertion, deletion and random access.

Manipulation with long strings in an ordinary list is not always the most effective way. For example, if you want to insert a substring into the string, the time complexity is linear with regards to the length of the string. The rope data structure allows us to do this more efficiently on average.

In ropes, substrings of long string are saved in leaf nodes. Each leaf node holds the length of a substring too. Each non-leaf node holds sum of the lengths of all the leaves in its left subtree ("weight" of node). For example:

This is a rope, which stores the string "Hello_World!".

Your task is to implement basic operations on the extended rope data structure (insertion, deletion, searching) as well as some tests for it. The extended rope data structure stores any list of any type.

You are given the definition of the data structure itself (you are not allowed to change it!). You can find descriptions of the required operations below.

As for the tests, writing an instance of Arbitrary for Rope is quite tedious, as it requires generating all sorts of valid ropes. Therefore we have written an instance of Arbitrary for Rope for you to use in the tests. However, there is an alternative approach to testing a data structure defined in terms of their operations: Generating a random sequence of operations and comparing the results with a reference implementation. Write a generator for such sequences (ActionSeq) and at least the test comparing ropes built using this sequence with a list build from this sequence. Furthermore, write your own tests for the rest of the functionality – all functions should be covered by tests, these test should be written is such a way that it is likely that errors will be discovered in the simplest possible instances. Furthermore, from the test failures it should be clear what operation failed and what were the inputs. These tests will be a part of your evaluation.

You should name all tests with the prop_ prefix. You can then execute them using the runTests function we have defined for you.

Assignment summary

Modules and packages

You can use any modules from the base package, as well as the data-default-class, and the QuickCheck packages. If you wish, you can also use Unicode syntax from base-unicode-symbols. If you want to use any other module or package, please ask in the discussion forum before use.

General Notes & Tips

  • Please be sure that you calculate weights correctly.
  • Don't forget to check that every tree leaf is Leaf (every Node node has to have at least one descendant).
  • Test all functions and their combinations.
  • Keep in mind that rope should have fast modification times, therefore you should either make minimal changes to its structure in insert/delete, or balance it and keep the amortized complexity in mind
Synopsis

Documentation

data Rope a #

A rope structure, which stores a list of values of type a. You are not allowed to change this definition.

Constructors

Node 

Fields

Leaf 

Fields

Instances
Read a => Read (Rope a) # 
Instance details

Defined in HW02

Methods

readsPrec :: Int -> ReadS (Rope a)

readList :: ReadS [Rope a]

readPrec :: ReadPrec (Rope a)

readListPrec :: ReadPrec [Rope a]

Show a => Show (Rope a) # 
Instance details

Defined in HW02

Methods

showsPrec :: Int -> Rope a -> ShowS

show :: Rope a -> String

showList :: [Rope a] -> ShowS

Default (Rope a) #

Creates an empty rope (stores an empty list).

Instance details

Defined in HW02

Methods

def :: Rope a

Arbitrary a => Arbitrary (Rope a) #

Here we give you an instance of Arbitrary for Rope

Instance details

Defined in HW02

Methods

arbitrary :: Gen (Rope a)

shrink :: Rope a -> [Rope a]

index :: Int -> Rope a -> Maybe a #

Return the value at the position i.

empty :: Rope a -> Bool #

Is the stored list empty?

size :: Rope a -> Int #

Get the size (length of the stored list) of the given rope.

valid :: Rope a -> Bool #

Check if the given rope is valid: weights are calculated correctly and every Node has at least one descendant.

validWithRate :: Int -> Rope a -> Bool #

Check if the rope is valid and if the weight of each leaf node is at most the given value (rate).

insert #

Arguments

:: Int

position

-> [a]

given list to insert

-> Rope a

given rope

-> Rope a

result

Inserts a list into a rope at the given position. If the given position is invalid (out of range), then the given rope doesn't change.

For example: insert 3 [10, 11, 42] (fromList [0, 1, 2, 3, 4, 5]) represents the same sequence as fromList [0, 1, 2, 10, 11, 42, 3, 4, 5].

delete #

Arguments

:: Int

beginning position to remove

-> Int

length of the substring to remove

-> Rope a

given rope

-> Rope a

result

Removes a sublist. If the given position is invalid (out of range), then the given rope doesn't change. If the position is valid, but the range ends after the end of the rope, removes all elements from the position to the end.

fromList :: [a] -> Rope a #

Create a rope that stores the given list.

fromListWithRate :: Int -> [a] -> Rope a #

Create a rope that stores the given list and the weight of each leaf node is at most the given value (rate). The rope should be balanced.

toList :: Rope a -> [a] #

Get the list that is stored in the given rope.

Tests

data Action a #

This data type describes actions over Rope in such a way that these actions can be generated by QuickCheck.

Constructors

Insert Int [a] 
Delete Int Int 
Instances
Show a => Show (Action a) # 
Instance details

Defined in HW02

Methods

showsPrec :: Int -> Action a -> ShowS

show :: Action a -> String

showList :: [Action a] -> ShowS

Arbitrary a => Arbitrary (Action a) #

Write an instance for generation of Rope actions. Keep in mind that shrink should try to do one change in each step and must never generate the same value as its argument (to prevent cycling). Arbitrary should be written in such a way that it generates slightly more inserts (in total) then erases.

Instance details

Defined in HW02

Methods

arbitrary :: Gen (Action a)

shrink :: Action a -> [Action a]

newtype ActionSeq a #

A sequence of actions for Arbitrary instance.

Constructors

ActionSeq 

Fields

Instances
Show a => Show (ActionSeq a) # 
Instance details

Defined in HW02

Methods

showsPrec :: Int -> ActionSeq a -> ShowS

show :: ActionSeq a -> String

showList :: [ActionSeq a] -> ShowS

Arbitrary a => Arbitrary (ActionSeq a) #

We give you a simple instance of Arbitrary for ActionSeq (which just uses the implementation for a list of Actions).

Instance details

Defined in HW02

Methods

arbitrary :: Gen (ActionSeq a)

shrink :: ActionSeq a -> [ActionSeq a]

replayRope :: ActionSeq a -> Rope a #

Produce a Rope that corresponds to the given sequence of actions.

replayList :: ActionSeq a -> [a] #

Produce a list that corresponds to the given sequence of actions.

You are not allowed to use the functions that you implemented for the Rope structure here, because you want to test your function against a trustworthy implementation.

prop_compare_with_list :: ActionSeq Char -> Property #

Write a property that produces a Rope and a list from the given sequence and compares them if they hold exactly the same data. Try to write this in such a way that it has nice error messages.

runTests :: IO Bool #

Run all our prop_* tests using QuickCheck, with 1000 tests for each property.

The $ is a TemplateHaskell special symbol which means the following expression (forAllProperties in our case) is evaluated at compile-time.