github.com/lingyao2333/mo-zero@v1.4.1/core/fx/stream.go (about)

     1  package fx
     2  
     3  import (
     4  	"sort"
     5  	"sync"
     6  
     7  	"github.com/lingyao2333/mo-zero/core/collection"
     8  	"github.com/lingyao2333/mo-zero/core/lang"
     9  	"github.com/lingyao2333/mo-zero/core/threading"
    10  )
    11  
    12  const (
    13  	defaultWorkers = 16
    14  	minWorkers     = 1
    15  )
    16  
    17  type (
    18  	rxOptions struct {
    19  		unlimitedWorkers bool
    20  		workers          int
    21  	}
    22  
    23  	// FilterFunc defines the method to filter a Stream.
    24  	FilterFunc func(item interface{}) bool
    25  	// ForAllFunc defines the method to handle all elements in a Stream.
    26  	ForAllFunc func(pipe <-chan interface{})
    27  	// ForEachFunc defines the method to handle each element in a Stream.
    28  	ForEachFunc func(item interface{})
    29  	// GenerateFunc defines the method to send elements into a Stream.
    30  	GenerateFunc func(source chan<- interface{})
    31  	// KeyFunc defines the method to generate keys for the elements in a Stream.
    32  	KeyFunc func(item interface{}) interface{}
    33  	// LessFunc defines the method to compare the elements in a Stream.
    34  	LessFunc func(a, b interface{}) bool
    35  	// MapFunc defines the method to map each element to another object in a Stream.
    36  	MapFunc func(item interface{}) interface{}
    37  	// Option defines the method to customize a Stream.
    38  	Option func(opts *rxOptions)
    39  	// ParallelFunc defines the method to handle elements parallelly.
    40  	ParallelFunc func(item interface{})
    41  	// ReduceFunc defines the method to reduce all the elements in a Stream.
    42  	ReduceFunc func(pipe <-chan interface{}) (interface{}, error)
    43  	// WalkFunc defines the method to walk through all the elements in a Stream.
    44  	WalkFunc func(item interface{}, pipe chan<- interface{})
    45  
    46  	// A Stream is a stream that can be used to do stream processing.
    47  	Stream struct {
    48  		source <-chan interface{}
    49  	}
    50  )
    51  
    52  // Concat returns a concatenated Stream.
    53  func Concat(s Stream, others ...Stream) Stream {
    54  	return s.Concat(others...)
    55  }
    56  
    57  // From constructs a Stream from the given GenerateFunc.
    58  func From(generate GenerateFunc) Stream {
    59  	source := make(chan interface{})
    60  
    61  	threading.GoSafe(func() {
    62  		defer close(source)
    63  		generate(source)
    64  	})
    65  
    66  	return Range(source)
    67  }
    68  
    69  // Just converts the given arbitrary items to a Stream.
    70  func Just(items ...interface{}) Stream {
    71  	source := make(chan interface{}, len(items))
    72  	for _, item := range items {
    73  		source <- item
    74  	}
    75  	close(source)
    76  
    77  	return Range(source)
    78  }
    79  
    80  // Range converts the given channel to a Stream.
    81  func Range(source <-chan interface{}) Stream {
    82  	return Stream{
    83  		source: source,
    84  	}
    85  }
    86  
    87  // AllMach returns whether all elements of this stream match the provided predicate.
    88  // May not evaluate the predicate on all elements if not necessary for determining the result.
    89  // If the stream is empty then true is returned and the predicate is not evaluated.
    90  func (s Stream) AllMach(predicate func(item interface{}) bool) bool {
    91  	for item := range s.source {
    92  		if !predicate(item) {
    93  			// make sure the former goroutine not block, and current func returns fast.
    94  			go drain(s.source)
    95  			return false
    96  		}
    97  	}
    98  
    99  	return true
   100  }
   101  
   102  // AnyMach returns whether any elements of this stream match the provided predicate.
   103  // May not evaluate the predicate on all elements if not necessary for determining the result.
   104  // If the stream is empty then false is returned and the predicate is not evaluated.
   105  func (s Stream) AnyMach(predicate func(item interface{}) bool) bool {
   106  	for item := range s.source {
   107  		if predicate(item) {
   108  			// make sure the former goroutine not block, and current func returns fast.
   109  			go drain(s.source)
   110  			return true
   111  		}
   112  	}
   113  
   114  	return false
   115  }
   116  
   117  // Buffer buffers the items into a queue with size n.
   118  // It can balance the producer and the consumer if their processing throughput don't match.
   119  func (s Stream) Buffer(n int) Stream {
   120  	if n < 0 {
   121  		n = 0
   122  	}
   123  
   124  	source := make(chan interface{}, n)
   125  	go func() {
   126  		for item := range s.source {
   127  			source <- item
   128  		}
   129  		close(source)
   130  	}()
   131  
   132  	return Range(source)
   133  }
   134  
   135  // Concat returns a Stream that concatenated other streams
   136  func (s Stream) Concat(others ...Stream) Stream {
   137  	source := make(chan interface{})
   138  
   139  	go func() {
   140  		group := threading.NewRoutineGroup()
   141  		group.Run(func() {
   142  			for item := range s.source {
   143  				source <- item
   144  			}
   145  		})
   146  
   147  		for _, each := range others {
   148  			each := each
   149  			group.Run(func() {
   150  				for item := range each.source {
   151  					source <- item
   152  				}
   153  			})
   154  		}
   155  
   156  		group.Wait()
   157  		close(source)
   158  	}()
   159  
   160  	return Range(source)
   161  }
   162  
   163  // Count counts the number of elements in the result.
   164  func (s Stream) Count() (count int) {
   165  	for range s.source {
   166  		count++
   167  	}
   168  	return
   169  }
   170  
   171  // Distinct removes the duplicated items base on the given KeyFunc.
   172  func (s Stream) Distinct(fn KeyFunc) Stream {
   173  	source := make(chan interface{})
   174  
   175  	threading.GoSafe(func() {
   176  		defer close(source)
   177  
   178  		keys := make(map[interface{}]lang.PlaceholderType)
   179  		for item := range s.source {
   180  			key := fn(item)
   181  			if _, ok := keys[key]; !ok {
   182  				source <- item
   183  				keys[key] = lang.Placeholder
   184  			}
   185  		}
   186  	})
   187  
   188  	return Range(source)
   189  }
   190  
   191  // Done waits all upstreaming operations to be done.
   192  func (s Stream) Done() {
   193  	drain(s.source)
   194  }
   195  
   196  // Filter filters the items by the given FilterFunc.
   197  func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream {
   198  	return s.Walk(func(item interface{}, pipe chan<- interface{}) {
   199  		if fn(item) {
   200  			pipe <- item
   201  		}
   202  	}, opts...)
   203  }
   204  
   205  // First returns the first item, nil if no items.
   206  func (s Stream) First() interface{} {
   207  	for item := range s.source {
   208  		// make sure the former goroutine not block, and current func returns fast.
   209  		go drain(s.source)
   210  		return item
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  // ForAll handles the streaming elements from the source and no later streams.
   217  func (s Stream) ForAll(fn ForAllFunc) {
   218  	fn(s.source)
   219  	// avoid goroutine leak on fn not consuming all items.
   220  	go drain(s.source)
   221  }
   222  
   223  // ForEach seals the Stream with the ForEachFunc on each item, no successive operations.
   224  func (s Stream) ForEach(fn ForEachFunc) {
   225  	for item := range s.source {
   226  		fn(item)
   227  	}
   228  }
   229  
   230  // Group groups the elements into different groups based on their keys.
   231  func (s Stream) Group(fn KeyFunc) Stream {
   232  	groups := make(map[interface{}][]interface{})
   233  	for item := range s.source {
   234  		key := fn(item)
   235  		groups[key] = append(groups[key], item)
   236  	}
   237  
   238  	source := make(chan interface{})
   239  	go func() {
   240  		for _, group := range groups {
   241  			source <- group
   242  		}
   243  		close(source)
   244  	}()
   245  
   246  	return Range(source)
   247  }
   248  
   249  // Head returns the first n elements in p.
   250  func (s Stream) Head(n int64) Stream {
   251  	if n < 1 {
   252  		panic("n must be greater than 0")
   253  	}
   254  
   255  	source := make(chan interface{})
   256  
   257  	go func() {
   258  		for item := range s.source {
   259  			n--
   260  			if n >= 0 {
   261  				source <- item
   262  			}
   263  			if n == 0 {
   264  				// let successive method go ASAP even we have more items to skip
   265  				close(source)
   266  				// why we don't just break the loop, and drain to consume all items.
   267  				// because if breaks, this former goroutine will block forever,
   268  				// which will cause goroutine leak.
   269  				drain(s.source)
   270  			}
   271  		}
   272  		// not enough items in s.source, but we need to let successive method to go ASAP.
   273  		if n > 0 {
   274  			close(source)
   275  		}
   276  	}()
   277  
   278  	return Range(source)
   279  }
   280  
   281  // Last returns the last item, or nil if no items.
   282  func (s Stream) Last() (item interface{}) {
   283  	for item = range s.source {
   284  	}
   285  	return
   286  }
   287  
   288  // Map converts each item to another corresponding item, which means it's a 1:1 model.
   289  func (s Stream) Map(fn MapFunc, opts ...Option) Stream {
   290  	return s.Walk(func(item interface{}, pipe chan<- interface{}) {
   291  		pipe <- fn(item)
   292  	}, opts...)
   293  }
   294  
   295  // Merge merges all the items into a slice and generates a new stream.
   296  func (s Stream) Merge() Stream {
   297  	var items []interface{}
   298  	for item := range s.source {
   299  		items = append(items, item)
   300  	}
   301  
   302  	source := make(chan interface{}, 1)
   303  	source <- items
   304  	close(source)
   305  
   306  	return Range(source)
   307  }
   308  
   309  // NoneMatch returns whether all elements of this stream don't match the provided predicate.
   310  // May not evaluate the predicate on all elements if not necessary for determining the result.
   311  // If the stream is empty then true is returned and the predicate is not evaluated.
   312  func (s Stream) NoneMatch(predicate func(item interface{}) bool) bool {
   313  	for item := range s.source {
   314  		if predicate(item) {
   315  			// make sure the former goroutine not block, and current func returns fast.
   316  			go drain(s.source)
   317  			return false
   318  		}
   319  	}
   320  
   321  	return true
   322  }
   323  
   324  // Parallel applies the given ParallelFunc to each item concurrently with given number of workers.
   325  func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
   326  	s.Walk(func(item interface{}, pipe chan<- interface{}) {
   327  		fn(item)
   328  	}, opts...).Done()
   329  }
   330  
   331  // Reduce is an utility method to let the caller deal with the underlying channel.
   332  func (s Stream) Reduce(fn ReduceFunc) (interface{}, error) {
   333  	return fn(s.source)
   334  }
   335  
   336  // Reverse reverses the elements in the stream.
   337  func (s Stream) Reverse() Stream {
   338  	var items []interface{}
   339  	for item := range s.source {
   340  		items = append(items, item)
   341  	}
   342  	// reverse, official method
   343  	for i := len(items)/2 - 1; i >= 0; i-- {
   344  		opp := len(items) - 1 - i
   345  		items[i], items[opp] = items[opp], items[i]
   346  	}
   347  
   348  	return Just(items...)
   349  }
   350  
   351  // Skip returns a Stream that skips size elements.
   352  func (s Stream) Skip(n int64) Stream {
   353  	if n < 0 {
   354  		panic("n must not be negative")
   355  	}
   356  	if n == 0 {
   357  		return s
   358  	}
   359  
   360  	source := make(chan interface{})
   361  
   362  	go func() {
   363  		for item := range s.source {
   364  			n--
   365  			if n >= 0 {
   366  				continue
   367  			} else {
   368  				source <- item
   369  			}
   370  		}
   371  		close(source)
   372  	}()
   373  
   374  	return Range(source)
   375  }
   376  
   377  // Sort sorts the items from the underlying source.
   378  func (s Stream) Sort(less LessFunc) Stream {
   379  	var items []interface{}
   380  	for item := range s.source {
   381  		items = append(items, item)
   382  	}
   383  	sort.Slice(items, func(i, j int) bool {
   384  		return less(items[i], items[j])
   385  	})
   386  
   387  	return Just(items...)
   388  }
   389  
   390  // Split splits the elements into chunk with size up to n,
   391  // might be less than n on tailing elements.
   392  func (s Stream) Split(n int) Stream {
   393  	if n < 1 {
   394  		panic("n should be greater than 0")
   395  	}
   396  
   397  	source := make(chan interface{})
   398  	go func() {
   399  		var chunk []interface{}
   400  		for item := range s.source {
   401  			chunk = append(chunk, item)
   402  			if len(chunk) == n {
   403  				source <- chunk
   404  				chunk = nil
   405  			}
   406  		}
   407  		if chunk != nil {
   408  			source <- chunk
   409  		}
   410  		close(source)
   411  	}()
   412  
   413  	return Range(source)
   414  }
   415  
   416  // Tail returns the last n elements in p.
   417  func (s Stream) Tail(n int64) Stream {
   418  	if n < 1 {
   419  		panic("n should be greater than 0")
   420  	}
   421  
   422  	source := make(chan interface{})
   423  
   424  	go func() {
   425  		ring := collection.NewRing(int(n))
   426  		for item := range s.source {
   427  			ring.Add(item)
   428  		}
   429  		for _, item := range ring.Take() {
   430  			source <- item
   431  		}
   432  		close(source)
   433  	}()
   434  
   435  	return Range(source)
   436  }
   437  
   438  // Walk lets the callers handle each item, the caller may write zero, one or more items base on the given item.
   439  func (s Stream) Walk(fn WalkFunc, opts ...Option) Stream {
   440  	option := buildOptions(opts...)
   441  	if option.unlimitedWorkers {
   442  		return s.walkUnlimited(fn, option)
   443  	}
   444  
   445  	return s.walkLimited(fn, option)
   446  }
   447  
   448  func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
   449  	pipe := make(chan interface{}, option.workers)
   450  
   451  	go func() {
   452  		var wg sync.WaitGroup
   453  		pool := make(chan lang.PlaceholderType, option.workers)
   454  
   455  		for item := range s.source {
   456  			// important, used in another goroutine
   457  			val := item
   458  			pool <- lang.Placeholder
   459  			wg.Add(1)
   460  
   461  			// better to safely run caller defined method
   462  			threading.GoSafe(func() {
   463  				defer func() {
   464  					wg.Done()
   465  					<-pool
   466  				}()
   467  
   468  				fn(val, pipe)
   469  			})
   470  		}
   471  
   472  		wg.Wait()
   473  		close(pipe)
   474  	}()
   475  
   476  	return Range(pipe)
   477  }
   478  
   479  func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
   480  	pipe := make(chan interface{}, option.workers)
   481  
   482  	go func() {
   483  		var wg sync.WaitGroup
   484  
   485  		for item := range s.source {
   486  			// important, used in another goroutine
   487  			val := item
   488  			wg.Add(1)
   489  			// better to safely run caller defined method
   490  			threading.GoSafe(func() {
   491  				defer wg.Done()
   492  				fn(val, pipe)
   493  			})
   494  		}
   495  
   496  		wg.Wait()
   497  		close(pipe)
   498  	}()
   499  
   500  	return Range(pipe)
   501  }
   502  
   503  // UnlimitedWorkers lets the caller use as many workers as the tasks.
   504  func UnlimitedWorkers() Option {
   505  	return func(opts *rxOptions) {
   506  		opts.unlimitedWorkers = true
   507  	}
   508  }
   509  
   510  // WithWorkers lets the caller customize the concurrent workers.
   511  func WithWorkers(workers int) Option {
   512  	return func(opts *rxOptions) {
   513  		if workers < minWorkers {
   514  			opts.workers = minWorkers
   515  		} else {
   516  			opts.workers = workers
   517  		}
   518  	}
   519  }
   520  
   521  // buildOptions returns a rxOptions with given customizations.
   522  func buildOptions(opts ...Option) *rxOptions {
   523  	options := newOptions()
   524  	for _, opt := range opts {
   525  		opt(options)
   526  	}
   527  
   528  	return options
   529  }
   530  
   531  // drain drains the given channel.
   532  func drain(channel <-chan interface{}) {
   533  	for range channel {
   534  	}
   535  }
   536  
   537  // newOptions returns a default rxOptions.
   538  func newOptions() *rxOptions {
   539  	return &rxOptions{
   540  		workers: defaultWorkers,
   541  	}
   542  }