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?
- 124,525
- 11
- 401
- 574
- 755
- 4
- 12
6 Answers
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"}
- 7,816
- 1
- 16
- 44
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.
- 271,378
- 34
- 587
- 1,371
Assuming:
lst = {"e", "c", "a", "d", "b"};
order = {1 -> 5, 2 -> 3, 3 -> 1, 4 -> 4, 5 -> 2};
lst[[#]] & /@ First /@ SortBy[order, Last]lst[[#]] & /@ Keys[SortBy[order, Last]]lst[[Keys[SortBy[order, Last]]]]
- 1,708
- 1
- 11
- 21
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.
- 42,267
- 3
- 51
- 143
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"}
- 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
-
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}
- 17,923
- 3
- 31
- 49
First /@rulesis the same asRange[Length[firstList]]. – David Baghdasaryan Jun 08 '17 at 09:48