Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement exercise dot-dsl #978

Merged
merged 15 commits into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,23 @@
"object_oriented_programming"
]
},
{
"uuid": "a9c2fbda-a1e4-42dd-842f-4de5bb361b91",
"slug": "dot-dsl",
"core": false,
"unlocked_by": null,
"difficulty": 5,
"topics": [
"equality",
"classes",
"lists",
"domain_specific_languages",
"graphs",
"object_oriented_programming",
"test_driven_development",
"transforming"
]
},
{
"uuid": "dc6e61a2-e9b9-4406-ba5c-188252afbba1",
"slug": "transpose",
Expand Down
14 changes: 14 additions & 0 deletions exercises/dot-dsl/.meta/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Description of DSL

A graph, in this DSL, is an object of type `Graph`, taking a list of one
or more

+ attributes
+ nodes
+ edges

described as tuples.

The implementations of `Node` and `Edge` provided in `dot_dsl.py`.

Observe the test cases in `dot_dsl_test.py` to understand the DSL's design.
52 changes: 52 additions & 0 deletions exercises/dot-dsl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Dot Dsl

Write a Domain Specific Language similar to the Graphviz dot language.

A [Domain Specific Language
(DSL)](https://en.wikipedia.org/wiki/Domain-specific_language) is a
small language optimized for a specific domain.

For example the dot language of [Graphviz](http://graphviz.org) allows
you to write a textual description of a graph which is then transformed
into a picture by one of the graphviz tools (such as `dot`). A simple
graph looks like this:

graph {
graph [bgcolor="yellow"]
a [color="red"]
b [color="blue"]
a -- b [color="green"]
}

Putting this in a file `example.dot` and running `dot example.dot -T png
-o example.png` creates an image `example.png` with red and blue circle
connected by a green line on a yellow background.

Create a DSL similar to the dot language.

## Description of DSL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section should also go into a .meta/hints.md file so that it doesn't get lost if we regenerate the READMEs in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!


A graph, in this DSL, is an object of type `Graph`, taking a list of one
or more

+ attributes
+ nodes
+ edges

described as tuples.

The implementations of `Node` and `Edge` provided in `dot_dsl.py`.

Observe the test cases in `dot_dsl_test.py` to understand the DSL's design.

## Submitting Exercises

Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.

For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.

For more detailed information about running tests, code style and linting, please see the [help page](http://exercism.io/languages/python).

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.
27 changes: 27 additions & 0 deletions exercises/dot-dsl/dot_dsl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
NODE, EDGE, ATTR = range(3)


class Node(object):
def __init__(self, name, attrs={}):
self.name = name
self.attrs = attrs

def __eq__(self, other):
return self.name == other.name and self.attrs == other.attrs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether it might be better not to implement this but to instead hint that it should be implemented? I feel like rather a lot of code is being supplied pre-written, without the user having to think about how to check for equality. I think that we should either set a lower difficulty for this problem, or we should add a .meta/hints.md that lets the user know that they need to implement the __eq__ method themself (with each method being stripped down to just pass).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was concerned that without defining __eq__, the tests wouldn't even pass. A lot of the test cases, such as

        self.assertEqual(g.nodes, [Node("a", {"color": "green"})])

requires __eq__ to be defined properly for the check to work.

Perhaps we could say that as part of TDD, the person working on this topic should seek to first complete __eq__ so that test cases work as the human would expect?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it again, I think it should be ok to leave it as it is - I'm not a great fan of supplying too much code, but the definition is also fairly easy and could border on being tedious.



class Edge(object):
def __init__(self, src, dst, attrs={}):
self.src = src
self.dst = dst
self.attrs = attrs

def __eq__(self, other):
return (self.src == other.src and
self.dst == other.dst and
self.attrs == other.attrs)


class Graph(object):
def __init__(self, data=[]):
pass
117 changes: 117 additions & 0 deletions exercises/dot-dsl/dot_dsl_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import unittest

from dot_dsl import Graph, Node, Edge, NODE, EDGE, ATTR


class DotDslTest(unittest.TestCase):
def test_empty_graph(self):
g = Graph()

self.assertEqual(g.nodes, [])
self.assertEqual(g.edges, [])
self.assertEqual(g.attrs, {})

def test_graph_with_one_node(self):
g = Graph([
(NODE, "a", {})
])

self.assertEqual(g.nodes, [Node("a")])
self.assertEqual(g.edges, [])
self.assertEqual(g.attrs, {})

def test_graph_with_one_node_with_keywords(self):
g = Graph([
(NODE, "a", {"color": "green"})
])

self.assertEqual(g.nodes, [Node("a", {"color": "green"})])
self.assertEqual(g.edges, [])
self.assertEqual(g.attrs, {})

def test_graph_with_one_edge(self):
g = Graph([
(EDGE, "a", "b", {})
])

self.assertEqual(g.nodes, [])
self.assertEqual(g.edges, [Edge("a", "b", {})])
self.assertEqual(g.attrs, {})

def test_graph_with_one_attribute(self):
g = Graph([
(ATTR, "foo", "1")
])

self.assertEqual(g.nodes, [])
self.assertEqual(g.edges, [])
self.assertEqual(g.attrs, {"foo": "1"})

def test_graph_with_attributes(self):
g = Graph([
(ATTR, "foo", "1"),
(ATTR, "title", "Testing Attrs"),
(NODE, "a", {"color": "green"}),
(NODE, "c", {}),
(NODE, "b", {"label", "Beta!"}),
(EDGE, "b", "c", {}),
(EDGE, "a", "b", {"color": "blue"}),
(ATTR, "bar", "true")
])

self.assertEqual(g.nodes, [Node("a", {"color": "green"}),
Node("c", {}),
Node("b", {"label", "Beta!"})])
self.assertEqual(g.edges, [Edge("b", "c", {}),
Edge("a", "b", {"color": "blue"})])
self.assertEqual(g.attrs, {
"foo": "1",
"title": "Testing Attrs",
"bar": "true"
})

def test_malformed_graph(self):
with self.assertRaises(TypeError):
Graph(1)

with self.assertRaises(TypeError):
Graph("problematic")

def test_malformed_graph_item(self):
with self.assertRaises(TypeError):
Graph([
()
])

with self.assertRaises(TypeError):
Graph([
(ATTR, )
])

def test_malformed_attr(self):
with self.assertRaises(ValueError):
Graph([
(ATTR, 1, 2, 3)
])

def test_malformed_node(self):
with self.assertRaises(ValueError):
Graph([
(NODE, 1, 2, 3)
])

def test_malformed_EDGE(self):
with self.assertRaises(ValueError):
Graph([
(EDGE, 1, 2)
])

def test_unknown_item(self):
with self.assertRaises(ValueError):
Graph([
(99, 1, 2)
])


if __name__ == '__main__':
unittest.main()
52 changes: 52 additions & 0 deletions exercises/dot-dsl/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
NODE, EDGE, ATTR = range(3)


class Node(object):
def __init__(self, name, attrs={}):
self.name = name
self.attrs = attrs

def __eq__(self, other):
return self.name == other.name and self.attrs == other.attrs


class Edge(object):
def __init__(self, src, dst, attrs={}):
self.src = src
self.dst = dst
self.attrs = attrs

def __eq__(self, other):
return (self.src == other.src and
self.dst == other.dst and
self.attrs == other.attrs)


class Graph(object):
def __init__(self, data=[]):
self.nodes = []
self.edges = []
self.attrs = {}

if not isinstance(data, list):
raise TypeError("Graph data malformed")

for item in data:
if len(item) < 3:
raise TypeError("Graph item incomplete")

type_ = item[0]
if type_ == ATTR:
if len(item) != 3:
raise ValueError("ATTR malformed")
self.attrs[item[1]] = item[2]
elif type_ == NODE:
if len(item) != 3:
raise ValueError("NODE malformed")
self.nodes.append(Node(item[1], item[2]))
elif type_ == EDGE:
if len(item) != 4:
raise ValueError("EDGE malformed")
self.edges.append(Edge(item[1], item[2], item[3]))
else:
raise ValueError("Unknown item {}".format(item[0]))