1

Suppose we start with some array of indices. I want some function

int* getOrdering(int* idxs, int len);

which is equivalent to this process (in pseudocode):

input: int* idxs, int len
std::vector<int> a = {1,2,...,len+1};
out = {0,...,0}; // empty initialized array with length len
n = 0;
while n < len:
    out[n] = a[idx[n]]; // first iteration sets out[0] to a[idxs[0]] = 4.
    a.erase(a.begin() + n);
    ++n;

For getOrdering({3,0,0,2,0}, 5):

n idx[n] a out
0 3 {1,2,3,4,5,6} {4}
1 0 {1,2,3,5,6} {4,1}
2 0 {2,3,5,6} {4,1,2}
3 2 {3,5,6} {4,1,2,6}
4 0 {3,5} {4,1,2,6,3}

I was wondering if anyone has a better algorithm than this for finding this ordering, because my application will be running this in the order of O(len!) times. We can always assume the indices input are are always correct and will never result in error, i.e. idx[n] is always in the range [0,len-n).

Further, does the vector erase operate in O(1)? If not, is there a better data structure to use to keep this process to O(1)?

Floyd Everest
  • 268
  • 2
  • 11
  • Does the vector order matter? If it doesn't, you can swap with the back element, then pop_back. – Eljay Jun 14 '21 at 12:10
  • its not quite clear if your indexing takes into account the shrinked size of `a`, ie after calling erase on it, some elements, which before were at index `i` are then at index `i-1`. (on the other hand, if not, you wouldn't need `a` in the first place). Can you show example input and example output? – 463035818_is_not_an_ai Jun 14 '21 at 12:11
  • @Eljay It matters, if we swapped it would change the order of the output. – Floyd Everest Jun 14 '21 at 12:11
  • @463035818_is_not_a_number I did show an example in the last paragraph, and yes we can assume the indices are always correct, i.e. `idx[n]` is always in the range `[0,len-n)`, – Floyd Everest Jun 14 '21 at 12:13
  • The `output {4,1,2,6,3}` does not seem correct to me.... – CiaPan Jun 14 '21 at 12:19
  • @CiaPan It is correct, I added a worked solution. – Floyd Everest Jun 14 '21 at 12:32
  • 1
    I think you can avoid actually creating any intermediate containers by calculating the indices directly. I am just not sure how to do that efficiently. For example `3 0 0 ...` becomes `3 0 1 ...` because you know that there is `1` index `<= 0` before that `0`, while `3 0` need not change – 463035818_is_not_an_ai Jun 14 '21 at 12:44
  • 1
    how big is `len` ? Erasing middle elements from a `std::list` is cheaper than erasing from a `std::vector`, but then a `std::list` is often worse overall – 463035818_is_not_an_ai Jun 14 '21 at 12:46
  • @463035818_is_not_a_number That's what I've been trying to consider, and the solution I'm looking for will be an analytical solution like this I imagine. – Floyd Everest Jun 14 '21 at 12:47
  • @463035818_is_not_a_number len will probably range from 4-30. – Floyd Everest Jun 14 '21 at 12:48
  • 1
    @FloydEverest I would not worry about the algorithmic complexity of a `std::vector` for ranges smaller than a few hundreds elements at a minimum (depending on the algorithm, a few tens of thousands, even). –  Jun 14 '21 at 12:55
  • @Frank this process is being called O(len!) times, so even if the complexity of `a.erase(a.begin() + n);` is O(len), it increases my function complexity to O(len^2), so the overall program complexity becomes O(len!len^2). Hence why it's important to improve it to O(len), or even O(len log(len)). – Floyd Everest Jun 14 '21 at 12:58
  • 1
    @FloydEverest, Sorry, I should have been clearer. Finding a better algorithm is absolutely worthwhile. It's replacing the `std::vector<>` for some other container because `erase()` is not O(1) while keeping the same algorithm that's not worth it at the scales being discussed. –  Jun 14 '21 at 12:59
  • @Frank Oh! That makes more sense - my bad for jumping to conclusions. – Floyd Everest Jun 14 '21 at 13:06
  • 1
    Maybe you might change your `O(len!)` algo to produce array directly usable. Which is a magnitude of `len` BTW? – Jarod42 Jun 14 '21 at 13:12
  • Is that program failing if i pass the input ```{0,0,0,0,0,3},5``` even it is valid according to your condition **idx[n] is always in the range [0,len-n)** – Roshan M Jun 14 '21 at 13:37
  • 1
    @RoshanM: `a[5] >= 6 - 5`, so invalid. – Jarod42 Jun 14 '21 at 13:47
  • Can you have a routine that converts the ordering into an indexing? So `{3,0,0,2,0}` would become `{3,0,1,4,2}`? That would allow you to avoid the erase overhead. – Eljay Jun 14 '21 at 13:55
  • @Eljay any algorithm which produces a correct output in under O(n^2) is an improvement - how would you convert the input into an indexing like that? And from that indexing how would you obtain the output? – Floyd Everest Jun 14 '21 at 13:58
  • @Jarod42 I have attempted this, but any attempt of mine required an increase in memory complexity of O(len!*len). Memory is more constrained than time for me in this case. – Floyd Everest Jun 14 '21 at 14:00
  • 1
    `O(len!)` make think you want to iterate over permutations, [`std::next_permutation`](https://en.cppreference.com/w/cpp/algorithm/next_permutation) exists. and there are in SO code for `next_combination`. – Jarod42 Jun 14 '21 at 14:24
  • @463035818_is_not_a_number `vector` vs. `list` makes no difference here. `a.erase()` would be faster, but `a.begin() + n` would be slower by the same amount. – Mark Ransom Jun 14 '21 at 14:35
  • @Jarod42 yes and no - I'm traversing a permutation tree, and I need the corresponding permutation at each leaf. – Floyd Everest Jun 14 '21 at 14:39
  • @Jarod42 for which there is a very simple recursive method outlined [here](https://www.geeksforgeeks.org/write-a-c-program-to-print-all-permutations-of-a-given-string/), which actually solves my issue... However, I'm going to leave this question open for people who may wish to solve a similar issue in the future, since it appears not to have been asked before. – Floyd Everest Jun 14 '21 at 15:01
  • The reason it hasn't been asked before is that it's not a common problem. Usually when you have a list of indexes, those indexes don't assume the array they're indexing will be shrinking as you work with it. – Mark Ransom Jun 14 '21 at 15:03
  • Actually if I use the method I linked earlier, will need the exact inverse to the function I described earlier, so I wonder which is easier. – Floyd Everest Jun 14 '21 at 15:12
  • 1
    @FloydEverest: You might be interested by that [Demo](http://coliru.stacked-crooked.com/a/d8aafab8919c23e5) with [next_partial_permutation](https://stackoverflow.com/a/61402151/2684539). – Jarod42 Jun 14 '21 at 16:02

1 Answers1

0

If I get it correctly, you want to create an array telling what rank in an ascending order each element of the input array idx has.

For this you can simply sort the idx array, during which you perform exactly the same copy/move/swap operations on a which are performed on idx. Then for all 0 ≤ i ≤ len–1 set output[a[i]] = i, which is 'at the original position of each value store its sorted position'.

CiaPan
  • 9,381
  • 2
  • 21
  • 35
  • Hi! thanks for the possible solution, can you show how this works for the example I gave? For that example, I believe the sorted indices for `{3,0,0,2,0}` is `{4,0,1,3,2}`, which isn't the correct solution. Am I correct? – Floyd Everest Jun 14 '21 at 12:34
  • 1
    So I must have misunderstood your needs. :( – CiaPan Jun 14 '21 at 12:35