Split collection into sub collections in Groovy - groovy

I have an array containing an unknown number of items that I would like to split into separate arrays so that each separate array contains no more than 4 items. What is the best way to do this in Groovy? Thanks!

We had this here: How to split a list into equal sized lists in Groovy?
I came up with this:
List.metaClass.partition = { size ->
def rslt = delegate.inject( [ [] ] ) { ret, elem ->
( ret.last() << elem ).size() >= size ? ret << [] : ret
}
!rslt.last() ? rslt[ 0..-2 ] : rslt
}
def list = [1, 2, 3, 4, 5, 6].partition( 4 )
Which should give you:
[ [ 1, 2, 3, 4 ], [ 5, 6 ] ]
Update!
With Groovy 1.8.6+ you can use list.collate( 4 ) to get the same result

Answer by tim_yates is cool, but it throws java.lang.ArrayIndexOutOfBoundsException on empty lists (for example: [].partition(4)). This can be fixed in this way:
List.metaClass.partition = {size ->
if (!delegate)
return []
def rslt = delegate.inject([[]]) {ret, elem ->
(ret.last() << elem).size() >= size ? (ret << []) : ret
}
!rslt.last() ? rslt[0..-2] : rslt
}
assert [].partition(4) == []
assert [1, 2, 3, 4, 5, 6].partition(4) == [[1, 2, 3, 4], [5, 6]]

Since Groovy 1.8.6, you can use collate:
def letters = 'a'..'g'
assert letters.collate(3) == [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]
def letters = 'a'..'g'
assert letters.collate(3) == [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]
Credit to Mrhaki's Groovy goodness series:
http://mrhaki.blogspot.com.au/2012/04/groovy-goodness-collate-list-into-sub.html

Related

My code appends the same list 10 times instead of appending 10 randomized lists?

At the moment, the while loop creates one list and appends it 10 times to the results list.
What do I need to change in the def dice() so that the while loop creates 10 different lists and appends them to the results list?
from random import choice
list = [1, 2, 3, 4, "a", "b", "c", "d"]
winner = []
ticket = [1, 2]
results = []
class Die:
def __init__(self, a_list):
self.a_list = a_list
def dice(self):
while len(results) < 10:
results.append(winner)
while len(winner) < 2:
die = choice(self.a_list)
winner.append(die)
print(results)
my_dice = Die(list)
my_dice.dice()
I wrote a different approach using zip, if this works for you:
import random
lis = [1, 2, 3, 4, "a", "b", "c", "d"]
combo_1 = []
ticket = [1, 2]
combo_2 = []
class Die:
def __init__(self, a_list):
self.a_list = a_list
def dice(self):
while len(results) < 10:
combo_2.append(random.choice(self.a_list))
combo_1.append(random.choice(self.a_list))
dice = [list(i) for i in zip(combo_1, combo_2)]
print(dice)
my_dice = Die(lis)
my_dice.dice()
Output:
[['b', 'd'], [3, 'b'], ['c', 'b'], ['d', 2], ['a', 4], ['d', 2], ['c', 'c'], ['b', 'b'], [3, 'b'], [3, 3]]
Currently you never reset winner to be empty, so it just keeps growing in size. But I think you're overcomplicating this.
You don't need a while loop if it's of fixed size - use for loop instead
If the sub-list only has two values, you don't need a loop at all
You're losing some of the benefit of having a class by having global variables
And syntactically list is a bad name for a variable because it shadows the builtin, and I would avoid having a method that prints at the end without returning anything.
How about this?
import random
class Die:
def __init__(self, dice_options):
self.dice_options = dice_options
def dice(self):
return [
[random.choice(self.dice_options), random.choice(self.dice_options)]
for _ in range(10)
]
my_dice = Die([1, 2, 3, 4, "a", "b", "c", "d"])
print(my_dice.dice())
# [[3, 'd'], ['d', 2], ['b', 4], [1, 'd'], [4, 'c'], ['b', 4], [2, 1], ['a', 'd'], ['a', 'd'], ['b', 4]]

Python: from list group by elements after a specific trigger element [duplicate]

