23

I wanted to use the Pick function with a condition. But there seems to be an issue here. Take a look at this:

selection = {0,1.2,3,0.,5};
Pick[{1,2,3,4,5},selection,elem_ /; elem =!= 0]

In Mathematica 8 it will give {1,2,3,4,5} instead of {2,3,4,5}. Please note, that the Pick function works nicely with

Pick[{1,2,3,4,5},selection,elem_ /; elem === 0]

Giving {1} as a result. Is this a bug or am I missing something?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
gwr
  • 13,452
  • 2
  • 47
  • 78

3 Answers3

19

This confused me as well, but using Trace revealed what is going on:

Trace@Pick[{1, 2, 3, 4, 5}, selection, elem_ /; elem =!= 0]

{{selection,{0,1.2,3,0.,5}},
 Pick[{1,2,3,4,5},{0,1.2,3,0.,5},elem_/;elem=!=0],
 {{0,1.2,3,0.,5}=!=0,True},
 {1,2,3,4,5}}

The key is the 4th line: note that the pattern is applied to the full list, at level 0. The full list selection does match (because it is not structurally equivalent to 0) thus the full first argument is picked out.

The reason why we don't see this behavior with Equal (i.e. ==) is that {0, 1.2, 3, 0., 5} != 0 stays unevaluated.

(I did not find a way to restrict at which levels Pick operates, but it is possible to tweak the pattern instead, e.g. elem_?NumericQ/;elem=!=0, possibly with a performance hit.)

rcollyer
  • 33,976
  • 7
  • 92
  • 191
Szabolcs
  • 234,956
  • 30
  • 623
  • 1,263
  • 2
    Does it mean that the patt in Pick[list, sel, patt] is not mapped over list and sel in this case? That is contradicting the help, I think. – István Zachar May 01 '12 at 11:54
  • 1
    @István It seems that Pick compares list and sel at every level, successively, including level 0. Once a level matches, it picks everything from there. If it doesn't, it looks at deeper levels. Another example would be Pick[{1, 2, {x, y, z}}, {1, 2, {a, b, c}}, a_ /; Length[a] == 3]. The full list is picked. If we change 1, 2 to 1, 2, 3 in both lists to make them length 4, only the last element will be picked. – Szabolcs May 01 '12 at 11:57
  • Thank Szabolcs. One has to be really careful with the patterns as of course - thinking about it - everything is totally logical as elem_ matches a list in toto. I forgot to put in a Filter as you suggested. One might wish for a levelspec, but then you should probably turn to Cases anyway. – gwr May 01 '12 at 11:58
  • 1
    @gwr I agree that it's really confusing. If it weren't for Trace, I would have believed it was a bug ... – Szabolcs May 01 '12 at 12:00
  • 7
    Then I really wonder why Pick does not have a 4th argument for level specification... – István Zachar May 01 '12 at 12:07
  • @Szabolcs I do find confusing that Cases will work with the pattern not having to use the ?NumericQ test. So it is really the levelspec that is misleading. How good that Mathematica is an interpreted language ;-) – gwr May 01 '12 at 12:08
  • @gwr It seems Cases looks only at level 1 by default. In contrast to Pick, it does accept a level specification though, so we can force it to look at level 0 (using {0} as the last argument). Cases is different in that once an element has matched, it will still look at its sub-elements: Cases[{{1, 2}, {3, {4, 5}}}, _List, {1, Infinity}]. – Szabolcs May 01 '12 at 12:14
  • 2
    @Szabolcs +1. As to Cases: "once an element has matched, it will still look at its sub-elements" - this actually happens the other way around: Cases uses a depth-first traversal (which causes some other effects as well), so it sees the parts before it sees the expression as a whole. – Leonid Shifrin May 01 '12 at 12:30
  • Maybe, an alternative way to avoid level 0 matches could be to add that condition explicitly in the pattern: elem_ /; elem =!= 0 && elem =!= selection? – kglr May 01 '12 at 12:48
19

Here is a version which avoids any extra performance overhead associated with Condition etc:

Pick[{1, 2, 3, 4, 5}, selection, Except[_List | 0]]

I did not benchmark, but for large lists I'd expect it to be significantly faster than the versions based on Condition and / or PatternTest.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
6

Edit: Assuming that you do not differ between 0 and 0.

The unequal sign is !=

Pick[{1, 2, 3, 4, 5}, selection, elem_ /; elem != 0]

gives

{2, 3, 5}

Alternatively you can use the True selection mechanism:

 Pick[{1, 2, 3, 4, 5}, !PossibleZeroQ[#] & /@ selection]
Markus Roellig
  • 7,703
  • 2
  • 29
  • 53
  • Thank you, Markus, but I wanted to explicitedly make a difference between a Null-entry as in a sparse array and a numerical Value of 0. That is why I used =!= instead of !=. – gwr May 01 '12 at 12:09
  • @gwr: If your goal is to find the elements which are explicitly in the sparse array, you can use DeleteCases[#[[1,1]]&/@ArrayRules[yoursparsearray],Verbatim[_]] or, if the array might be multi-dimensional, DeleteCases[#[[1]]&/@ArrayRules[yoursparsearray],{Verbatim[_]...}] – celtschk May 02 '12 at 13:52