21

Given an array arr and an array of indices ind, I'd like to rearrange arr in-place to satisfy the given indices. For example:

var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 0, 5, 2, 1, 3];

rearrange(arr, ind);

console.log(arr); // => ["B", "E", "D", "F", "A", "C"]

Here is a possible solution that uses O(n) time and O(1) space, but mutates ind:

function swap(arr, i, k) {
  var temp = arr[i];
  arr[i] = arr[k];
  arr[k] = temp;
}

function rearrange(arr, ind) {
  for (var i = 0, len = arr.length; i < len; i++) {
    if (ind[i] !== i) {
      swap(arr, i, ind[i]);
      swap(ind, i, ind[i]);
    }
  }
}

What would be the optimal solution if we are limited to O(1) space and mutating ind is not allowed?


Edit: The algorithm above is wrong. See this question.

Community
  • 1
  • 1
Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746
  • 3
    Are you sure there's even a solution given your limitations? – Nick Zuber May 26 '16 at 12:49
  • 1
    If I say I am using an array with the MAX size in javascript, then this solution will be O(1) since it is constant and unrelated to n :) – Nuri Tasdemir May 26 '16 at 16:45
  • @Misha Moroshko The `arr` array may contain only string or there can be any object? And the `ind` array can't be modified at all or it can be modified and then reverse the values to its initial state? – Iulian Popescu May 27 '16 at 13:59
  • @lulian `arr` items can be of any type, including objects, and let's assume that it's ok to mutate `ind` during the execution as long as when you finish `ind` looks unchanged. – Misha Moroshko May 29 '16 at 07:06
  • @Misha Moroshko Could you please check my answer. http://stackoverflow.com/a/37466714/4543207 – Redu May 29 '16 at 10:44
  • 2
    To me, it looks like this problem reduces to sorting `arr` using an ordering of the letters "A"..."Z" given by the indices in `ind`. So you're asking whether it is possible to sort an array in linear-time with constant space. The best linear-time sorting algorithms (Radix sort, Bucket sort, Counting sort) cannot achieve this so I suspect there isn't a known optimal solution to your problem. – James Lawson May 29 '16 at 11:32
  • 2
    @JamesLawson, I don't agree this is a (classical) sorting algorithm, because the final position for each element is already given as input, which is not the case for even the Radix-class sorting algorithms. I think some of the answers given do represent an optimal solution for performing the rearrangement. – trincot May 30 '16 at 14:18
  • Can you explain why your solution does not use the final two numbers in the `ind` array? For example, your shown solution has the same result for `var ind = [4, 0, 5, 2];` as it does for `var ind = [4, 0, 5, 2, 1, 3];`. Is skipping the last two values intentional? – Travis J May 30 '16 at 23:06
  • Why should `rearrange(["A", "B", "C", "D", "E", "F"],[4, 0, 5, 2, 1, 3])` be `["B", "E", "D", "F", "A", "C"]`? I expected ["E", "A", "F", "C", "B", "D"]. I am also wondering if something like [0,0,0,0,0,0] would be an acceptable rearrangement with result ["A", "A", "A", "A", "A", "A"] of course. – hkBst May 31 '16 at 08:05
  • @hkBst, you interpreted the values of `ind` as "take from" indices, but they are "send to" indices. The first value in the `ind` array means: take element at my index (i.e. at index 0) and move it to what my value says (i.e. move it to index 4). – trincot May 31 '16 at 18:55
  • @trincot - If that is indeed the meaning of `ind`, then it should be `0` 0<->4...A<->E ... ["E", "B", "C", "D", "A", "F"] ... `1` 1<->0...B<->E ... ["B", "E", "C", "D", "A", "F"] `2` ... 2<->5...C<->F ... ["B", "E", "F", "D", "A", "C"] `3` ... 3<->2...D<->F ... ["B", "E", "D", "F", "A", "C"] `4` ... 4<->1...A<->E ... ["B", "A", "D", "F", "E", "C"] `5` ... 5<->3...C<->F ... ["B", "A", "D", "C", "E", "F"] – Travis J May 31 '16 at 20:52
  • No, it is not like that: This is not about swapping. It is about moving, and moving, ...: "A" moves to 4, but it is not a swap. The "E" that is in slot 4, should go where `ind[4]` points to, i.e. to slot 1; just like the "A" should go where `ind[0]` points to. They don't swap, they are in chain of movements. – trincot May 31 '16 at 21:01
  • @trincot - I see. Thank you for the clarification, that makes sense. – Travis J May 31 '16 at 22:11

