All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bloidonia.groovy.extensions.CollectionExtensionMethods.groovy Maven / Gradle / Ivy

/*
 * Copyright 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.bloidonia.groovy.extensions

import org.codehaus.groovy.runtime.DefaultGroovyMethods
import static org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport.createSimilarList

/**
 * @author Tim Yates
 * @author dwoods
 * @author Andrew Taylor
 */
class CollectionExtensionMethods {
  /**
   * Sort a list based on a collection of Closures.  If the first closure fails to differentiate the objects in the
   * list, it moves to the second, and continues until all Closures have been executed.
   *
   * @param self      The collection to sort
   * @param mutate    Should the original list be mutated
   * @param closures  The list of Closures used to sort the Collection
   * @return          A sorted collection
   */
  static Collection sort( Collection self, boolean mutate, Closure... closures ) {
    self.sort( mutate ) { a, b ->
      closures.findResult { cls -> cls( a ) <=> cls( b ) ?: null }
    }
  }

  /**
   * Take up to n items from a List.  If n is negative, take off the end of the list, else delegate to the existing
   * Groovy take method.
   *
   * @param self  The collection to take items from.
   * @param n     The number of elements to take.
   * @return      A list of elements up to n long.
   */
  static List take( List self, int n ) {
    int absn = Math.abs( n )
    if( n >= 0 || absn == self.size() ) {
      // Shouldn't be doing this, but I can't think of another way to delegate back to DGM
      DefaultGroovyMethods.take( self, absn )
    }
    else {
      createSimilarList( self, NumericExtensionMethods.clamp( absn, 0, self.size() ) ).with { List ret ->
        ret.addAll( self[ (n.clamp(-self.size(),0))..-1 ] )
        ret
      }
    }
  }

  /**
   * Select a single random element from a List
   *
   * @param self The List to select from
   * @return     A random element from the List
   */
  static  T rand( List self ) {
    rand( self, 1, true, new Random() )[0]
  }

  /**
   * Select a list of n unique items from the List
   *
   * @param self The List to select from
   * @param n    The number of items to select
   * @return     A List containing the random elements
   */
  static  List rand( List self, int n ) {
    rand( self, n, false, new Random() )
  }

  /**
   * Select a list of n items from the List
   *
   * @param self             The List to select from
   * @param n                The number of items to select
   * @param allowDuplicates  If true, the same element can be selected more than once.
   * @return                 A List containing the random elements
   */
  static  List rand( List self, int n, boolean allowDuplicates ) {
    rand( self, n, allowDuplicates, new Random() )
  }

  /**
   * Select a list of n items from the List
   *
   * @param self             The List to select from
   * @param n                The number of items to select
   * @param allowDuplicates  If true, the same element can be selected more than once.
   * @param r                An instance of Random so we can set a seed to get reproducible random results
   * @return
   */
  static  List rand( List self, int n, boolean allowDuplicates, Random r ) {
    List ret = createSimilarList( self, 0 )
    if( n <= 0 ) {
      return ret
    }
    if( !allowDuplicates && n > self.size() ) {
      throw new IllegalArgumentException( "Cannot have $n unique items from a list containing only ${self.size()}" )
    }
    (1..n).each {
      int idx = r.nextInt( self.size() )
      ret << self[ idx ]
      if( !allowDuplicates ) {
        self = [ *self.take( idx ), *self.drop( idx + 1 ) ]
      }
    }
    ret
  }

  /**
   * Select a single random element from an Iterator
   *
   * @param self The Iterator to select from
   * @return     A random element from the List
   */
  static  T rand( Iterator self ) {
    rand( self, 1, true, new Random() )[0]
  }

  /**
   * Select a list of n unique items from the Iterator
   *
   * @param self The Iterator to select from
   * @param n    The number of items to select
   * @return     A List containing the random elements
   */
  static  List rand( Iterator self, int n ) {
    rand( self, n, false, new Random() )
  }

  /**
   * Select a list of n items from the Iterator
   *
   * @param self             The Iterator to select from
   * @param n                The number of items to select
   * @param allowDuplicates  If true, the same element can be selected more than once.
   * @return                 A List containing the random elements
   */
  static  List rand( Iterator self, int n, boolean allowDuplicates ) {
    rand( self, n, allowDuplicates, new Random() )
  }

