1
$\begingroup$

I would like to remove element from a nested list when they have a certain number of elements repeated. For example:

{{1,2,3,4},{1,1,2,3},{1,3,4,1},{1,4,2,4}}

I would like to remove the 2nd and 3rd sublists because they have 2 cases of the 1. I would like the end result to remain nested however.

{{1,2,3,4},{1,4,2,4}}

I believe that Delete cases and count will be used to solve this problem.

$\endgroup$
  • $\begingroup$ So you mean, not "greater than 2", but "greater than or equal to 2"? $\endgroup$ – Michael E2 Mar 7 '16 at 19:11
  • $\begingroup$ Will it always be 2 or do you want to generalise to an arbitrary threshold? $\endgroup$ – Martin Ender Mar 7 '16 at 19:11
  • $\begingroup$ I am using 2 as a arbitrary threshold. Also it would be greater than or equal to 2. $\endgroup$ – Andre Mar 7 '16 at 19:13
  • 1
    $\begingroup$ The 4th sublist has two cases of a 4 -- should it be removed, too? $\endgroup$ – Michael E2 Mar 7 '16 at 19:13
  • 1
    $\begingroup$ Select[#, Count[#, 1] < 2 &] &@ {{1, 2, 3, 4}, {1, 1, 2, 3}, {1, 3, 4, 1}, {1, 4, 2, 4}} gives you the list of lists that have "1" not more than 1 time $\endgroup$ – BlacKow Mar 7 '16 at 19:15
9
$\begingroup$

There are a lot of ways to do this. Using Select:

Select[list, Count[#, 1] < 2 &]

Using Cases:

Cases[list, l_ /; Count[l, 1] < 2]

Also if the threshold is at 2, then this DeleteCases version might also be an option (but it gets ugly when you want a greater threshold):

DeleteCases[list, {___, 1, ___, 1, ___}]

As garej points out you can also turn the Cases solution into DeleteCases:

DeleteCases[list, l_ /; Count[l, 1] > 1]

Or use Replace for the pattern matching:

Replace[list, {___, 1, ___, 1, ___} -> Nothing, 1]

However, out of all of these, the pattern matching solutions are actually fastest:

list = RandomInteger[4, {500, 4}];
RepeatedTiming[Select[list, Count[#, 1] < 2 &];]
RepeatedTiming[Cases[list, l_ /; Count[l, 1] < 2];]
RepeatedTiming[DeleteCases[list, l_ /; Count[l, 1] > 1];]
RepeatedTiming[DeleteCases[list, {___, 1, ___, 1, ___}];]
RepeatedTiming[Replace[list, {___, 1, ___, 1, ___} -> Nothing, 1];]

(* {0.000919, Null}
   {0.00094, Null}
   {0.00093, Null}
   {0.00038, Null}
   {0.000253, Null} *)

But note that once you go to bigger thresholds, the pattern matching becomes expensive. It's still fastest for some other small thresholds, but around a threshold of 10, the speed is comparable to that of the other three solutions. When you go to a threshold of 20, then the pattern matching is ten times slower than the other solutions.

$\endgroup$
  • $\begingroup$ would it be possible to add an OR/AND to this and add another criteria $\endgroup$ – Andre Mar 7 '16 at 19:17
  • $\begingroup$ @Andre In the first two cases, easily. Just use && or || after Count[..., 1] < 2 and your additional criteria. $\endgroup$ – Martin Ender Mar 7 '16 at 19:18
  • $\begingroup$ As regards the last approach, when the threshold increases one could replace the pattern by Riffle[ConstantArray[___, threshold + 1], 1] $\endgroup$ – Coolwater Mar 7 '16 at 19:43
  • $\begingroup$ @Coolwater Sure, but I think the pattern matching will become a lot more expensive than a simple Count and it's also pretty obscure code whereas the first solution is very readable. $\endgroup$ – Martin Ender Mar 7 '16 at 19:44
  • $\begingroup$ @MartinBüttner, why not DeleteCases[data, l_ /; Count[l, 1] > 1] $\endgroup$ – garej Mar 8 '16 at 8:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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