2
$\begingroup$

What would be the fastest way to find all cases in a list that match a pattern but without repetition in the result?

Cases[ list, pattern ] // DeleteDuplicates does the job but is not so efficient since it adds all duplicates to a list and only then deletes them (while not adding them would be faster). Cases[ DeleteDuplicates @ list, pattern ] will first delete all duplicates from the list while only the duplicates from the result need to be deleted.

I searched through the documentation of Cases but could not find any way to turn off repetition.

Edit: A Toy Example

One of the cases I have to deal with is the following. I have a large (>10^6) list of equations in some variables, say { x[1], ..., x[n] }, and say for example that I would like to use all equalities of the form ( n_?NumericQ == x[i_] | x[i_] == n_?NumericQ ) /; i >= m to find out some information about the variables x[i] with i > m. This could be useful to see whether there's some inconsistency, or maybe some of these values are too big, or not real, etc.

One way to tackle this problem is as follows

(* Small toy example *)

equationList = 
  {
    x[1] == x[3]+x[5],
    2 == x[3] x[4],
    6 == x[1] x[4],
    3 == x[1],
    x[5]^2 == x[2]x[3],
    x[1] == x[3] + x[5],
    3 == x[1],
    7 == x[6]
  };

infoVars[ m_Integer, list_List ] := 
  Cases[ 
    list, 
    ( n_?NumericQ == x[i_] | x[i_] == n_?NumericQ ) /; i >= m :> x[i] -> n
  ];

infoVars[ 1 , equationList ] then returns { x[1] -> 3, x[1] -> 3, x[6] -> 7 }, i.e. a list with duplicate elements.

I could of course apply DeleteDuplicates to this list, but if equationList contains a lot of duplicate matching expressions, then Mathematica would basically create a long list with multiple duplicates. This is a bit stupid since I have to delete these duplicates anyway so it would be nice if there were an option such that Cases would not add all those duplicates to the list in the first place.

Deleting duplicates beforehand might also be costly because I would delete duplicates in which I might not be interested.

$\endgroup$
9
  • 2
    $\begingroup$ Could you please load some concrete test cases along with expected outcome? $\endgroup$
    – Syed
    Mar 1 at 9:57
  • 2
    $\begingroup$ will first delete all duplicates from the list while only the duplicates from the result need to be deleted I do not see what is the difference? But it will help to give specific test case where you think it makes difference. $\endgroup$
    – Nasser
    Mar 1 at 10:03
  • 1
    $\begingroup$ Btw, I think the question is not clear. Since Cases uses a PATTERN, not an actual expression to search for, what do you mean by it finds all duplicates? It just finds all expressions that match the PATTERN. If you just want the first case only found, then there is a specific function for that, called FirstCase see help gives the first Subscript[e, i] to match pattern $\endgroup$
    – Nasser
    Mar 1 at 10:10
  • $\begingroup$ @Syed: I added an example that hopefully clarifies my question. $\endgroup$
    – Gert
    Mar 1 at 13:01
  • 2
    $\begingroup$ infoVars[ 1 , equationList ] then returns { x[1] -> 3, x[1] -> 3, x[6] -> 7 } That is not what I get screen shot !Mathematica graphics $\endgroup$
    – Nasser
    Mar 1 at 13:44

2 Answers 2

4
$\begingroup$

As someone said, premature optimization is the root of all evil.

I think you are trying to optimize something not needed. As mentioned in comments, you can simply remove duplicate items from the list either before or after calling Cases. DeleteDuplicates or Union are very much optimized and fast as they are built in functions.

But if you must do it inside cases, here is one option.

 lis = {Sin[x], Sin[2 x], Cos[x], x, Sin[x]}
 Cases[lis, Sin[_]]

Mathematica graphics

We see duplicates. Change Cases to check as it finds pattern, if such an expression was already collected or not. It does this by keeping in a side list all expressions found so far. If the current one was not found so far, it will add it to the side list and use it, else it will skip it.

lis = {Sin[x], Sin[2 x], Cos[x], x, Sin[x]}
c = {};
Cases[lis, x_ /; MatchQ[x, Sin[_]] :> If[FreeQ[c, x], AppendTo[c, x]; x, Nothing]]

Mathematica graphics

No duplicates.

Is this more efficient that simply removing duplicates before or after? I doubt it. But Cases needs a way to know if it has found such an expression or not as it traverse the list. How would it "remember" that other than keeping a list of what was detected by the pattern?

Any way, Cases has no such option and I think for a good reason.

My 2 cents advice, forget about this goal, and simply remove duplicates either before calling Cases or after. i.e. simply do

lis = {Sin[x], Sin[2 x], Cos[x], x, Sin[x]}
Cases[lis, Sin[_]] // Union

Mathematica graphics

$\endgroup$
1
$\begingroup$

I agree with everything that Nasser has said above.

However, if you don't mind the result as an association (rather than a list of rules), you could do something like the following? Not sure how efficient this is compared with using DeleteDuplicates, though.

infoVarsZ[ m_Integer, list_List ] := Module[{assoc = <||>},
 Cases[list, 
   ( n_?NumericQ == x[i_] | x[i_] == n_?NumericQ ) /; i >= m :> AssociateTo[assoc,x[i] -> n]
      ];
 assoc]

infoVarsZ[1 , equationList]
(* <|x[1] -> 3, x[6] -> 7|> *)
$\endgroup$

Your Answer

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

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