Repository: nltk/nltk

Issue:
Function to access the Synset object using sense key
I might have missed it but is there a function to access the `Synset` object from the NLTK wordnet interface from the [sense key](https://wordnet.princeton.edu/wordnet/man/senseidx.5WN.html)?

If there isn't could we expose a function that can achieve that in `nltk.corpus.wordnet`? E.g. https://stackoverflow.com/questions/48170666/how-to-get-the-gloss-given-sense-key-using-nltk-wordnet/ 

Ideally, it would be good to have functionalities to access `Synset` objects using:

 1. `offset-pos`, e.g. `1433493-a` -> `Synset('long.a.02')`
 2. `sense_key`, e.g. `long%3:00:02::` ->  `Synset('long.a.02')`

Currently, we have the `synset_from_pos_and_offset()` for (1). 

There's another function (`_synset_from_pos_and_line `) that reads the following line to return `Synset('long.a.02')`:

```
01433493 00 a 01 long 1 016 = 05129201 n 0000 + 05133287 n 0101 ! 01436003 a 0101 & 01434007 a 0000 & 01434218 a 0000 & 01434530 a 0000 & 01434717 a 0000 & 01434841 a 0000 & 01434966 a 0000 & 01435060 a 0000 & 01435189 a 0000 & 01435290 a 0000 & 01435399 a 0000 & 01435507 a 0000 & 01435675 a 0000 & 01435891 a 0000 | primarily spatial sense; of relatively great or greater than average spatial extension or extension as specified; "a long road"; "a long distance"; "contained many long words"; "ten miles long" 
``` 

but it's not the sense key.


Discussion:
I'd like to work on this!

Resolving patch:
diff --git a/nltk/corpus/reader/wordnet.py b/nltk/corpus/reader/wordnet.py
--- a/nltk/corpus/reader/wordnet.py
+++ b/nltk/corpus/reader/wordnet.py
@@ -1475,6 +1475,50 @@ def _next_token(): return next(_iter)
 
         return synset
 
+    def synset_from_sense_key(self, sense_key):
+        """
+        Retrieves synset based on a given sense_key. Sense keys can be
+        obtained from lemma.key()
+
+        From https://wordnet.princeton.edu/wordnet/man/senseidx.5WN.html:
+        A sense_key is represented as:
+            lemma % lex_sense (e.g. 'dog%1:18:01::')
+        where lex_sense is encoded as:
+            ss_type:lex_filenum:lex_id:head_word:head_id
+
+        lemma:       ASCII text of word/collocation, in lower case
+        ss_type:     synset type for the sense (1 digit int)
+                     The synset type is encoded as follows:
+                     1    NOUN
+                     2    VERB
+                     3    ADJECTIVE
+                     4    ADVERB
+                     5    ADJECTIVE SATELLITE
+        lex_filenum: name of lexicographer file containing the synset for the sense (2 digit int)
+        lex_id:      when paired with lemma, uniquely identifies a sense in the lexicographer file (2 digit int)
+        head_word:   lemma of the first word in satellite's head synset
+                     Only used if sense is in an adjective satellite synset
+        head_id:     uniquely identifies sense in a lexicographer file when paired with head_word
+                     Only used if head_word is present (2 digit int)
+        """
+        sense_key_regex = re.compile(r"(.*)\%(.*):(.*):(.*):(.*):(.*)")
+        synset_types = {1:NOUN, 2:VERB, 3:ADJ, 4:ADV, 5:ADJ_SAT}
+        lemma, ss_type, _, lex_id, _, _ = sense_key_regex.match(sense_key).groups()
+
+        # check that information extracted from sense_key is valid
+        error = None
+        if not lemma:
+            error = "lemma"
+        elif int(ss_type) not in synset_types:
+            error = "ss_type"
+        elif int(lex_id) < 0 or int(lex_id) > 99:
+            error = "lex_id"
+        if error:
+            raise WordNetError("valid {} could not be extracted from the sense key".format(error))
+
+        synset_id = '.'.join([lemma, synset_types[int(ss_type)], lex_id])
+        return self.synset(synset_id)
+
     #############################################################
     # Retrieve synsets and lemmas.
     #############################################################


Test patch: