2

I'm looking for a one liner to convert

[[1], [1, 1], [1, 1, 1], [1], [1], [1], [1, 1, 1, 1]]

to

[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1, 1]]

where the algorithm combines the lists up to a certain threshold length.

I currently have this

batched = []
batch = []
for l in lists:
    batch.extend(l)
    if len(batch) > threshold:
        batched.append(batch)
        batch = []
shane
  • 1,742
  • 2
  • 19
  • 36
  • Where's your attempt? Is it really just lists of 1s in your list? Are you just looking to group by a certain length? – blacksite Feb 07 '17 at 15:13
  • What if you gets `[ [1, 1], [1, 1], [1, 1], [1, 1, 1], [1, 1], [1] ]` – Grijesh Chauhan Feb 07 '17 at 15:14
  • @not_a_robot no I don't have just 1's and yes I do want to group by a certain length – shane Feb 07 '17 at 15:16
  • 2
    that could have been a good question. – Jean-François Fabre Feb 07 '17 at 15:16
  • 1
    What is the question? You don't seem to have any issues w/ your attempt. – Scott Hunter Feb 07 '17 at 15:16
  • @ScottHunter I am looking for a *one liner*, its in the first sentence of the question – shane Feb 07 '17 at 15:17
  • I think the question is: what's the one-liner to do that. Considering that there's accumulation and global list length testing, it's not trivial. – Jean-François Fabre Feb 07 '17 at 15:17
  • Why does it need to be a one-liner? Its clear from the logic in your current approach that a one liner would be hard to read/maintain – Sayse Feb 07 '17 at 15:17
  • @Sayse: hard to write in the first place :) – Jean-François Fabre Feb 07 '17 at 15:17
  • @Jean-FrançoisFabre - I'm sure its doable, but not something worth getting a headache over right before I head home for the day! :) – Sayse Feb 07 '17 at 15:20
  • 1
    @Jean-FrançoisFabre challenge accepted, though it's already looking ugly – roganjosh Feb 07 '17 at 15:20
  • Is there any grouping logic, though? You say you have lists of other values, too (e.g. `[2, 2]`); how should these be combined? Is it okay to include 1s and 2s together? – blacksite Feb 07 '17 at 15:20
  • 2
    I'm not sure why this question is being downvoted, it seems fairly common for questions regarding python one liners to be asked, here is an example http://stackoverflow.com/questions/3508766/python-one-liner – shane Feb 07 '17 at 15:20
  • @not_a_robot Yes I only care about length, even if every individual element in the first list of lists were different I would still want the same *shaped* result – shane Feb 07 '17 at 15:21
  • @not_a_robot Consider `[[1], [2], [3], [4, 5], [6]] -> [[1,2,3], [4,5,6]]` – shane Feb 07 '17 at 15:22
  • @Jean-FrançoisFabre like me, the OP should really reconsider trying to do this. Not sure I can get over the final step, I can do it fine if I cheat and flatten the list first. If this is Python 2 then perhaps I can get it done with `flatten` but I think that was removed in Python 3. – roganjosh Feb 07 '17 at 15:28
  • @roganjosh I am trying to do it, and have been for about 20 min, I also was able to flatten the list and group it but I figured this was an interesting problem I came about for the community – shane Feb 07 '17 at 15:30
  • `[([1]*total)[i:i + 3] for i in range(0, total, 3)]` was the closest I came (where total is `sum(len(i) for i in input)` but theres a reason this isn't easy to do, and thats because you shouldn't be doing it (it outputs `[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1]]` if you were curious) – Sayse Feb 07 '17 at 15:32
  • @Jean-FrançoisFabre so close now, something wonky with the numbering (when I threw a `2` in) `[[[item for sublist in a for item in sublist][x*y] for x in range(1,4)] for y in range(0, (len([item for sublist in a for item in sublist])/3))]`. I thought it might be conflicting variable names but that doesn't seem to have fixed it :( – roganjosh Feb 07 '17 at 15:37
  • 1
    Also, @shane - It is being downvoted because it doesn't show any research effort into addressing the actual question (and its also unlikely to be useful) – Sayse Feb 07 '17 at 15:39

5 Answers5

5

There's nothing wrong with your original implementation but if you insist oneliner here's one ugly option:

from itertools import accumulate, chain, groupby

THRESHOLD = 3
l = [[1], [1, 1], [1, 1, 1], [1], [1], [1], [1, 1, 1, 1]]
res = [[y for x in g for y in x[1]]
       for k, g in groupby(zip(chain([0], accumulate(len(x) for x in l)), l),
                           lambda x: x[0] // THRESHOLD)]
print(res)

Output:

[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1, 1]]

The idea is to generate list of (item count so far, sublist) tuples and group them by dividing count by THRESHOLD.

>>> temp = list(zip(chain([0], accumulate(len(x) for x in l)), l))
>>> temp
[(0, [1]), (1, [1, 1]), (3, [1, 1, 1]), (6, [1]), (7, [1]), (8, [1]), (9, [1, 1, 1, 1])]
>>> groups = [list(g) for k, g in groupby(temp, lambda x: x[0] // THRESHOLD)]
>>> groups
[[(0, [1]), (1, [1, 1])], [(3, [1, 1, 1])], [(6, [1]), (7, [1]), (8, [1])], [(9, [1, 1, 1, 1])]]
>>> [[y for x in g for y in x[1]] for g in groups]
[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1, 1]]
niemmi
  • 17,113
  • 7
  • 35
  • 42
4

Cracked it but only because I'm stubborn. It's really ugly and inefficient though and maybe there's a cleaner way but, even if there is, it's not worth it.

a = [[1], [1, 1], [1, 1, 1], [2], [1], [1], [1, 1, 1, 1]]

b = [[[item for sublist in a for item in sublist][x+(y*3)] for x in range(1,4)] for y in range(0, (len([i for j in a for i in j])/3))]

EDIT: Because I was testing this in Python 2.7 I missed the fact that division works differently in Python 3. Thanks @nexus66 for pointing out a modification (which just makes it even longer!).

c = [[[item for sublist in a for item in sublist][x+(y*3)] for x in range(1,4)] for y in range(0, int((len([i for j in a for i in j])/3)))]
roganjosh
  • 12,594
  • 4
  • 29
  • 46
3

This may not be ideal, but it would be my attempt. The idea is to chain your lists together with itertools.chain , then pull from the chain with itertools.islice and append a new list until you cannot any longer. It admittedly isn't a one liner.

from itertools import chain, islice
def grouper(n, li):
    it = chain(*li)
    out_l = []
    while True:
        chunk = list(islice(it, n))
        if len(chunk) < n:
            if chunk:
                out_l[-1] += chunk
            return out_l
        out_l.append(chunk)

Demo

In[238]: orig = [[1], [1, 1], [1, 1, 1], [1], [1], [1], [1, 1, 1, 1]]
In[239]: grouper(3, orig)
Out[239]: [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1, 1]]
In[240]: grouper(4, orig)
Out[240]: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1, 1]]
In[241]: grouper(5, orig)
Out[241]: [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1]]
Out[242]: grouper(1, orig)
Out[242]: [[1], [1], [1], [1], [1], [1], [1], [1], [1], [1], [1], [1], [1]]
miradulo
  • 28,857
  • 6
  • 80
  • 93
0

This is an ugly one-liner... It's not exactly what you asked for (pretty close), but maybe it'll give you some idea of how to approach it...

arr = [[1], [1, 1], [1, 1, 1], [1], [1], [1], [1, 1, 1, 1]]

threshold = 4

[[x for y in arr for x in y][i:i+threshold] 
     for i, _ in enumerate([x for y in arr for x in y])
       if i % threshold == 0]

Out[31]:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1]]
blacksite
  • 12,086
  • 10
  • 64
  • 109
0

I appreciate all the help, and I learned that a one liner is not be best for this problem as it is ugly and unreadable and probably inefficient. That being said I did come up with this which seems to work from a different slightly cleaner approach.

>>> from functools import reduce
>>> l = [[1], [2, 3], [4, 5, 6], [7], [8], [9], [10, 11, 12, 13]]
>>> t = 3
>>> b = reduce(lambda p, n: p[:-1] + [p[-1] + n] if len(p[-1]) + len(n) <= t or not p[-1] else p + [n], l, [[]])
>>> b
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12, 13]]
shane
  • 1,742
  • 2
  • 19
  • 36