9 Answers9

9

This is the "sign bit" solution.

Given that this is a JavaScript question and the numerical literals specified in the ind array are therefore stored as signed floats, there is a sign bit available in the space used by the input.

This algorithm cycles through the elements according to the ind array and shifts the elements into place until it arrives back to the first element of that cycle. It then finds the next cycle and repeats the same mechanism.

The ind array is modified during execution, but will be restored to its original at the completion of the algorithm. In one of the comments you mentioned that this is acceptable.

The ind array consists of signed floats, even though they are all non-negative (integers). The sign-bit is used as an indicator for whether the value was already processed or not. In general, this could be considered extra storage (n bits, i.e. O(n)), but as the storage is already taken by the input, it is not additional acquired space. The sign bits of the ind values which represent the left-most member of a cycle are not altered.

Edit: I replaced the use of the ~ operator, as it does not produce the desired results for numbers equal or greater than 231, while JavaScript should support numbers to be used as array indices up to at least 232 - 1. So instead I now use k = -k-1, which is the same, but works for the whole range of floats that is safe for use as integers. Note that as alternative one could use a bit of the float's fractional part (+/- 0.5).

Here is the code:

var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 0, 5, 2, 1, 3];

rearrange(arr, ind);

console.log('arr: ' + arr);
console.log('ind: ' + ind);

function rearrange(arr, ind) {
    var i, j, buf, temp;
    
    for (j = 0; j < ind.length; j++) {
        if (ind[j] >= 0) { // Found a cycle to resolve
            i = ind[j];
            buf = arr[j];
            while (i !== j) { // Not yet back at start of cycle
                // Swap buffer with element content
                temp = buf;
                buf = arr[i];
                arr[i] = temp;
                // Invert bits, making it negative, to mark as visited
                ind[i] = -ind[i]-1; 
                // Visit next element in cycle
                i = -ind[i]-1;
            }
            // dump buffer into final (=first) element of cycle
            arr[j] = buf;
        } else {
            ind[j] = -ind[j]-1; // restore
        }
    }
}

Although the algorithm has a nested loop, it still runs in O(n) time: the swap happens only once per element, and also the outer loop visits every element only once.

