1
$\begingroup$

I have a list like:

list = {{3, 1, 4}, {1, 5}, {9, 2, 6}, {5, 3}, {5, 8, 9, 7}, {9, 3},
       {2}, {3, 8, 4, 6, 2, 6}, {4, 3, 3}, {8}, {3, 2, 7, 9, 5, 0}}

Now I can select all nested lists of length 6:

Select[list, Length[#] == 6 &]

which gives the following result:

{{3, 8, 4, 6, 2, 6}, {3, 2, 7, 9, 5, 0}}

Now I want to create lists like this for lengths 1, 2, ... maximum length. I understand I'll need

Sort[DeleteDuplicates[Length /@ list]]

which gives

{1, 2, 3, 4, 6}

How do I run over this list with the Select function above?

$\endgroup$
3
  • 1
    $\begingroup$ Does GatherBy[list,Length] do what you want? $\endgroup$
    – N.J.Evans
    Nov 1, 2016 at 13:56
  • $\begingroup$ @n.j.evans - More or less. I want each of the sublists as separate lists, but I think I can handle that. Thanks. $\endgroup$
    – stevenvh
    Nov 1, 2016 at 13:59
  • $\begingroup$ Table[Set[x[Last@Dimensions@i], i], {i, GatherBy[list, Length]}] will gather the lists then set them to the values of x[len], where x[1] is the length 1 lists, and x[2] all the length 2 lists etc. $\endgroup$
    – N.J.Evans
    Nov 1, 2016 at 14:14

4 Answers 4

4
$\begingroup$

GatherBy as N.J.Evans suggested:

list = {{3, 1, 4}, {1, 5}, {9, 2, 6}, {5, 3}, {5, 8, 9, 7}, {9, 3}, {2}, {3, 8, 4, 
    6, 2, 6}, {4, 3, 3}, {8}, {3, 2, 7, 9, 5, 0}};

GatherBy[Sort @ list, Length]
{{{2}, {8}},
 {{1, 5}, {5, 3}, {9, 3}},
 {{3, 1, 4}, {4, 3, 3}, {9, 2, 6}},
 {{5, 8, 9, 7}},
 {{3, 2, 7, 9, 5, 0}, {3, 8, 4, 6, 2, 6}}}

Or with GroupBy for explicit length values:

KeySort @ GroupBy[list, Length]
<|1 -> {{2}, {8}},
  2 -> {{1, 5}, {5, 3}, {9, 3}}, 
  3 -> {{3, 1, 4}, {9, 2, 6}, {4, 3, 3}},
  4 -> {{5, 8, 9, 7}}, 
  6 -> {{3, 8, 4, 6, 2, 6}, {3, 2, 7, 9, 5, 0}}|>
$\endgroup$
2
  • $\begingroup$ Or SplitBy[Sort @ list, Length] $\endgroup$
    – corey979
    Nov 1, 2016 at 16:49
  • $\begingroup$ @corey979 Yes, but oddly SplitBy usually seems to be slower than GatherBy so I tend to default to the latter. $\endgroup$
    – Mr.Wizard
    Nov 2, 2016 at 2:07
2
$\begingroup$

If performance is important, you can use Sow and Reap to define two functions: ListsofLength and Lengths. I define them as such:

ListsOfLength[list_, length_] := 
  If[ListQ[#], #, {}] &@(length /. Lengths[list]);
Lengths[list_] := Lengths[list] = Last@Reap[Sow[#, Length[#]] & /@ list;, _, Rule];

Here you call ListOfLengths with your list and the desired lengths as input. It calls Lengths which is the expensive function that uses Sow and tags all sublists with their respective lengths. Reap can then extract the sublists, grouping together the different tags (=lengths) and supplying them as a rule for easy use. Since Lengths is memoized it's only expensive the first time you call ListOfLengths.

For long lists it's about four times faster, see:

list = Table[RandomInteger[{0, 10}, RandomInteger[{1, 5}]], 1000000];
GatherBy[Sort@list, Length][[2]]; // AbsoluteTiming
(*returns 3.16s*)
ListsOfLength[list, 2]; // AbsoluteTiming
(*returns 0.81s*)
$\endgroup$
2
  • $\begingroup$ Pardon me, but I don't understand the point of this. This only returns a single length rather than all lengths as the OP requested. For a single length of two with your example data Select[list, Length@# == 2 &] is faster than ListsOfLength[list, 2], and Pick[list, Length /@ list, 2] is faster still. $\endgroup$
    – Mr.Wizard
    Nov 2, 2016 at 2:11
  • $\begingroup$ I imagined a usecase where Op wants to access some list of sublists individually which he can by using ListsOfLength. So if at one point he's wants all lists of length '5' he calculates the expensive function Lengths[list] (which is a faster version of your GroupBy answer) once and if later he wants lists of length 10 it'll basically be free. Ultimately ListsOfLength is just a fancy wrapper. If that doesn't make sense feel free to downvote and I might delete the answer if it doesn't help. $\endgroup$
    – AndreasP
    Nov 2, 2016 at 8:25
1
$\begingroup$

Using Function:

Function[{x}, Select[list, Length[#] == x &]] /@ 
 Sort[DeleteDuplicates[Length /@ list]]
$\endgroup$
1
$\begingroup$

Just to illustrate GroupBy and Reap/Sow:

gb = GroupBy[list, Length]
sw = Last@Reap[Sow[#, Length@#] & /@ list, _, Rule]
fg[n_?(MemberQ[Keys[gb], #] &)] := n /. gb
fg[n_] := "Missing"
fs[n_?(MemberQ[Keys[sw], #] &)] := n /. gb
fs[n_] := "Missing"

So,

With[{rg = Range[6]}, 
 Grid[Transpose[{rg, fg /@ rg, fs /@ rg}], Frame -> All]]

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.