-
-
Notifications
You must be signed in to change notification settings - Fork 550
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
Shrink & clean up custom_set.json #232
Shrink & clean up custom_set.json #232
Conversation
I do not understand how size, is_empty and delete are not vital for a set. I need the size very often when dealing with sets in my code, also I do need a fast check for emptiness which does not count a billion of entry's before it tells me that I have a size unequal to 0. Also when we are able to insert a single element, we should also be able to remove one. For some implementations a specialised delete is much faster than creating and subtracting a singleton set. |
@NobbZ, I think it depends on what we want this exercise to teach. I think there are two options:
If it's 1, then you're absolutely right. We should do But if it's 2, then I think we can focus the API on teaching what we want the exercise to teach. And I didn't see how |
I think that whether we lean towards 1 or 2 we should clarify in the README what the purpose is. |
@exercism/track-maintainers, thoughts on what the purpose of this exercise is? |
One |
I was actually thinking (2) more than (1). |
@kytrinyx's vote puts at 2-1 in favor of option (2). That's not a very high turnout, though. I'd like to hear from more people... |
I like (1), personally |
Although I don't think that the options are really mutually exclusive |
I'm leaning towards 2 because there are plenty of other places to learn about implementing data structures in the language of your choice. I wouldn't call that a primary purpose of exercism. However, I agree with @ryanplusplus that the two aren't mutually exclusive. Still, I feel like 1 might be putting too many details into the exercise and could result in more muddied code and/or discussions. tl;dr: 2, but open to changing |
Hmm. I do find when reviewing submissions of the more involved exercises (ones that require a lot of code) that sometimes it's hard to figure out how to go about it. There's a lot of code, where do I start reviewing? etc. For that reason, it is good to keep exercises focused. No more than what they need to be. ... All right, admitted that the above doesn't take a stance on whether this exercise should focus on the full set library or just the interesting set operations. I'll pick just the set operations, but I could be convinced either way. |
@ryanplusplus What tests would you see in the suite if we combined options 1 & 2? |
@IanWhitney basically my thought was that we'd strip the exercise down to the essential set operations (as in 1), but reorder those tests to try to minimize the amount of code that has to be written to pass each successive test (as in 2). I think that the example of starting with |
I like that approach, @ryanplusplus. What would the right order be, do you think? I think I would go with:
I lean towards dropping Downside of this approach is that the test suite stays pretty big, right? Probably ~45 tests. Is having those extra methods worth it? |
@IanWhitney I like your suggested order, but would move |
Sure. I keep forgetting that equality can be done much better than I did in my terrible implementation. |
4d00d78
to
5f9ccba
Compare
Changes!
I'm going to let this sit until Monday. If things look good then, I'll merge. |
"equal": { | ||
"description": ["Test two sets for equality."], | ||
"empty": { | ||
"description": "Returns true if the set contains any elements", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic inverted. true if it contains no elements, or false if it contains any elements.
Updated to address @petertseng's comments. I'll rebase all this stuff before any merge. |
"set2": [4], | ||
"expected": [1, 2, 3, 4] | ||
"description": "sets with the same elements are equal", | ||
"set1": [1,2], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we care very much about consistent spacing (space after comma)? If so, should deal with this and the other test a few lines below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Sat 2016-05-14 15:08, Peter Tseng wrote:
}, {
"description": "no elements in common",
"set1": [1, 2, 3],
"set2": [4],
"expected": [1, 2, 3, 4]
"description": "sets with the same elements are equal",
"set1": [1,2],
do we care very much about consistent spacing (space after comma)? If so, should deal with this and the other test a few lines below
Yes, always care about consistency, anything that is a change in
consistency is used in communicating something. Sometimes it
communicates we aren't paying attention, someone looking at it will
spend thought cycles trying to determine if there is something special
about the difference, and may eventually determine the truth... someone
was not paying attention, or may attribute it to something else.
So yes, we should very much care about consistency.
I think that's all I have to say about what's here, and I can't think of any test that should be here that shouldn't be. |
Maybe a test that [1, 2, 3] is not a subset of [1, 2], but that may have already been covered by the test that [1, 2, 3] is not a subset of [1, 2, 4], unsure. |
Possible thing someone may say: does it seem "weird" to have equal come between difference and union? Maybe because we're going back and forth between operations that take two sets and return
Seems weird to go back and forth like that, I guess? I know there was a desire to have equal after difference though, so it's a bit tough. Not sure if anyone will be bothered by it |
(to be fair, I had the Rust PR ready to go before I even merged the test changes) |
Okay, I realize I'm late on this, but I think there are some issues with the new tests. Many of the tests that come before "equal" depend upon "equal" in the assertion (which kind of defeats the purpose of the reordering). I think this is probably my fault because I suggested putting "equal" after "subset" and "difference". Given this, I think we need to make a few more changes before we roll this out. I think this can be fixed by moving "equal" up above all dependent tests. It can still stay below "subset", but can't be below "difference". Looking through the tests, I think we'd be okay as long as we move "subset" and "equal" below "add". Thoughts? |
I'm not sure what you mean. How do the earlier tests depend on equal? |
Take an "add" test:
It's strongly implied that the expectation will be checked with "equal". I realize that you can check with repeated uses of "contains" (and I started by doing this), but eventually it becomes absurd. Additionally, it appears that your tests for Rust use "equal" prematurely (thought I'm not a Rust guy so maybe I'm misunderstanding something). |
Maybe it's semantics, but is the assert_eq!(Set::new(1), Set::new(1)) And that can work, even if I don't define the In Ruby I could do the same: class CustomSet
#...
def ==(other)
self.collection == other.collection
end
#...
end
assert_equal CustomSet.new(1), CustomSet.new(1) Then, later on, I implement an actual class CustomSet
def equal(other)
self.difference(other).empty? && other.difference(self).empty?
end
def ==(other)
self.collection == other.collection
end
end Maybe this approach doesn't work in other languages, though. These two are the only ones I'm familiar with. |
I think that for many (most?) of the language tracks Even if a language does check value equality by default, a set can be represented multiple ways and you're not going to be able to use value equality as a reliable measure of set equality. For instance, say that your internal representation is a vector of all of the set elements. Because sets are not ordered, you might have a set with an internal representation |
This is bikeshedding in the extreme (which is fine by me, btw. not complaining). In the cases above, neither language has an When the Student reaches the test for implementing the I'm fully on board with your suggestion that this might be a problem in other languages. Maybe a maintainer of a language that has a problem with our current test implementation can contribute here? Or do we need a new issue, since I'm not sure how many people read comments on closed pull requests. |
I think the fundamental problem is that regardless of what mechanism(s) a language provides, implementing Given this, the reordering doesn't make sense because if someone goes through the tests from top to bottom, they will need to implement set equality before getting to the |
Pardon me for butting in, have not been following this closely, but I do think that this test has a bit of a problem. Since sets are unordered one is unlikely to be able to use a built in equality operator to check if the "given set plus an element" is equivalent to the "expected set". In the Common Lisp track the test's assertion would most likely use the |
I'm not familiar with Lisp, but since you say that the Student could implement their own Also, in the case of Lisp (or other languages with an existing @ryanplusplus, maybe a longer Ruby example will make my idea clear. class CustomSet
def ==(other)
collection == other.collection
end
def eq?(other)
self == other
end
def initialize(element)
self.collection << [element]
end
def collection
@collection ||= []
end
end With that code, and that code only, this test passes: set1 = CustomSet.new(1)
set2 = CustomSet.new(1)
assert_equal(set1, set2) Because we implemented the set1 = CustomSet.new(1)
set2 = CustomSet.new(1)
assert set1.equal(set2)
#NoMethodError: undefined method `equal' for #<CustomSet:0x007fd7ad006db0 @collection=[[1]]>
# from (irb):21
# from /Users/Ian/.rubies/2.2.3/bin/irb:11:in `<main>' Because there is no global class CustomSet
# everything else is unchanged
def equal(other)
self == other
end
end Or I could do it in a wrong way class CustomSet
# everything else is unchanged
def equal(other)
self != other
end
end Or I could do it using my set methods class CustomSet
# everything else is unchanged
def equal(other)
self.difference(other).empty? && other.difference(self).empty?
end
end The behavior of my two correct implementations are exactly the same, sure. But (in Ruby, at least) I'm not required to define If your language allows this sort of test ordering, then I think it's valuable to leave the definition of But if there's a language where this sort of approach is impossible, then move the |
@IanWhitney believe me, I understand that you can implement both To hopefully make this more clear, consider this
Even in your sample Ruby implementation this will fail for It is untrue to say that |
I think I need to punt on this one. It's not that I don't see the problem you're describing, it's that I don't see it as a problem. The best approach here is to submit a PR, get feedback from people with opinions other than mine and then I'll update the Rust tests to do whichever. |
Backing up @ryanplusplus and @verdammelt here. For Common Lisp this exercise could easily devolve into an exercise of how well a programmer can implement generic equality since one could easily implement something with weird behaviors if they rely on (defmethod equals ((obj1 t) (obj2 t)
&rest keys
&key recursive (case-sensitive t) &allow-other-keys)
(declare (ignorable recursive))
(when (equal (type-of obj1) (type-of obj2))
(typecase obj1
(number (= obj1 obj2))
(cons (tree-equal obj1 obj2 :test #'equals))
(character (funcall (if case-sensitive #'char= #'char-equal) obj1 obj2))
(string (funcall (if case-sensitive #'string= #'string-equal) obj1 obj2))
(array (loop for i from 0 below (array-total-size obj1)
always (apply #'EQUALS
(row-major-aref obj1 i)
(row-major-aref obj2 i)
keys)))
(hash-table (error "Not implemented.")) ; FIXME
;; structure not implemented either but default behavior probably okay
(otherwise (equalp obj1 obj2)))))
(defmethod equals ((set1 set) (set2 set)
&rest keys
&key recursive &allow-other-keys)
(null (set-difference (elements-of set1) (elements-of set2) :test #'equals))) |
@IanWhitney is right, a PR is the best way to further discuss these changes. Please see #257. |
updated tests for custom-set exercism#328 In the process of updating the tests I ended up simplifying(hopefully) the tests cases. I removed all the extra methods and functions including String which fundamentally changed the tests. I've also added a stub with only the Set type in it, so that the exercise focuses on the set operations rather than defining a set type. This also means that the set type is agreed up front and so there's no need to accomodate all possible types that people could come up with for a set by defining it with something like a string method. Also relevant for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
updated tests for custom-set exercism#328 In the process of updating the tests I ended up simplifying(hopefully) the tests cases. I removed all the extra methods and functions including String which fundamentally changed the tests. I've also added a stub with only the Set type in it, so that the exercise focuses on the set operations rather than how to define the set type. This also means that the set type is agreed up front and so there's no need to accomodate all possible types that people could come up with for a set by defining it with something like a string method. Also relevant for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
updated tests for custom-set exercism#328 for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
updated tests for custom-set exercism#328 for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
updated tests for custom-set exercism#328 for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
updated tests for custom-set exercism#328 for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
updated tests for custom-set exercism#328 for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
updated tests for custom-set exercism#328 for reference: exercism/problem-specifications#232 exercism/problem-specifications#257
Removed of_rna transcription logic and tests for rna_transcription exercise
Discussed here: exercism/discussions#10
I'm trying to improve custom_set in two ways:
implementations.
Reducing the test suite
The previous test suite contained 74 tests, which is a lot. I haven't
checked all the exercises, but it's the biggest test suite that I've
come across.
If all of those tests provided value (exposing corner cases, improving
implementations, etc.) then that's fine. But I found there to be a lot
of duplicate tests. With the subset/union/etc tests, a student's
implementation is usually done by the 2nd or 3rd test, so the remaining
tests didn't provide any additional value.
So I have removed the tests that seemed redundant.
The tests also expected methods like
size
,delete
oris_empty
. Ihave removed those because
Changing test order
The previous test suite started with
equal
. I found that this requiresstudents to implement two things:
I have chosen to start the tests with
contains
, since that onlyrequires one set. And, helpfully, when the student implements
add
andequal
, they can leverage their already-existingcontains
function.