This question already has answers here:
Python3: How can I split a list based on condition?
(2 answers)
Closed last year.
I have a list like
a=['a',2,'[abcd]','bb',4,5,'kk','[efgh]',6,7,'no','[ijkl]',4,5,'lo']
So here we want group by after each '[]'
so the expected one would be
[['a',2],{'abcd': ['bb',4,5,'kk']},{'efgh': [6,7,'no']},{'ijkl': [4,5,'lo']}]
Any help would be appriciable
You can use groupby:
from itertools import groupby
a=['a',2,'[abcd]','bb',4,5,'kk','[efgh]',6,7,'no','[ijkl]',4,5,'lo']
def group_by_tag(li):
def tag_counter(x):
if isinstance(x, str) and x.startswith('[') and x.endswith(']'):
tag_counter.cnt += 1
return tag_counter.cnt
tag_counter.cnt = 0
return groupby(li, key=tag_counter)
Which you can use to make a list of tuples for each segment partitioned by the [tag]:
>>> x=[(k,list(l)) for k, l in group_by_tag(a)]
>>> x
[(0, ['a', 2]), (1, ['[abcd]', 'bb', 4, 5, 'kk']), (2, ['[efgh]', 6, 7, 'no']), (3, ['[ijkl]', 4, 5, 'lo'])]
And then create your desired mixed-type list from that:
>>> [v if k==0 else {v[0].strip('[]'):v[1:]} for k,v in x]
[['a', 2], {'abcd': ['bb', 4, 5, 'kk']}, {'efgh': [6, 7, 'no']}, {'ijkl': [4, 5, 'lo']}]
But consider that it is usually better to have a list of the same type of object to make processing that list easier.
If you want that, you could do:
>>> [{'no_tag':v} if k==0 else {v[0].strip('[]'):v[1:]} for k,v in x]
[{'no_tag': ['a', 2]}, {'abcd': ['bb', 4, 5, 'kk']}, {'efgh': [6, 7, 'no']}, {'ijkl': [4, 5, 'lo']}]
By "=>" I assume you want a dictionary if there is a preceding keyword and key be the word enclosed within the square brackets (if that's not what you intended feel free to comment and I'll edit this post)
import re
def sort(iterable):
result = {}
governing_match = None
for each in list(iterable):
match = re.findall(r"\[.{1,}\]", str(each))
if len(match) > 0:
governing_match = match[0][1:-1]
result[governing_match] = []
continue
result[governing_match].append(each)
return result
a=['[foo]', 'a' , 2 ,'[abcd]','bb',4,5,'kk','[efgh]',6,7,'no','[ijkl]',4,5,'lo']
for (k, v) in sort(a).items():
print(f"{k} : {v}")
Result :
foo : ['a', 2]
abcd : ['bb', 4, 5, 'kk']
efgh : [6, 7, 'no']
ijkl : [4, 5, 'lo']
The limitation of this is that every sequence should start with an element that is enclosed within square brackets.
You can use pairwise for that:
from itertools import pairwise
a=['a',2,'[abcd]','bb',4,5,'kk','[efgh]',6,7,'no','[ijkl]',4,5,'lo']
bracket_indices = [i for i, x in enumerate(a) if isinstance(x, str)
and x.startswith('[') and x.endswith(']')]
bracket_indices.append(len(a))
output = [a[:bracket_indices[0]]] # First item is special cased
output.extend({a[i][1:-1]: a[i+1:j]} for i, j in pairwise(bracket_indices))

How do I order double list of elements of this type: [[1,2,3], [a,b,c]]?

I have a double list of this type: dl = [[13, 22, 41], ['c', 'b', 'a']], in which, each element dl[0][i] belongs a value in dl[1][i] (with the same index). How can I sort my list using dl[0] values as my order criteria, maintainning linked both sublists? Sublist are kind of 'linked data', so the previous dl[0][i] and dl[1][i] values must match their index after sorting the parent entire list, using as sorting criteria, the first sublist values
I expect something like:
input: dl = [ [14,22,7,17], ['K', 'M', 'F','A'] ]
output: dl = [ [7, 14, 17, 22], ['F', 'K', 'A', 'M'] ]
This was way too much fun to write. I don't doubt that this function can be greatly improved, but this is what I've gotten in a very short amount of time and should get you started.
I've included some tests just so you can verify that this does indeed do what you want.
from unittest import TestCase, main
def sort_by_first(data):
sorted_data = []
for seq in data:
zipped_to_first = zip(data[0], seq)
sorted_by_first = sorted(zipped_to_first)
unzipped_data = zip(*sorted_by_first)
sorted_data.append(list(tuple(unzipped_data)[1]))
return sorted_data
class SortByFirstTestCase(TestCase):
def test_sort(self):
output_1 = sort_by_first([[1, 3, 5, 2, 4], ['a', 'b', 'c', 'd', 'e']])
self.assertEqual(output_1, [[1, 2, 3, 4, 5], ['a', 'd', 'b', 'e', 'c']])
output_2 = sort_by_first([[9, 1, 5], [21, 22, 23], ['spam', 'foo', 'bar']])
self.assertEqual(output_2, [[1, 5, 9], [22, 23, 21], ['foo', 'bar', 'spam']])
if __name__ == '__main__':
main()
Updated for what you're looking for, selection sort but added another line to switch for the second list to match the first.
for i in range(len(dl[0])):
min_idx = i
for j in range(i+1, len(dl[0])):
if dl[0][min_idx] > dl[0][j]:
min_idx = j
dl[0][i], dl[0][min_idx] = dl[0][min_idx], dl[0][i]
dl[1][i], dl[1][min_idx] = dl[1][min_idx], dl[1][i]
You can try solving this with a for loop also:
dl = [ [3,2,1], ['c', 'b', 'a'] ]
for i in range(0,len(dl)):
dl[i].sort()
print(dl)

