Repository: Qiskit/qiskit

Issue:
Count constructor crashes when data is empty
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.15.1
- **Python version**: 3.7.3
- **Operating system**: Mac OS Catalina 10.15.6

### What is the current behavior?
I simply ran a circuit which does not perform any measurements. On the returned results, `get_counts()` crashes because an empty dictionary is passed into the `Count` constructor and the constructor tries to iterate on the empty dictionary.

I tried this on an older version, Terra 0.14.2, which instead of `Count` uses `postprocess.format_counts()`, which simply ignores the empty dictionary and returns an empty dictionary in this case. This did not crash.


### Steps to reproduce the problem
Run any circuit without calling `measure()`, then try to apply `get_counts()` to the results.


### What is the expected behavior?
`get_counts()` should return an empty dictionary (and not crash).


### Suggested solutions
A simple check for an empty dictionary in the `Count` constructor would be sufficient, I think.




Discussion:

Resolving patch:
diff --git a/qiskit/result/counts.py b/qiskit/result/counts.py
--- a/qiskit/result/counts.py
+++ b/qiskit/result/counts.py
@@ -63,44 +63,49 @@ def __init__(self, data, time_taken=None, creg_sizes=None,
         """
         bin_data = None
         data = dict(data)
-        first_key = next(iter(data.keys()))
-        if isinstance(first_key, int):
-            self.int_raw = data
-            self.hex_raw = {
-                hex(key): value for key, value in self.int_raw.items()}
-        elif isinstance(first_key, str):
-            if first_key.startswith('0x'):
-                self.hex_raw = data
-                self.int_raw = {
-                    int(key, 0): value for key, value in self.hex_raw.items()}
-            elif first_key.startswith('0b'):
-                self.int_raw = {
-                    int(key, 0): value for key, value in data.items()}
+        if not data:
+            self.int_raw = {}
+            self.hex_raw = {}
+            bin_data = {}
+        else:
+            first_key = next(iter(data.keys()))
+            if isinstance(first_key, int):
+                self.int_raw = data
                 self.hex_raw = {
                     hex(key): value for key, value in self.int_raw.items()}
-            else:
-                if not creg_sizes and not memory_slots:
-                    self.hex_raw = None
-                    self.int_raw = None
-                    bin_data = data
+            elif isinstance(first_key, str):
+                if first_key.startswith('0x'):
+                    self.hex_raw = data
+                    self.int_raw = {
+                        int(key, 0): value for key, value in self.hex_raw.items()}
+                elif first_key.startswith('0b'):
+                    self.int_raw = {
+                        int(key, 0): value for key, value in data.items()}
+                    self.hex_raw = {
+                        hex(key): value for key, value in self.int_raw.items()}
                 else:
-                    bitstring_regex = re.compile(r'^[01\s]+$')
-                    hex_dict = {}
-                    int_dict = {}
-                    for bitstring, value in data.items():
-                        if not bitstring_regex.search(bitstring):
-                            raise exceptions.QiskitError(
-                                'Counts objects with dit strings do not '
-                                'currently support dit string formatting parameters '
-                                'creg_sizes or memory_slots')
-                        int_key = int(bitstring.replace(" ", ""), 2)
-                        int_dict[int_key] = value
-                        hex_dict[hex(int_key)] = value
-                    self.hex_raw = hex_dict
-                    self.int_raw = int_dict
-        else:
-            raise TypeError("Invalid input key type %s, must be either an int "
-                            "key or string key with hexademical value or bit string")
+                    if not creg_sizes and not memory_slots:
+                        self.hex_raw = None
+                        self.int_raw = None
+                        bin_data = data
+                    else:
+                        bitstring_regex = re.compile(r'^[01\s]+$')
+                        hex_dict = {}
+                        int_dict = {}
+                        for bitstring, value in data.items():
+                            if not bitstring_regex.search(bitstring):
+                                raise exceptions.QiskitError(
+                                    'Counts objects with dit strings do not '
+                                    'currently support dit string formatting parameters '
+                                    'creg_sizes or memory_slots')
+                            int_key = int(bitstring.replace(" ", ""), 2)
+                            int_dict[int_key] = value
+                            hex_dict[hex(int_key)] = value
+                        self.hex_raw = hex_dict
+                        self.int_raw = int_dict
+            else:
+                raise TypeError("Invalid input key type %s, must be either an int "
+                                "key or string key with hexademical value or bit string")
         header = {}
         self.creg_sizes = creg_sizes
         if self.creg_sizes:
@@ -119,8 +124,12 @@ def most_frequent(self):
         Returns:
             str: The bit string for the most frequent result
         Raises:
-            QiskitError: when there is >1 count with the same max counts
+            QiskitError: when there is >1 count with the same max counts, or
+                an empty object.
         """
+        if not self:
+            raise exceptions.QiskitError(
+                "Can not return a most frequent count on an empty object")
         max_value = max(self.values())
         max_values_counts = [x[0] for x in self.items() if x[1] == max_value]
         if len(max_values_counts) != 1:


Test patch:
diff --git a/test/python/result/test_counts.py b/test/python/result/test_counts.py
--- a/test/python/result/test_counts.py
+++ b/test/python/result/test_counts.py
@@ -254,3 +254,37 @@ def test_hex_outcomes_0b_bitstring_counts(self):
         counts_obj = counts.Counts(raw_counts)
         result = counts_obj.hex_outcomes()
         self.assertEqual(expected, result)
+
+    def test_empty_bitstring_counts(self):
+        raw_counts = {}
+        expected = {}
+        result = counts.Counts(raw_counts)
+        self.assertEqual(expected, result)
+
+    def test_empty_bistring_counts_with_exta_formatting_data(self):
+        raw_counts = {}
+        expected = {}
+        result = counts.Counts(raw_counts, 'test_counts',
+                               creg_sizes=[['c0', 2], ['c0', 1], ['c1', 1]],
+                               memory_slots=4)
+        self.assertEqual(result, expected)
+
+    def test_int_outcomes_with_empty_counts(self):
+        raw_counts = {}
+        expected = {}
+        counts_obj = counts.Counts(raw_counts)
+        result = counts_obj.int_outcomes()
+        self.assertEqual(expected, result)
+
+    def test_most_frequent_empty_bitstring_counts(self):
+        raw_counts = {}
+        counts_obj = counts.Counts(raw_counts)
+        self.assertRaises(exceptions.QiskitError,
+                          counts_obj.most_frequent)
+
+    def test_hex_outcomes_empty_bitstring_counts(self):
+        raw_counts = {}
+        expected = {}
+        counts_obj = counts.Counts(raw_counts)
+        result = counts_obj.hex_outcomes()
+        self.assertEqual(expected, result)