15

Suppose I have a list like {"e", "c", "a", "d", "b"} and list of rules {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2}. The second list says that for example element on position 1 shoud be on position 5 and so on. So the desired result is {"a", "b", "c", "d", "e"}. How it can be done in the fastest and elegant way?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574

6 Answers6

21

A bit simpler:

Permute[lst, SparseArray[order]]

Example:

lst = {"e", "c", "a", "d", "b"};
order = {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2};
Permute[lst, SparseArray[order]]

{"a", "b", "c", "d", "e"}

Ray Shadow
  • 7,816
  • 1
  • 16
  • 44
14

By using assignment to parts. Update: now cleaner.

fn[list_, r_] :=
  Module[{n = list},
    n[[Values @ r]] = n[[Keys @ r]];
    n
  ]

Test:

x = {"e", "c", "a", "d", "b"} ;
r = {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2};

fn[x, r]
{"a", "b", "c", "d", "e"}

Performance

This outperforms even Shadowray's elegant code:

x = RandomReal[1, 50000];
r = #2[Thread[# -> #2[#]]] &[Range@50000, RandomSample];

a = Permute[x, SparseArray[r]];   // RepeatedTiming
b = fn[x, r];                     // RepeatedTiming

a === b
{0.016, Null}

{0.0048, Null}

True

Optimization for a specific format

If all positions are specified and in order as in the example, we can simplify:

f2[list_, r_] := Module[{n = list}, n[[Values @ r]] = n; n]

This can be very fast:

r = Sort[r];

c = f2[x, r]; // RepeatedTiming

a === c
{0.0010, Null}

True


Old answer

If the position of every element is specified, as in the example, we can use:

x = {"e", "c", "a", "d", "b"} ;
r = {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2};

x[[ Ordering @ Values @ Sort @ r ]]
{"a", "b", "c", "d", "e"}

Sort is redundant if the rules are already sorted but I included it for robustness.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
9

Assuming:

lst = {"e", "c", "a", "d", "b"};
order = {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2};    
  1. lst[[#]] & /@ First /@ SortBy[order, Last]
  2. lst[[#]] & /@ Keys[SortBy[order, Last]]
  3. lst[[Keys[SortBy[order, Last]]]]
SuTron
  • 1,708
  • 1
  • 11
  • 21
7

You may use Permute and FindCycles.

With

vals = {"e", "c", "a", "d", "b"};
pos = {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2};

Then

Permute[vals, Cycles@Map[Last, FindCycle[pos, {1, ∞}, All], {2}]]
{"a", "b", "c", "d", "e"}

FindCycle will find more than one cycle if they exists in the rules of pos and Permute will apply all of them.

Hope this helps.

Also vals[[Ordering[Values@r]]] but I need to think if this will work with multiple cycles but have to go right now.

Edmund
  • 42,267
  • 3
  • 51
  • 143
5

A bit less elegant:

values = {"e", "c", "a", "d", "b"};
pos = {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2};
list = Table[{}, {i, 1, Length[values]}];
Table[list[[pos[[i, 2]]]] = values[[pos[[i, 1]]]], {i,1,Length[values]}];

list

gives {"a", "b", "c", "d", "e"}

Valacar
  • 970
  • 5
  • 14
  • 2
    +1 because after a half hour of independent development I realize that ended up with a cleaner version of your assignment. You might take a look at my answer to see how this might be done in a more concise and efficient way. – Mr.Wizard Jun 08 '17 at 12:31
  • I should definitely consider using the mapping functionality... – Valacar Jun 08 '17 at 16:25
5
Thread[SparseArray[rules] -> lst] // SparseArray // Normal 

or:

rules // SparseArray // Normal // SparseArray[# -> lst] & // Normal

Previous versions:

SparseArray[Thread[ rules[[;; , 2]] -> lst[[rules[[;; , 1]]]]]] // Normal

{lst, rules} // Apply[Thread[Values[#2] -> #[[Keys@#2]]] &] //SparseArray // Normal

{a, b, c, d, e}

user1066
  • 17,923
  • 3
  • 31
  • 49