5
$\begingroup$

How can I check if Position[i] > Position[j] for two arbitrary elements in a given list?

The problem is that Position[i] returns {{k}} instead of just k, and I don't know how to compare {{k}} and {{l}}.

$\endgroup$
1
  • 1
    $\begingroup$ say X contains a position. X[[1,1]] will be the scalar value. You can also goof it with things like Max[X], etc. that will strip the braces. $\endgroup$
    – ciao
    Commented Apr 29, 2014 at 4:16

7 Answers 7

4
$\begingroup$

Here's a simple example re: my comment:

afterQ[list_, ele1_, ele2_] := Max[Position[list, ele1]] > Max[Position[list, ele2]]


list = Range[10];
afterQ[list, 3, 4]
afterQ[list, 4, 3]
afterQ[list, 4, 99]
afterQ[list, 99, 4]

(*
False
True
True
False
*)

So, False if element 1 is not after element 2, True if it is, True if element 1 exists in the list and element 2 does not, False if element 1 is not in the list at all.

You'll want to think about ambiguities here: what if an element is both before and after? Or what if one does not even exist in the list? The above treats the last positions as the arbiter, you may want to do things differently.

$\endgroup$
4
$\begingroup$

I think Simon's method is definitely the way to go provided:

  • You are only concerned with first appearances
  • You know that both elements are in fact present
  • You will not be scanning the list many times

If you are not certain that both elements are present you should scan the list for both of them. You can limit the number of indexes returned by Position using its fourth parameter:

list = Characters @ "xdslkridiatjxzyoedem";

Position[list, #, 1, 1] & /@ {"i", "j"}
{{{7}}, {{12}}}

From this uniform expression you have many options for making a comparison, but the most direct is likely OrderereQ:

OrderedQ @ {{{7}}, {{12}}}

OrderedQ @ {{{12}}, {{7}}}
True

False

Perhaps you wish to allow for the possibility that one or both of the elements are missing from the list. You could check like this:

f1 = If[# ~MatchQ~ {{{_}} ..}, OrderedQ @ #, $Failed] &;

f1 @ {{{7}}, {{12}}}
f1 @ {{{12}}, {{7}}}
f1 @ {{}, {{12}}}
True

False

$Failed

I wrote this function to allow a generalization to more than two elements. Combining it with the Position call:

elemOrderedQ[a_List, elem__] :=
  Position[a, #, 1, 1] & /@ {elem} //
    If[# ~MatchQ~ {{{_}} ..}, OrderedQ @ #, $Failed] &

Now:

elemOrderedQ[list, "d", "s", "t"]
elemOrderedQ[list, "x", "y", "z"]
elemOrderedQ[list, "s", "t", "u"]
True

False

$Failed

Look-up tables (rule lists)

If you are going to be using this operation on the same list many times (with different elements) you should build a look-up table of elements and positions.

Single positions:

rls = Dispatch @ Thread[# -> Range@Length@#] & @ list
 Dispatch[{x->1,d->2,s->3,l->4,k->5,r->6,i->7,d->8,i->9,a->10,t->11,j->12,x->13,z->14,
  y->15,o->16,e->17,d->18,e->19,m->20},-DispatchTables-]

(If there are many duplicates the rule list should be filtered or building the Dispatch table will be slow. That can be done with e.g. GatherBy[rules, First][[All, 1]] where rules is the raw Rules list. Application of the rules will be fast either way.)

Now making comparisons of (first) positions in this list is as simple as this:

"i" > "j" /. rls
"i" > "x" /. rls
False

True

It is also very fast. If you have need to compare all positions you can do this:

rls2 = Dispatch @ Reap[MapIndexed[Sow[#2[[1]], #] &, list];, _, Rule][[2]]
Dispatch[{x->{1,13},d->{2,8,18},s->{3},l->{4},k->{5},r->{6},i->{7,9},a->{10},t->{11},
  j->{12},z->{14},y->{15},o->{16},e->{17,19},m->{20}},-DispatchTables-]

With this you can easily pose queries such as:
Are all appearances of "x" before the first appearance of "e"?

Last["x"] < First["e"] /. rls2 // Quiet
True

I used Quiet here to suppress messages from e.g. Last["x"]. I could also use Unevaluated:

Unevaluated[Last["x"] < First["e"]] /. rls2
True
$\endgroup$
3
$\begingroup$

An alternative to Position is to scan through the list with Cases and see if j appears first:

jbeforei[list_, i_, j_] := Cases[list, i | j, 1, 1] == {j}

jbeforei[Range[10], 8, 5]
jbeforei[Range[10], 2, 5]

(*
True
False
*)
$\endgroup$
2
  • $\begingroup$ Lovely method, assuming both elements are present. What is your reason for using a -1 levelspec? $\endgroup$
    – Mr.Wizard
    Commented Apr 30, 2014 at 1:31
  • $\begingroup$ @Mr.Wizard, no good reason - just habit and tiredness! $\endgroup$ Commented Apr 30, 2014 at 8:37
2
$\begingroup$

Ordering can tell you the position of the smallest and largest values and be used to sort list, e.g.

list = RandomSample[Range[10], 10]

Example:

list={7, 4, 10, 8, 6, 5, 1, 9, 3, 2}

then

ord=Ordering@list

produces:

{7, 10, 9, 2, 6, 5, 1, 4, 8, 3}

i.e. the smallest element of list is at positon 7 and larges at position 3.

You can sort (in addition to Sort) using:

list[[ord]]

You can get the position of the smallest 3 elements:

Ordering[list, 3]

or largest 3 elements:

Ordering[list, -3]
$\endgroup$
2
$\begingroup$

Using Mr. Wizard's data:

list = Characters@"xdslkridiatjxzyoedem";

we can use GroupBy as follows:

pin = GroupBy[Range@Length@#, Function[x, Part[#, x]]][[All, 1]] &@list;

"i" > "j" /. pin

(*False*)

"i" > "x" /. pin

(*True*)
$\endgroup$
2
$\begingroup$

Using TakeBefore by Wolfram Staff

TakeBefore = ResourceFunction["TakeBefore"];

before[list_, {a_, b_}] := MemberQ[a] @ TakeBefore[b, All] @ list

1.

list = Range[10];

before[list, #] & /@ {{3, 4}, {4, 3}}

{True, False}

2.

Using Mr. Wizard's example:

list = Characters @ "xdslkridiatjxzyoedem";

before[list, #] & /@ {{"j", "i"}, {"x", "i"}}

{False, True}

3.

If a and / or b are outside, before returns False

before[Range[10], {11, 12}]

False

$\endgroup$
1
$\begingroup$

Using Mr. Wizard's data:

list = Characters @ "xdslkridiatjxzyoedem";

we can use PositionIndex to our advantage

pin = PositionIndex[list][[All, 1]]

"i" > "j" /. pin

False

"i" > "x" /. pin

True

$\endgroup$

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.