The variable declarations show that the memory usage is constant, but with the remark that the sign bits of the ind array elements -- in space already allocated by the input -- are used as well.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Just from looking, it doesn't look like `O(n)` to me - correct me if I'm wrong – Nick Zuber May 26 '16 at 13:09
  • My defence to it being *O(n)* is in the text below the snippet. – trincot May 26 '16 at 13:12
  • 1
    I was close to the same solution: you cannot solve this problem without "saving" the previously visited elements in some way. I think this is the best approach with the given limitations – Pablo Lozano May 26 '16 at 13:21
  • @Pablo I'm going to write out a formal proof of this algorithm later when I get home from work - stay tuned!! – Nick Zuber May 26 '16 at 21:44
  • 2
    I am of two most minds about this: on the one hand, the question parameters speak of JavaScript arrays, typically 32/64 bit signed floats, and array indexes traditionally do not rely on the sign, so using it, while theoretically O(n) space, does not add extra space to what is already given; on the other hand, in JavaScript you can also do this `array[-3] = somevalue`, which might make it difficult to use your algorithm if `ind` includes negative values. – גלעד ברקן May 26 '16 at 22:12
  • @גלעדברקן, you're absolutely right. I find that negative indices lead to lots of issues in JavaScript anyway: when outputting arrays, elements with negative indices are often excluded (e.g. `ind.join(','); JSON.stringify(ind); ind.length; ind.forEach(...);`). They act more like extra properties of an array. But, one could extend the algorithm to make it work, by adding the difference of the largest and smallest index (plus 1) to `ind[i]` instead of swapping the bits. – trincot May 27 '16 at 08:22
  • Nice. Ok, about if `ind` includes `a`, `b` or other random symbols? I mean, what's important here is their order. (Btw, I think you meant the properties would be excluded in `arr.join`, `arr.forEach`, etc., although they would be included in `for (var i in arr)`. The properties of `arr` are elements in the `ind` array.) – גלעד ברקן May 27 '16 at 11:45
  • @גלעדברקן, using non-numerical symbols as properties for `arr` would indeed make the algorithm not suitable. The challenge is then to find a way to somehow flag that an element was visited. The properties of `arr` are both the elements in, and properties of, the `ind` array. Unless the order of the properties is used as zero-based index in the `ind` array. But that would be an ugly mix in my opinion. – trincot May 27 '16 at 12:28
  • @trincot Not certain how `arr` is rearranged based on `ind`? – guest271314 May 29 '16 at 19:54
  • @guest271314, I tried to explain that in my answer. What is not clear? – trincot May 29 '16 at 20:31
  • @trincot Do not gather, here, how `ind` transforms `arr` to `["B", "E", "D", "F", "A", "C"]` ? How does `4` transform `A` to `B`? – guest271314 May 29 '16 at 20:34
  • It doesn't immediately transform "A" to "B". It takes the "A", goes to index 4, swaps in the "A" there, takes the "E" that was there, goes to index 1, swaps it in there, taking the "B" out, and then finally dumps the "B" in slot 0. – trincot May 29 '16 at 20:37
  • @trincot Ok, got it – guest271314 May 29 '16 at 21:08
  • @trincot Is requirement to stop permutations of original input array when indexes of original permutated array matches indexes of `ind`? – guest271314 May 30 '16 at 16:54
  • No, the permutation cycle that I described in above comment stops when you arrive at the index where you started, i.e. at index 0. Then the algorithm moves to the next index (i.e. 1), detects that that index was already visited, and moves to index 2. There it takes out "C", goes to index 5, puts it there, takes the "F",... etc until it arrives back at index 2. As we are were we started this loop (2), that second cycle is now also finished. The algorithm continues to check index 3, 4, and 5, but notices that all of them were visited already. Done. – trincot May 30 '16 at 18:26
  • Although I think this is a clever trick, it is of course no more `O(1)` space than a array of bits would be. With the same kind of logic, one could argue that sorting integers in any programming language can be done in `O(n)` time, because the size of those integers is bounded by a constant. I still think this is the best answer to the question, though :) – Vincent van der Weele May 31 '16 at 18:37
  • According to ECMA-262, `an array index is an integer index whose numeric value i is in the range +0 ≤ i < 2^32−1.` Since you are effectively converting indices to two-complements via ~~index for which ~~index === index only if index < 2^(32-1)-1, you divide the allowed size of the input array by 2 and use that remaining space to store your flags. You basically steal your O(n) storage space from the indices input array. Your solution's space complexity is in O(n). – le_m Jun 04 '16 at 16:56
  • You could solve aforementioned issue by e.g. subtracting 2^32 from your indices instead (which is still well within range of Number.MAX_SAFE_INT). Of course, you would need to add checks for index > 0 before subtracting or reverting. This would unfortunately still violate OPs requirement of not mutating `ind` and will fail for indices that are not following the ECMA specification on array indices, so I think we could add the later as a reasonable requirement. – le_m Jun 04 '16 at 18:42
  • @trincot Assume your indices array contains an element 2³²⁻¹, which is a perfectly fine array index. Now you are negating and 'restoring' this element via the bitwise ~ operator which turns its operand into a two-complement. Now, due to the limited range of two-complements, ~~2³²⁻¹ != 2³²⁻¹. Restoring the indices array thus fails. – le_m Jun 04 '16 at 19:42
  • @le_m, I see what you are saying. The `~` operator will only work as needed on ranges to `2^31-1`. So I made a slight change to the code to avoid this "limitation", as any other unused bit in the float representation can be used for this purpose. The code now uses the first bit of the fractional part of the input floats. Note that the OP stated in comments that it is acceptable that *ind* is mutated on the condition it is restored during the algorithm. This is what this algorithm does. – trincot Jun 04 '16 at 20:03
  • Clever fix, it works now for all array indices according to spec. You might want to change your description ("sign bit") though :) – le_m Jun 04 '16 at 20:19
  • 1
    @le_m, although the `+0.5` version worked, I went back to the original idea of using the sign bit, as somehow I find it more elegant. Instead of `~ind[i]`, I just use `-ind[i]-1` which works as needed for the higher safe-integer range. – trincot Jun 05 '16 at 06:42
4

Index array defines a permutation. Each permutation consists of cycles. We could rearrange given array by following each cycle and replacing the array elements along the way.

The only problem here is to follow each cycle exactly once. One possible way to do this is to process the array elements in order and for each of them inspect the cycle going through this element. If such cycle touches at least one element with lesser index, elements along this cycle are already permuted. Otherwise we follow this cycle and reorder the elements.

function rearrange(values, indexes) {
    main_loop:
    for (var start = 0, len = indexes.length; start < len; start++) {
        var next = indexes[start];
        for (; next != start; next = indexes[next])
            if (next < start) continue main_loop;

        next = start;
        var tmp = values[start];
        do {
            next = indexes[next];
            tmp = [values[next], values[next] = tmp][0]; // swap
        } while (next != start);
    }
    return values;
}

This algorithm overwrites each element of given array exactly once, does not mutate the index array (even temporarily). Its worst-case complexity is O(n2). But for random permutations its expected complexity is O(n log n) (as noted in comments for related answer).


This algorithm could be optimized a little bit. Most obvious optimization is to use a short bitset to keep information about several indexes ahead of current position (whether they are already processed or not). Using a single 32-or-64-bit word to implement this bitset should not violate O(1) space requirement. Such optimization would give small but noticeable speed improvement. Though it does not change worst case and expected asymptotic complexities.

To optimize more, we could temporarily use the index array. If elements of this array have at least one spare bit, we could use it to maintain a bitset allowing us to keep track of all processed elements, which results in a simple linear-time algorithm. But I don't think this could be considered as O(1) space algorithm. So I would assume that index array has no spare bits.

Still the index array could give us some space (much larger then a single word) for look-ahead bitset. Because this array defines a permutation, it contains much less information than arbitrary array of the same size. Stirling approximation for ln(n!) gives n ln n bits of information while the array could store n log n bits. Difference between natural and binary logarithms gives us to about 30% of potential free space. Also we could extract up to 1/64 = 1.5% or 1/32 = 3% free space if size of the array is not exactly a power-of-two, or in other words, if high-order bit is only partially used. (And these 1.5% could be much more valuable than guaranteed 30%).