  /**
   * Select a list of n random items from the Iterator
   *
   * @param self             The Iterator to select from
   * @param n                The number of items to select
   * @param allowDuplicates  If true, the same element can be selected more than once.
   * @param r                An instance of Random so we can set a seed to get reproducible random results
   * @return                 A List containing the random elements
   */
  static  List rand( Iterator self, int n, boolean allowDuplicates, Random rnd ) {
    List result

    if (allowDuplicates) {

      int i = 2
      result = [self.next()] * n 
      while (self.hasNext()) {
        T item = self.next()
        for (int j = 0; j < n; j++) {
          if (rnd.nextInt(i) == 0) {
            result[j] = item
          }
        }
        i++
      }

    } else {

      result = self.take(n).toList()
      Collections.shuffle(result)
      int i = n + 1
      while( self.hasNext() ) {
        int j = rnd.nextInt(i)
        T item = self.next()
        if( j < n ) {
          result[j] = item
        }
        i++
      }
    }

    result
  }

  static class TransposingIterator implements Iterator {
    private int idx = 0
    private List> iterators
    private List weights
   
    TransposingIterator( List> lists, List amounts ) {
      iterators = lists*.iterator()
      weights = [amounts,0.. it ->
        [ it[ 1 ] ] * it[ 0 ]
      }.flatten()
    }
   
    private void moveIdx() {
      idx = ++idx % weights.size()
    }
   
    @Override boolean hasNext() {
      iterators*.hasNext().any()
    }
   
    @Override T next() {
      if( !hasNext() ) { throw new NoSuchElementException() }
      while( !iterators[ weights[ idx ] ].hasNext() ) { moveIdx() }
      T ret = iterators[ weights[ idx ] ].next()
      moveIdx()
      ret
    }
   
    @Override void remove() {
      throw new UnsupportedOperationException()
    }
  }

  /**
   * Return a TransposingIterator that returns an element from each list in turn.
   *
   * 
   *   def left  = [   1,   2,   3      ]
   *   def right = [ 'a', 'b', 'c', 'd' ]
   *
   *   def result = [left,right].transposedIterator().collect()
   *
   *   assert result == [ 1, 'a', 2, 'b', 3, 'c', 'd' ]
   * 
* * @param lists A List of Lists to cycle over * @return An Iterator that can be used to fetch an item from each list in turn */ static Iterator transposedIterator( List> lists ) { transposedIterator( lists, [ 1 ] * lists.size() ) } /** * Return a TransposingIterator that returns an element from each list in turn. * *
   *   def left  = [   1,   2,   3      ]
   *   def right = [ 'a', 'b', 'c', 'd' ]
   *
   *   def result = [left,right].transposedIterator( [ 1, 2 ] ).collect()
   *
   *   assert result == [ 1, 'a', 'b', 2, 'c', 'd', 3 ]
   * 
* * @param lists A List of Lists to cycle over * @param amounts The number to take from each list * @return An Iterator that can be used to fetch an item from each list in turn */ static Iterator transposedIterator( List> lists, List amounts ) { new TransposingIterator( lists, amounts ) } @groovy.transform.Immutable( knownImmutables=[ 'median' ]) public static class AverageStats { BigDecimal mean BigDecimal median BigDecimal variance BigDecimal stdDev String toString() { "AverageStats( mean:$mean, median:$median, variance:$variance, stdDev:$stdDev )" } } /** * Calculate the mean, median, variance and standard deviation of a Collection of * Numbers * *
   *   def avg = (1..10).average()
   *
   *   assert avg.mean == 5.5
   *   assert avg.median == 5.5
   *   assert avg.variance == 8.25
   *   assert String.format( '%.5g', avg.stdDev ) == '2.8723'
   * 
* * @param collection The {@code Collection} of {@code Number} elements to generate stats for * @return A populated {@code AverageStats} object */ static AverageStats average( Collection collection ) { int size = collection.size() if( size == 0 ) { return new AverageStats( mean: 0, median: 0, variance: 0, stdDev: 0 ) } V sum = collection.sum() int zsize = size - 1 BigDecimal median = size % 2 == 1 ? collection.sort( false )[ ( zsize / 2 ).toInteger() ] : collection.sort( false )[ [ Math.floor( zsize / 2 ), Math.ceil( zsize / 2.0 ) ]*.toInteger() ].sum() / 2 BigDecimal mean = sum / size BigDecimal variance = 0 for( V num : collection ) { variance += ( mean - num ) * ( mean - num ) } variance /= size new AverageStats( mean: mean, median: median, variance: variance, stdDev: Math.sqrt( variance ) ) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy