Arturo
Arturo

Reputation: 1089

Mix two lists in groovy according to a pattern in Groovy?

I have two lists lets say:

def list1 = [a,b,c,d,e...]
def list2 = [1,2,3,4,5... ]

I want them to mix in a certain pattern so that final list looks like: [a,b,c,d,1,2,e,f,g,h,3,4,i,j,k,l,5,6...]

Basically after every n elements from list1, i get m elements from list2.

EDIT: If one list runs out of elements, the items in the remaining list should simply get added to the final list.

EDIT2: Both the lists can have objects as elements.

I want to find the most efficient way to solve this problem.

Upvotes: 4

Views: 952

Answers (2)

Norberto Yoan
Norberto Yoan

Reputation: 261

I have a functional solution preserving remaining elements based on the main answer.

List mix( Map<Integer,List> amounts ) {
    def maxItems = amounts.collect { k, v -> v.size() }.max()
    amounts.collect { k, v ->
        def padding = maxItems - v.size()
        it.addAll((1..padding).collect { null })
        v.collate( k )
    }.transpose().flatten().grep { it }
}

I tested it in a simplified version to mix three lists taking one item at time. The idea is just adding padding with null objects to make all of the same length.

Upvotes: 0

tim_yates
tim_yates

Reputation: 171084

Here's one way of doing this in Groovy:

So I have a method mix which takes a map with Integer keys (the number of elements required), and Lists as values:

List mix( Map<Integer,List> amounts ) {
  amounts.collect { k, v ->
    v.collate( k )
  }.transpose().flatten()
}

Then, given:

// The letters a to z
def list1 = 'a'..'z'

// The numbers 1 to 10
def list2 = 1..10

// Call, and ask for 4 of list1 followed by 2 of list2
mix( [ 4:list1, 2:list2 ] )

That returns:

[ 'a', 'b', 'c', 'd', 1, 2,
  'e', 'f', 'g', 'h', 3, 4,
  'i', 'j', 'k', 'l', 5, 6,
  'm', 'n', 'o', 'p', 7, 8,
  'q', 'r', 's', 't', 9, 10 ]

(formatted to look better here)

As you can see, it runs out of numbers first, and when it does, the list ends. This is because transpose stops when one list runs out of elements.

EDIT:

Worked out another way with Iterators (so it's lazy and won't use up more memory than is otherwise required):

class MixingIterator<T> implements Iterator<T> {
  private int idx = 0
  private List<Iterator> iter
  private List<Integer>  amts
  
  MixingIterator( List<List> lists, List<Integer> amounts ) {
    iter = lists*.iterator()
    int i = 0
    amts = amounts.collectMany { [ i++ ] * it }
    // OR FOR GROOVY 1.7.8
    // amts = amounts.collect { [ i++ ] * it }.flatten()
  }
  
  private void moveIdx() {
    idx = ++idx % amts.size()
  }
  
  @Override boolean hasNext() {
    iter*.hasNext().any()
  }
  
  @Override T next() {
    if( !hasNext() ) { throw new NoSuchElementException() }
    while( !iter[ amts[ idx ] ].hasNext() ) { moveIdx() }
    T ret = iter[ amts[ idx ] ].next()
    moveIdx()
    ret
  }
  
  @Override void remove() {
    throw new UnsupportedOperationException()
  }
}

You call it by:

def list1 = 'a'..'z'
def list2 = 1..10

def ret = new MixingIterator( [ list1, list2 ], [ 4, 2 ] ).collect()
// OR FOR GROOVY 1.7.8
// def ret = new MixingIterator( [ list1, list2 ], [ 4, 2 ] ).collect { it }

And ret will then equal:

['a', 'b', 'c', 'd', 1, 2,
 'e', 'f', 'g', 'h', 3, 4,
 'i', 'j', 'k', 'l', 5, 6,
 'm', 'n', 'o', 'p', 7, 8,
 'q', 'r', 's', 't', 9, 10,
 'u', 'v', 'w', 'x', 'y', 'z']

Upvotes: 5

Related Questions