The idea is to compress all indexes to the left of current position (because they are never used by the algorithm), use part of free space between compressed data and current position to store a look-ahead bitset (to boost performance of the main algorithm), use other part of free space to boost performance of the compression algorithm itself (otherwise we'll need quadratic time for compression only), and finally uncompress all the indexes back to original form.

To compress the indexes we could use factorial number system: scan the array of indexes to find how many of them are less than current index, put the result to compressed stream, and use available free space to process several values at once.

The downside of this method is that most of free space is produced when algorithm comes to the array's end while this space is mostly needed when we are at the beginning. As a result, worst-case complexity is likely to be only slightly less than O(n2). This could also increase expected complexity if not this simple trick: use original algorithm (without compression) while it is cheap enough, then switch to the "compressed" variant.

If length of the array is not a power of 2 (and we have partially unused high-order bit) we could just ignore the fact that index array contains a permutation, and pack all indexes as if in base-n numeric system. This allows to greatly reduce worst-case asymptotic complexity as well as speed up the algorithm in "average case".

Community
  • 1
  • 1
Evgeny Kluev
  • 24,287
  • 7
  • 55
  • 98
  • +1 for pointing out the related answer. Not sure whether that might even be a suitable duplicate target. – Bergi May 29 '16 at 13:40
  • @Redu: it gives [K,M,U,H,B,W,L,O,C,D,I,E,N,S,V,P,X,A,Q,F,Y,Z,R,T,J,G] on your data. Seems perfectly correct. (http://ideone.com/bBZzWx) – Evgeny Kluev May 29 '16 at 15:01
  • @Evgeny Kluev Sorry your code is fine. My testing was wrong. – Redu May 29 '16 at 15:20
1

This proposal utilizes the answer of Evgeny Kluev.

I made an extension for faster processing, if all elements are already treated, but the index has not reached zero. This is done with an additional variable count, which counts down for every replaced element. This is used for leaving the main loop if all elements are at right position (count = 0).

This is helpful for rings, like in the first example with

["A", "B", "C", "D", "E", "F"]
[ 4,   0,   5,   2,   1,   3 ]

index 5: 3 -> 2 -> 5 -> 3
index 4: 1 -> 0 -> 4 -> 1

Both rings are at first two loops rearranged and while each ring has 3 elements, the count is now zero. This leads to a short circuit for the outer while loop.

function rearrange(values, indices) {
    var count = indices.length, index = count, next;

    main: while (count && index--) {
        next = index;
        do {
            next = indices[next];
            if (next > index) continue main;
        } while (next !== index)
        do {
            next = indices[next];
            count--;
            values[index] = [values[next], values[next] = values[index]][0];
        } while (next !== index)
    }
}

function go(values, indices) {
    rearrange(values, indices);
    console.log(values);
}

go(["A", "B", "C", "D", "E", "F"], [4, 0, 5, 2, 1, 3]);
go(["A", "B", "C", "D", "E", "F"], [1, 2, 0, 4, 5, 3]);
go(["A", "B", "C", "D", "E", "F"], [5, 0, 1, 2, 3, 4]);
go(["A", "B", "C", "D", "E", "F"], [0, 1, 3, 2, 4, 5]);
Community
  • 1
  • 1
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

This answer has been updated to satisfy OP's conditions

In this answer there are no temp arrays and ind array doesn't get re-ordered or sorted by any means. All replacing operations are done in a single pass. getItemIndex function receives only a shallow portion of the ind array to work with. It's just done by harnessing all the information hidden in the ind array.

It's key to understand that the ind array keeps all history for us.

We have the following information by examining the ind array.

  1. By looking at the items we find the index map of the corresponding item at arr array.
  2. Each items index tells us how many swaps done before. We get history.
  3. Each items index also tells if there was a previous swap related with the current index position where did the previous element go. We can do like ind.indexOf(i); Anyways here is the code;

I have added a few functions like Array.prototype.swap() to make the code interpreted easier. Here is the code.

Array.prototype.swap = function(i,j){
  [this[i],this[j]] = [this[j],this[i]];
  return this;
};

function getItemIndex(a,i){
  var f = a.indexOf(i);
  return f !=-1 ? getItemIndex(a,f) : i;
}

function sort(arr,ind){
  ind.forEach((n,i,x) => x.indexOf(i) > i ? arr.swap(i,x[i]) // item has not changed before so we can swap 
                                          : arr.swap(getItemIndex(ind.slice(0,i),i),x[i])); // item has gone to somwhere in previous swaps get it's index and swap
  return arr;
}

var arr = ["A", "B", "C", "D", "E", "F"],
    ind = [4, 0, 5, 2, 1, 3];


console.log(sort(arr,ind),ind);

Ok the very final version of this code is this. It is very simplified and includes a test case with 26 letters. Each time you run you will get a different purely random unique indexes map.

Array.prototype.swap = function(i,j){
  i !=j && ([this[i],this[j]] = [this[j],this[i]]);
  return this;
};

Array.prototype.shuffle = function(){
  var i = this.length,
      j;
  while (i > 1) {
    j = ~~(Math.random()*i--);
    [this[i],this[j]] = [this[j],this[i]];
  }
return this;
};

var   arr = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"],
      ind = (new Array(arr.length)).fill("").map((e,i) => e = i).shuffle();
console.log(JSON.stringify(arr));
console.log(JSON.stringify(ind));

function getItemIndex(a,i,j){
  var f = a.indexOf(i);
  return f < j ? getItemIndex(a,f,j) : i;
}

function sort(arr,ind){
  ind.forEach((n,i,x) => arr.swap(getItemIndex(ind,i,i),n));
  return arr;
}
console.log(JSON.stringify(sort(arr,ind)));
console.log(JSON.stringify(ind));

So ok as per the comment from Trincot here it goes with an iterative getItemIndex() function.

Array.prototype.swap = function(i,j){
  i !=j && ([this[i],this[j]] = [this[j],this[i]]);
  return this;
};

Array.prototype.shuffle = function(){
  var i = this.length,
      j;
  while (i > 1) {
    j = ~~(Math.random()*i--);
    [this[i],this[j]] = [this[j],this[i]];
  }
return this;
};

var   arr = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"],
      ind = (new Array(arr.length)).fill("").map((e,i) => e = i).shuffle();
console.log(JSON.stringify(arr));
console.log(JSON.stringify(ind));

function getItemIndex(a,i){
  var f = a.indexOf(i),
      j;
  if (f >= i) return i; // this element hasn't been moved before.
  while (f < i) {       // this element has been swapped so get this elements current index
   j = f;
   f = a.indexOf(f);
  }
  return j;
}

function sort(arr,ind){
  ind.forEach((n,i,x) => arr.swap(getItemIndex(ind,i),n));
  return arr;
}
console.log(JSON.stringify(sort(arr,ind)));
console.log(JSON.stringify(ind));
Redu
  • 25,060
  • 6
  • 56
  • 76
  • You're not meant to mutate `ind` though. – 1983 May 26 '16 at 16:51
  • i get an error: *Invalid left-hand side in assignment* – Nina Scholz May 26 '16 at 16:55
  • Yes but it's only a matter of changing `ind.reduce` to `ind.slice(0).reduce`. In O(1) space are we not allowed to make any copies? – Redu May 26 '16 at 16:56
  • see below answer from me. :( – Nina Scholz May 26 '16 at 16:56
  • @FizzyTea OK `ind` stays the same. No temp array no mutation on `ind` array and a single `forEach` pass for swaps.. – Redu May 29 '16 at 10:49
  • 1
    Notice that this take `O(n²)` time though – Bergi May 29 '16 at 13:39
  • @Bergi Yes true... butO(n) is not OP's condition. He wants best possible solution in O(1) space and no mutation on `ind`. I really can not think of anything not using indexOf or a shim of it. – Redu May 29 '16 at 13:52
  • 1
    I don't think you can use `slice`, as it allocates memory up to the length of the array. Also the recursive call in `getItemIndex` consumes stack space, which in worst case could be `O(n)`. – trincot May 29 '16 at 14:40
  • OK the final stripped down version is https://repl.it/CWXo/5. I don't think it can get any better than this. There is a test with 26 letter scenario and a random index map generator. I will be happy if you check it up. – Redu May 29 '16 at 15:02
  • @trincot It was just for a shallow version of the ind with no intentions to modify it too.. but then thinking like you i removed the slice portion along with some other redundant code that i could find. Now there is an even a very stripped down version of this code at repl.it/CWXo/5 Could you please check it up. I will insert here that one too... – Redu May 29 '16 at 15:07
  • You should rewrite `getItemIndex` so it performs iteration instead of recursion, avoiding the use of **O(n)** memory (stack). It's quite easy to do. Still, that looping in combination with `indexOf` (which also loops in its implementation) could well mean this algo has a worst case of **O(n³)** time complexity. To be analysed... – trincot May 29 '16 at 15:25
  • @trincot Agreed.. but recursive or iteration won't matter, that `indexOf` in `getItemIndex()` has to run the number of times the letter (at the index position that we are analyzing in the forEach loop) had been swapped (moved) previously. Sometimes zero times sometimes more. I don't see any better chance of doing this. I can use a while loop though but it will still run the same number of times. – Redu May 29 '16 at 15:35
  • I am not talking about time complexity, but space complexity: with recursion you use -- in worst case -- **O(n)** (stack space) more than with iteration. – trincot May 29 '16 at 15:47
  • @trincot: I would not bother about this too much. Tail recursion is almost as good as iteration (most compilers handle it properly, in constant space). – Evgeny Kluev May 29 '16 at 15:56
  • @trincot: So fine here we go with a version utilizing an iterative `getItemIndex()` Thanks for your comments. – Redu May 29 '16 at 16:19
  • @EvgenyKluev, true, smart compilers turn it into an iterative mechanism, but in the context of algorithms and complexity, I still think it is good that Redu has turned it into an iterative version and does not rely on the compiler to do so. – trincot May 29 '16 at 16:22
0
var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 0, 5, 2, 1, 3];

function rearrange(arr, ind){
  var map = [];
  for (var i = 0; i < arr.length; i++)   map[ind[i]] = arr[i];
  for (var i = 0; i < arr.length; i++)   arr[i] = map[i];
}

rearrange(arr, ind);

console.log(arr);

That works but, because I'm not a smart developper, I assume it's probably not the fastest algorithm.

kevin ternet
  • 4,514
  • 2
  • 19
  • 27
  • Uses **O(n)** space. – trincot May 30 '16 at 18:42
  • I recognise not knowing what means **O(n)** – kevin ternet May 30 '16 at 18:44
  • 1
    It practically means this algorithm allocates extra memory (`map`) of which the size is linear to the length of the input array. The question is to do it in constant sized memory (**O(1)**), no matter how large the input array – trincot May 30 '16 at 19:00
  • @tricot, If i used **Array.prototype.map()** instead of **for loop** would it be always **O(n)** ? – kevin ternet Jun 03 '16 at 17:52
  • `Array.prototype.map` returns a newly created array, so if you apply it to one of the input arrays, you will allocate **O(n)** extra space still. For it to be **O(1)** you would need to find a way to only use a limited number of simple variables (not arrays), so that you don't use space that becomes larger for larger input, but remains constant no matter how large the input arrays are. – trincot Jun 04 '16 at 07:29
0

Below, one may find a PARTIAL solution for a case when we have only ONE cycle, i.e.

var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 2, 5, 0, 1, 3];

function rearrange( i, arr, ind, temp ){
    if( temp ){
        if( arr[ind[i]] ){
            var temp2 = arr[ind[i]];
            arr[ind[i]] = temp;
            rearrange( ind[i], arr, ind, temp2 );
        }
        else{                                           // cycle
            arr[ind[i]] = temp;
            // var unvisited_index = ...;
            // if( unvisited_index ) rearrange( unvisited_index, arr, ind, "" );
        }
    }
    else{
        if( i == ind[i] ){
            if( i < arr.length ) rearrange( i + 1, arr, ind, temp );    
        }
        else{
            temp = arr[ind[i]];
            arr[ind[i]]=arr[i];
            arr[i] = "";
            i = ind[i];
            rearrange(i, arr, ind, temp );
        }
    }
}

rearrange( 0, arr, ind, "" );

To make this solution to work for a general case, we need to find total numbers of unique cycles and one index from each of them.

For the OP example:

var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 0, 5, 2, 1, 3];

