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$
6
  • $\begingroup$ So you mean, not "greater than 2", but "greater than or equal to 2"? $\endgroup$
    – Michael E2
    Mar 7, 2016 at 19:11
  • $\begingroup$ Will it always be 2 or do you want to generalise to an arbitrary threshold? $\endgroup$ Mar 7, 2016 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, 2016 at 19:13
  • 1
    $\begingroup$ The 4th sublist has two cases of a 4 -- should it be removed, too? $\endgroup$
    – Michael E2
    Mar 7, 2016 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, 2016 at 19:15

1 Answer 1

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$
8
  • $\begingroup$ would it be possible to add an OR/AND to this and add another criteria $\endgroup$
    – Andre
    Mar 7, 2016 at 19:17
  • $\begingroup$ @Andre In the first two cases, easily. Just use && or || after Count[..., 1] < 2 and your additional criteria. $\endgroup$ Mar 7, 2016 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, 2016 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$ Mar 7, 2016 at 19:44
  • $\begingroup$ @MartinBüttner, why not DeleteCases[data, l_ /; Count[l, 1] > 1] $\endgroup$
    – garej
    Mar 8, 2016 at 8:40

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.