9

I want to select all elements that repeat 4 times in the list. I propose this code

list = {a, a, a, b, c, a, b, b, b, e};

elementsplus4 = Select[list, Count[list, #] == 4 &];

I obtain:

{a, a, a, b, a, b, b, b}

How can I obtain just {a,b}

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
developer2000
  • 165
  • 10

4 Answers4

19

You may use Tally to finish the task as follows:

Cases[Tally[list], {x_, 4} :> x]

the result will be {a,b}.

sunt05
  • 4,367
  • 23
  • 34
9

The code given by Rojo and sunt05 is almost surely the cleanest:

Cases[Tally @ list, {x_, 4} :> x]

However, here are some other possibilities:

Cases[Split @ Sort @ list, {x_, _, _, _} :> x]

Cases[Split @ Sort @ list, {Repeated[x_, {4}]} :> x]

Cases[Last @ Reap[Sow[1, list], _, {#, Tr@#2} &], {x_, 4} :> x]

Module[{c},
  c[_] = 0;
  Scan[c[#]++ &, list];
  Cases[DownValues[c], (_@_@x_ :> 4) :> x]
]

Performance

Interestingly, some of these may be significantly faster than Tally in certain cases:

list = FromCharacterCode /@ RandomInteger[15000, 100000];

Cases[Tally @ list, {x_, 4} :> x]                               // Timing // First

Cases[Last @ Reap[Sow[1, list], _, {#, Tr@#2} &], {x_, 4} :> x] // Timing // First

Module[{c},
 c[_] = 0;
 Scan[c[#]++ &, list];
 Cases[DownValues[c], (_@_@x_ :> 4) :> x]
] // Timing // First
0.546

0.109

0.2622


Since it seems to be only in the case of String objects that Sow/Reap is faster, for clarity one might write the second method as:

stringTally = Last @ Reap[Sow[1, #], _, {#, Tr@#2} &] &;

Cases[stringTally @ list, {x_, 4} :> x] // Timing // First
0.103

Addendum

The OP wrote: "I want to list all elements that appear at least four times." In light of that here are all the methods modified accordingly:

stringTally = Last @ Reap[Sow[1, #], _, {#, Tr@#2} &] &;

Cases[stringTally @ list, {x_, n_} /; n >= 4 :> x]

Cases[Split @ Sort @ list, {x_, _, _, __} :> x]

Cases[Split @ Sort @ list, {Repeated[x_, {4, ∞}]} :> x]

Cases[Last @ Reap[Sow[1, list], _, {#, Tr@#2} &], {x_, n_} /; n >= 4 :> x]

Module[{c},
  c[_] = 0;
  Scan[c[#]++ &, list];
  Cases[DownValues[c], (_@_@x_ :> n_) /; n >= 4 :> x]
]
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • How can we change Cases[Last @ Reap[Sow[1, list], _, {#, Tr@#2} &], {x_, 4} :> x] when we need to select the elements repated greater than 4 times ? – developer2000 Jan 13 '14 at 07:50
  • @developer2000 just change the 4 in {x_, 4} :> x, as you would with the Tally solution. – Mr.Wizard Jan 13 '14 at 07:52
  • I think that you did not understand me. I would select the elements that repeated 4 times, 5 times, 6 times and so on (greater than 4). I think that I can use Table[Cases[stringTally @ list, {x_, i} :> x],{i,4,n}] but is there any other pretty solution? – developer2000 Jan 13 '14 at 09:02
  • @developer2000 Oh, that was not clear. Do you mean that you wish to list the elements that appear four times separately from those that appear five times, etc., or list all elements that appear at least four times? – Mr.Wizard Jan 13 '14 at 09:10
  • I want to list all elements that appear at least four times. sorry for the bed english. – developer2000 Jan 13 '14 at 11:37
  • @developer2000 See my updated answer. – Mr.Wizard Jan 13 '14 at 11:45
  • where you are using Tr@ to sum a list of 1's wouldn't it be even faster to use Length@? – george2079 Sep 30 '15 at 20:41
  • @george2079 Yes, I think it probably would be. (I can't test it at the moment.) Tr is habit from totaling binary lists. – Mr.Wizard Oct 11 '15 at 08:24
4
DeleteDuplicates[Select[list, Count[list, #] == 4 &]]
developer2000
  • 165
  • 10
0

Or be lazy and just run the set theory-inspired Union[] command over the result of your initial computation, provided that the initial one yields the results you listed -- it doesn't for me.

Extrapolator
  • 281
  • 2
  • 6