There are 2 unique cycles:

4 -> 1 -> 0 -> 4
5 -> 3 -> 2 -> 5

If one runs

rearrange( 0, arr, ind, "" );
rearrange( 5, arr, ind, "" );

S(he) will get desirable output for the OP problem.

lllllllllll
  • 144
  • 7
  • How, for any given array, would you know which calls to make to `rearrange` to get the final result? – trincot Jun 05 '16 at 09:52
  • @trincot As I said I don't know how to do it effectively. This is a PARTIAL solution for a specific case with only one cycle. It's doable in O(1) space for any given array, but you need to iterate the array for each index except the 1st one and apply a cycle detection algorithm (i.e. Floyd's or Brent's) for all cycles were discovered already. Obviously, that will kill runtime. – lllllllllll Jun 05 '16 at 18:09
0

I'm not sure on the time, but the map function does appear to do what was requested. It's an option, but since I don't know the inner workings of .map then I can't say for sure this is what you're looking for.

var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 0, 5, 2, 1, 3];

arr = ind.map(function(value)
{ return arr[value]; });

Another solution that doesn't use the map function could look something like this:

var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 0, 5, 2, 1, 3];

var temp = [];

for (var i = 0, ind_length = ind.length; i < ind_length; i++) 
{ 
    var set_index = ind[i];
    temp.push(arr[set_index]); 
    delete arr[set_index]; 
}