Merge contents of two lists alternately into one list in groovy

I want to merge the contents of two lists alternately into a new list. The length of the lists are undefined. I am using the below code to achieve this. I want to know, if there is any groovy way to achieve this without using all the conditions and loop. The objective is to shorten the code as much as possible using groovy features.
def combineList(ArrayList list1, ArrayList list2){
def list = [];
int j = k = 0;
def size = (list1.size() + list2.size());
for (int i = 0; i < size; i++) {
if(j < list1.size())
list.add(list1.get(j++));
if(k < list2.size())
list.add(list2.get(k++));
}
println list;
}
Input:
case 1:
combineList([1,2,3,4,5,6,7,8,9,0], ['a','b','c','d','e','f'])
case 2:
combineList([1,2,3,4], ['a','b','c','d','e','f'])
Output:
case 1:
[1, a, 2, b, 3, c, 4, d, 5, e, 6, f, 7, 8, 9, 0]
case 2:
[1, a, 2, b, 3, c, 4, d, e, f]
One of many ways:
List combineList(List one, List two) {
def result = [one, two].transpose()
( result += (one - result*.get(0)) ?: (two - result*.get(1)) ).flatten()
}
assert combineList([1,2,3,4], ['a','b','c','d','e','f']) == [1, 'a', 2, 'b', 3, 'c', 4, 'd', 'e', 'f']
assert combineList([1,2,3,4,5,6,7,8,9,0], ['a','b','c','d','e','f']) == [1, 'a', 2, 'b', 3, 'c', 4, 'd', 5, 'e', 6, 'f', 7, 8, 9, 0]
assert combineList([1,2,3,4], ['a','b','c','d']) == [1, 'a', 2, 'b', 3, 'c', 4, 'd']
Explanation:
Transposing the lists results a list like [[1, a], [2, b], [3, c]].
Next check if there is any residual elements left in either of the list.
This is done by checking if a minus operation results with any elements. Take all first elements from result list to check against first list parameter, similarly take second elements of result list to check against second list passed as parameter to method.
If present, then add them to the result
Finally, flatten the result
It goes also simpler and slightly more performant:
def combineList( List o, List t ){
def res = [ o, t ].transpose().flatten()
int resSize = res.size() >> 1
if( o.size() > resSize ) res += o[ resSize..-1 ]
else if( t.size() > resSize ) res += t[ resSize..-1 ]
res
}
Java and Groovy does not differ a lot , one quick and easy to read solution can be like this, for existing List l1 and l2
List list3 = new ArrayList<>();
Iterator it = l1.iterator();
Iterator it = l2.iterator();
while(itA.hasNext() || itB.hasNext())
{
if(itA.hasNext())
list3.add(itA.next());
if(itB.hasNext())
list3.add(itB.next());
}

How do I add an element to a list in Groovy?

Let's say I've got a list, like this...
def myList = ["first", 2, "third", 4.0];
How do I add (push) an element to the end of it? I come from a PHP background, and there I would just do something like $myList[] = "fifth";. What's the equivalent of that in Groovy?
From the documentation:
We can add to a list in many ways:
assert [1,2] + 3 + [4,5] + 6 == [1, 2, 3, 4, 5, 6]
assert [1,2].plus(3).plus([4,5]).plus(6) == [1, 2, 3, 4, 5, 6]
//equivalent method for +
def a= [1,2,3]; a += 4; a += [5,6]; assert a == [1,2,3,4,5,6]
assert [1, *[222, 333], 456] == [1, 222, 333, 456]
assert [ *[1,2,3] ] == [1,2,3]
assert [ 1, [2,3,[4,5],6], 7, [8,9] ].flatten() == [1, 2, 3, 4, 5, 6, 7, 8, 9]
def list= [1,2]
list.add(3) //alternative method name
list.addAll([5,4]) //alternative method name
assert list == [1,2,3,5,4]
list= [1,2]
list.add(1,3) //add 3 just before index 1
assert list == [1,3,2]
list.addAll(2,[5,4]) //add [5,4] just before index 2
assert list == [1,3,5,4,2]
list = ['a', 'b', 'z', 'e', 'u', 'v', 'g']
list[8] = 'x'
assert list == ['a', 'b', 'z', 'e', 'u', 'v', 'g', null, 'x']
You can also do:
def myNewList = myList << "fifth"

Resources