@@ -833,3 +833,70 @@ def __iter__(self):
833
833
834
834
def __repr__ (self ):
835
835
return 'SequenceZip({})' .format (self .sequences )
836
+
837
+
838
+ # TODO: remove Product from larray_editor.utils (it is almost identical)
839
+ class Product (object ):
840
+ """
841
+ Represents the `cartesian product` of several sequences.
842
+
843
+ This is very similar to itertools.product but only accepts sequences and acts as a sequence (it can be
844
+ indexed and has a len).
845
+
846
+ Parameters
847
+ ----------
848
+ sequences : Iterable of Sequence
849
+ Sequences on which to apply the cartesian product.
850
+
851
+ Examples
852
+ --------
853
+ >>> p = Product([['a', 'b', 'c'], [1, 2]])
854
+ >>> for i in range(len(p)):
855
+ ... print(p[i])
856
+ ('a', 1)
857
+ ('a', 2)
858
+ ('b', 1)
859
+ ('b', 2)
860
+ ('c', 1)
861
+ ('c', 2)
862
+ >>> p[1:4]
863
+ [('a', 2), ('b', 1), ('b', 2)]
864
+ >>> p[-3:]
865
+ [('b', 2), ('c', 1), ('c', 2)]
866
+ >>> list(p)
867
+ [('a', 1), ('a', 2), ('b', 1), ('b', 2), ('c', 1), ('c', 2)]
868
+ """
869
+ def __init__ (self , sequences ):
870
+ self .sequences = sequences
871
+ assert len (sequences )
872
+ shape = [len (a ) for a in self .sequences ]
873
+ self ._div_mod = [(int (np .prod (shape [i + 1 :])), shape [i ])
874
+ for i in range (len (shape ))]
875
+ self ._length = np .prod (shape )
876
+
877
+ def __len__ (self ):
878
+ return self ._length
879
+
880
+ def __getitem__ (self , key ):
881
+ if isinstance (key , (int , np .integer )):
882
+ if key >= self ._length :
883
+ raise IndexError ("index %d out of range for Product of length %d" % (key , self ._length ))
884
+ # this is similar to np.unravel_index but a tad faster for scalars
885
+ return tuple (array [key // div % mod ]
886
+ for array , (div , mod ) in zip (self .sequences , self ._div_mod ))
887
+ else :
888
+ assert isinstance (key , slice ), "key (%s) has invalid type (%s)" % (key , type (key ))
889
+ start , stop , step = key .indices (self ._length )
890
+ div_mod = self ._div_mod
891
+ arrays = self .sequences
892
+ # XXX: we probably want to return another Product object with an updated start/stop to stay
893
+ # lazy in that case too.
894
+ return [tuple (array [idx // div % mod ]
895
+ for array , (div , mod ) in zip (arrays , div_mod ))
896
+ for idx in range (start , stop , step )]
897
+
898
+ def __iter__ (self ):
899
+ return product (* self .sequences )
900
+
901
+ def __repr__ (self ):
902
+ return 'Product({})' .format (self .sequences )
0 commit comments