Repository: joke2k/faker

Issue:
Ensure words() returns a unique list of words without duplicates
When calling `words(10)` there is no guarantee that the returned list of elements is unique.   While I could do `set(words(10))`, this may only return 9 items when I am in need of 10.

If creating a list of words with potential duplicates is a useful feature to some, maybe adding an optional parameter called `unique` to the `words` generator could prove useful.


Discussion:
Hi @fgs-dbudwin 

Thanks for the report.

I have PR #792  up to refactor how we choose elements from list, which include optional uniqueness.

Once that's merged in, this feature will be trivial to implement



Resolving patch:
diff --git a/faker/providers/lorem/__init__.py b/faker/providers/lorem/__init__.py
--- a/faker/providers/lorem/__init__.py
+++ b/faker/providers/lorem/__init__.py
@@ -26,7 +26,7 @@ class Provider(BaseProvider):
     word_connector = ' '
     sentence_punctuation = '.'
 
-    def words(self, nb=3, ext_word_list=None):
+    def words(self, nb=3, ext_word_list=None, unique=False):
         """
         :returns: An array of random words. for example: ['Lorem', 'ipsum', 'dolor']
 
@@ -34,10 +34,13 @@ def words(self, nb=3, ext_word_list=None):
         :param nb: how many words to return
         :param ext_word_list: a list of words you would like to have instead of
             'Lorem ipsum'
+        :param unique: If True, the returned word list will contain unique words
 
         :rtype: list
         """
         word_list = ext_word_list if ext_word_list else self.word_list
+        if unique:
+            return self.random_sample(word_list, length=nb)
         return self.random_choices(word_list, length=nb)
 
     def word(self, ext_word_list=None):


Test patch:
diff --git a/tests/test_factory.py b/tests/test_factory.py
--- a/tests/test_factory.py
+++ b/tests/test_factory.py
@@ -330,6 +330,91 @@ def test_ext_word_list(self):
         word = fake.word(ext_word_list=my_word_list)
         self.assertIn(word, my_word_list)
 
+    def test_no_words(self):
+        fake = Faker()
+
+        words = fake.words(0)
+        self.assertEqual(words, [])
+
+    def test_some_words(self):
+        fake = Faker()
+
+        num_words = 5
+        words = fake.words(num_words)
+        self.assertTrue(isinstance(words, list))
+        self.assertEqual(len(words), num_words)
+
+        for word in words:
+            self.assertTrue(isinstance(word, string_types))
+            self.assertTrue(re.match(r'^[a-z].*$', word))
+
+    def test_words_ext_word_list(self):
+        fake = Faker()
+
+        my_word_list = [
+            'danish',
+            'cheesecake',
+            'sugar',
+            'Lollipop',
+            'wafer',
+            'Gummies',
+            'Jelly',
+            'pie',
+        ]
+
+        num_words = 5
+        words = fake.words(5, ext_word_list=my_word_list)
+        self.assertTrue(isinstance(words, list))
+        self.assertEqual(len(words), num_words)
+
+        for word in words:
+            self.assertTrue(isinstance(word, string_types))
+            self.assertIn(word, my_word_list)
+
+    def test_words_ext_word_list_unique(self):
+        fake = Faker()
+
+        my_word_list = [
+            'danish',
+            'cheesecake',
+            'sugar',
+            'Lollipop',
+            'wafer',
+            'Gummies',
+            'Jelly',
+            'pie',
+        ]
+
+        num_words = 5
+        words = fake.words(5, ext_word_list=my_word_list)
+        self.assertTrue(isinstance(words, list))
+        self.assertEqual(len(words), num_words)
+
+        checked_words = []
+        for word in words:
+            self.assertTrue(isinstance(word, string_types))
+            self.assertIn(word, my_word_list)
+            # Check that word is unique
+            self.assertTrue(word not in checked_words)
+            checked_words.append(word)
+
+    def test_unique_words(self):
+        fake = Faker()
+
+        num_words = 20
+        words = fake.words(num_words, unique=True)
+        self.assertTrue(isinstance(words, list))
+        self.assertEqual(len(words), num_words)
+
+        checked_words = []
+        for word in words:
+            self.assertTrue(isinstance(word, string_types))
+            # Check that word is lowercase letters. No numbers, symbols, etc
+            self.assertTrue(re.match(r'^[a-z].*$', word))
+            # Check that word list is unique
+            self.assertTrue(word not in checked_words)
+            checked_words.append(word)
+
     def test_random_pystr_characters(self):
         from faker.providers.python import Provider
         provider = Provider(self.generator)