20
$\begingroup$

I am using Mathematica7. I have a list of lists (creatively named list in the following sample code) containing data. I would like to find the positions of the elements that satisfy a particular criterion. However, Position takes a pattern as input, whereas Select takes a criterion as input.

Suppose I want to find the positions of all lists (i.e., sublists) whose first element is 1. The following code works (or so it seems), but it generates error messages as well:

list = {{1, "A"}, {1, "B"}, {2, "C"}};
Position[list, _?(#[[1]] == 1 &)]

The output looks correct: {{1},{2}}

But I also get the following error messages, which in a long computation seem to slow down Position:

Part::partd : Part specification List[[1]] is longer than depth of object.
Part::partd : Part specification List[[1]] is longer than depth of object.
Part::partd : Part specification [[1]][[1]] is longer than depth of object.
General::stop : Further output of Part::partd will be suppressed during this calculation.

Does my code indeed work properly? If so, why do I get the error messages? If not, how can I improve my code?

$\endgroup$
0

4 Answers 4

18
$\begingroup$

To understand why you're getting that error, try your code with _ as the pattern and see what elements are returned:

list[[Sequence @@ #]] & /@ Position[list, _]
(* {List, List, 1, "A", {1, "A"}, List, 1, "B", {1, "B"}, List, 2, "C", {2, "C"}, 
    {{1, "A"}, {1, "B"}, {2, "C"}}} *)

You can see that in your case, Position is walking down every branch and visiting every leaf of the expression tree and checking to see if the criterion fits. You can confirm that these results are indeed the leaves:

Level[list, -1, Heads -> True]
(* {List, List, 1, "A", {1, "A"}, List, 1, "B", {1, "B"}, List, 2, "C", {2, "C"}} *)

The last element is the entire expression itself. None of the elements in the above list except for elements of the original list have parts that are indexable as you've done in your pattern, which is why you get the error.

Once you understand this, you can now proceed to fix the errors and narrow down where position acts, namely:

  • set Heads -> False so that you don't visit them
  • look only at level 1 and not deeper/shallower levels
  • narrow down the pattern to something more suitable (hint: you know the first element should be 1)

If you do these, you'll reach your desired solution (which Rojo and kguler have already answered). So going by the points above, you'd do something like,

Position[list, _?(First[#] == 1 &), {1}, Heads -> False]
(* {{1}, {2}} *)

which is the same as Rojo's answer. Now the pattern can be refined further and not require either the use of Heads -> False or the level {1}, and this leads you to kguler's answer:

Position[list, {1, ___}]
(* {{1}, {2}} *)

With experience, you'll recognize how to simplify and choose the right pattern. Note that in more complicated cases, you might have to operate at different levels or only at certain specific levels, etc., and you might have to specify the pattern and the level.

$\endgroup$
0
12
$\begingroup$

Use a level specification so Position doesn't search at level 0 or deeper, where it can't take part 1. Also, prevent it from checking the heads

list = {{1, "A"}, {1, "B"}, {2, "C"}};
Position[list, _?(#[[1]] == 1 &), {1}, Heads -> False]

{{1}, {2}}

Also, in this particular case I would work on the list first

Position[list[[All, 1]], 1]
$\endgroup$
2
  • $\begingroup$ I guess that Position[list[[All, 1]], 1] is faster; is that why you would work on the list first in this particular case? $\endgroup$
    – Andrew
    Jun 30, 2012 at 0:53
  • 3
    $\begingroup$ @Andew I also believe that it's faster, and I also find it clearer $\endgroup$
    – Rojo
    Jun 30, 2012 at 0:59
10
$\begingroup$
list = {{1, "A"}, {1, "B"}, {2, "C"}, {1}};
Position[list, {1, ___}]

(* ==> {{ 1}, {2}, {4}}  *)
$\endgroup$
2
  • $\begingroup$ This indeed runs with no error messages; thanks. So I guess that this means there is some problem with "assuming" that each element has a position with index 1? $\endgroup$
    – Andrew
    Jun 30, 2012 at 0:50
  • 3
    $\begingroup$ Andrew, yes ... pls see @R.M's answer for an explanation. $\endgroup$
    – kglr
    Jun 30, 2012 at 0:53
8
$\begingroup$

The answers above give you a great alternative: Position[list, {1, ___}] and a nice explanation of the problem. However, as I see it they don't entirely answer the question that was asked: how to use Position with a True/False "criterion" function without errors.

Consider this:

list = {{1, "A"}, {1, "B"}, {2, "C"}};

test = #[[1]] == 1 &;

Position[list, _List?test]

This restricts the application of test to only objects that have the head List. If none of your lists are empty this will operate without error. If you also want to restrict matches to level 1 as Select does, use:

Position[list, _List?test, {1}]

Apparently I didn't make clear the purpose of this answer. I wish to show that you can combine "type" (Head) checking with PatternTest to control what expressions are sent to the test function.

Consider what happens if list contains an atomic element at level 1:

list = {{1, "A"}, {1, "B"}, {2, "C"}, "atom"};

Merely restricting the level that is tested is insufficient in this case:

Position[list, _?(First[#] == 1 &), {1}, Heads -> False]

During evaluation of In[2]:= First::normal: Nonatomic expression expected at position 1 in First[atom]. >>

The pattern _List in Position[list, _List?test] successfully restricts testing to lists in this case.

You could also use Condition rather than PatternTest, which may be useful if you need a more restrictive pattern. (See this for important differences between these two functions.)

Position[list, x : {_, "B"} /; test[x]]

Which can also be written:

Position[list, {_, "B"}?test]

With this only elements matching the pattern {_, "B"} are passed to test, and only the positions of elements that pass the test are returned.

$\endgroup$
2
  • 1
    $\begingroup$ I wouldn't say "they don't entirely answer the question", because my answer does have _?(First[#] == 1 &) and so does Rojo's. Your test is not any different :) $\endgroup$
    – rm -rf
    Jun 30, 2012 at 1:16
  • $\begingroup$ @R.M okay, that's not what I meant (and you've already got my vote); rather I am trying to show that you can use a pattern to restrict what is fed to test. Consider what happens if list = {{1, "A"}, {1, "B"}, {2, "C"}, "atom"}; $\endgroup$
    – Mr.Wizard
    Jun 30, 2012 at 1:18

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.