arr = temp;

This makes good use of space by using the delete option which also keeps the indexes from shifting. Since it's only doing one loop I imagine the execution is rather fast. Since the commands are very basic and simple this should be a viable solution. It's not quite what was asked which was a swap with no extra space used, but it comes pretty close. I'm new to answering questions like this one so please... constructive criticism.

Robert McMahan
  • 531
  • 5
  • 6
  • The array elements are moved as if the value of *ind[i]* indicates where a the value for *arr[i]* should be ***taken from***, but the question is different: the value of *ind[i]* indicates where the value of *arr[i]* should be ***moved to***. Check the output generated by your code and compare it with the desired output mentioned in the question. – trincot Jun 05 '16 at 09:43
  • Also, this solution does not perform **in-place** modification of `arr`. It really creates a new array. If you would create this algorithm as a function (which is what you would eventually would want to do) , to which `arr` would be passed as an argument, then the first solution would not have altered anything in `arr`, and the second would have deleted all elements from `arr`. – trincot Jun 05 '16 at 10:09
  • @trincot I completely misunderstood the request... jeez. I messed that up. I was aware that this wouldn't do in-place modification, however it's the best I could come up with at the time. Thanks for the feedback~ Good points. – Robert McMahan Jun 05 '16 at 23:16
0

Try this:

var result = new Array(5);
for (int i = 0; i < result.length; i++) {
    result[i] = arr[ind[i]];
}
console.log(arr);
Handy
  • 1
  • 2
  • The code should be generic, so `new Array(5)` should better be just `[]`, or `new Array(arr.length)`. But by creating this array, you use **O(n)** space, and the question was to do it with **O(1)** space. – trincot Jun 05 '16 at 09:46
-2

I am using ind as indexes in it's own order

var arr = ["A", "B", "C", "D", "E", "F"];
var ind = [4, 0, 5, 2, 1, 3];
var obj = {}
for(var i=0;i<arr.length;i++)
    obj[ind[i]]=arr[i];
console.log(obj);

Working Fiddle

Netham
  • 1,168
  • 6
  • 16