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}}
.
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.
I think Simon's method is definitely the way to go provided:
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
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
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
*)
-1
levelspec?
$\endgroup$
Commented
Apr 30, 2014 at 1:31
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]
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*)
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
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
X
contains a position.X[[1,1]]
will be the scalar value. You can also goof it with things likeMax[X]
, etc. that will strip the braces. $\endgroup$