Repository: more-itertools/more-itertools (Python)

Issue:
Proposal: `iequals` function
### Description
<!-- Explain what your proposed itertools does here -->
New function `iequals` that checks whether two iterables consist of the same elements. This is useful when comparing sequences of different data types or iterators, because we can't use the `==` operator. In such cases, it is necessary to convert sequences to the same data type, which requires extra memory and performance costs. We can use this function, for example, in tests, so as not to convert the iterator into a list for comparison with the expected result.

### References
<!-- Link to a discussion on, e.g. Stack Overflow, showing where the new tool would have been useful -->
<!-- Or link to a GitHub issue in another repository -->
<!-- Or a discussion on the python-ideas mailing list -->
<!-- Alternatively, describe a real problem you had to solve with this itertool -->
https://stackoverflow.com/questions/900420/elegant-way-to-compare-sequences

### Examples
<!-- Provide a code sample showing how your itertool works -->
```python
>>> iequals(map(abs, [-1, 2, -3, 4]), [1, 2, 3, 4])
True

>>> iequals(longest_common_prefix(['ab', 'abc', 'abd']), 'ab')
True

>>> string = 'step on no pets'
>>> iequals(string, reversed(string))
True
```

### Name
The letter "i" in the function name emphasizes that this function handles the given iterables as iterables, ignoring type differences. Perhaps we should use the `ieq` name so that the function name refers to `operator.eq`, but I'm not sure that a three-letter name based on two abbreviations would be understandable. Maybe, we can use the the name `elementwise_equals`, which may be more understandable, but my knowledge of English is not enough to determine whether this is a good name. Вesides, this name seems too long.


Discussion:
Is this different from [all_equal](https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.all_equal)?
`all_equal` checks whether all elements of the sequence are equal to each other, `iequals` will check whether two iterables are equal element by element.
Oh, thanks - apologies; I should have read more carefully.

I will accept a PR that does something like:

```python
return all(a == b for a, b in izip_longest(iterable_1, iterable_2))
```

Or for bonus points:
```python
return all(all(x == t[0] for x in t) for t in zip_longest(*iterables))
```



Patch:
diff --git a/docs/api.rst b/docs/api.rst
index ce738c8f..6c0058d5 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -153,6 +153,7 @@ These tools return summarized or aggregated data from an iterable.
 .. function:: minmax(iterable, *[, key, default])
 .. autofunction:: minmax(arg1, arg2, *args[, key])
   :noindex:
+.. autofunction:: iequals
 
 ----
 
diff --git a/more_itertools/more.py b/more_itertools/more.py
index 8944536d..18c78908 100755
--- a/more_itertools/more.py
+++ b/more_itertools/more.py
@@ -70,6 +70,7 @@
     'first',
     'groupby_transform',
     'ichunked',
+    'iequals',
     'ilen',
     'interleave',
     'interleave_evenly',
@@ -3356,6 +3357,26 @@ def ichunked(iterable, n):
         chunk.fill_cache()
 
 
+def iequals(*iterables):
+    """Return ``True`` if all given *iterables* are equal to each other,
+    which means that they contain the same elements in the same order.
+
+    The function is useful for comparing iterables of different data types
+    or iterables that do not support equality checks.
+
+    >>> iequals("abc", ['a', 'b', 'c'], ('a', 'b', 'c'), iter("abc"))
+    True
+
+    >>> iequals("abc", "acb")
+    False
+
+    Not to be confused with :func:`all_equals`, which checks whether all
+    elements of iterable are equal to each other.
+
+    """
+    return all(map(all_equal, zip_longest(*iterables, fillvalue=object())))
+
+
 def distinct_combinations(iterable, r):
     """Yield the distinct combinations of *r* items taken from *iterable*.
 
diff --git a/more_itertools/more.pyi b/more_itertools/more.pyi
index 2d7819c0..d61e04af 100644
--- a/more_itertools/more.pyi
+++ b/more_itertools/more.pyi
@@ -665,3 +665,4 @@ def minmax(
 def longest_common_prefix(
     iterables: Iterable[Iterable[_T]],
 ) -> Iterator[_T]: ...
+def iequals(*iterables: Iterable[object]) -> bool: ...


Test patch:
diff --git a/tests/test_more.py b/tests/test_more.py
index 2bb99950..b1745f88 100644
--- a/tests/test_more.py
+++ b/tests/test_more.py
@@ -5101,3 +5101,39 @@ def test_infinite_iterables(self):
     def test_contains_infinite_iterables(self):
         iterables = [[0, 1, 2], count()]
         self.assertEqual(list(mi.longest_common_prefix(iterables)), [0, 1, 2])
+
+
+class IequalsTests(TestCase):
+    def test_basic(self):
+        self.assertTrue(mi.iequals("abc", iter("abc")))
+        self.assertTrue(mi.iequals(range(3), [0, 1, 2]))
+        self.assertFalse(mi.iequals("abc", [0, 1, 2]))
+
+    def test_no_iterables(self):
+        self.assertTrue(mi.iequals())
+
+    def test_one_iterable(self):
+        self.assertTrue(mi.iequals("abc"))
+
+    def test_more_than_two_iterable(self):
+        self.assertTrue(mi.iequals("abc", iter("abc"), ['a', 'b', 'c']))
+        self.assertFalse(mi.iequals("abc", iter("abc"), ['a', 'b', 'd']))
+
+    def test_order_matters(self):
+        self.assertFalse(mi.iequals("abc", "acb"))
+
+    def test_not_equal_lengths(self):
+        self.assertFalse(mi.iequals("abc", "ab"))
+        self.assertFalse(mi.iequals("abc", "bc"))
+        self.assertFalse(mi.iequals("aaa", "aaaa"))
+
+    def test_empty_iterables(self):
+        self.assertTrue(mi.iequals([], ""))
+
+    def test_none_is_not_a_sentinel(self):
+        # See https://stackoverflow.com/a/900444
+        self.assertFalse(mi.iequals([1, 2], [1, 2, None]))
+        self.assertFalse(mi.iequals([1, 2], [None, 1, 2]))
+
+    def test_not_identical_but_equal(self):
+        self.assertTrue([1, True], [1.0, complex(1, 0)])