0

I am trying to implement a function similar to std::next_permutation(std::string w). Please see the code below for how I am doing this:

string biggerIsGreater(string w) {
    // 1. Find the largest non-increasing suffix. This suffix already has the maximum permutation.
    // 2. Assign the char before that as the pivot
    //       if there is no such char, 
    //          then the whole string is non-increasing => no next permutation.
    // 3. Swap the pivot with the smallest element in the suffix that is greater than the pivot itself.
    //      If there are multiple smallest char > pivot, then choose the rightmost one
    //      As this will keep the suffix non-increasing.
    // 4. reverse the order of the suffix, so that it is now non-decreasing.
    // This is the lowest next permutation.
    
    // 1.
    int suffix_start = (int)w.length()-1;
    //single character has no next permutation:
    if (suffix_start == 0) return "no answer";
    
    // else keep decreasing suffix_start until the element is not > the previous.
    while(w[suffix_start-1] <= w[suffix_start]) {
        suffix_start--;
        if(suffix_start==0) return "no answer";
    }
    
    // 2.
    int pivot = suffix_start - 1;
    
    // 3.
    int smallest_char = (int)w.length()-1;
    while(w[smallest_char] <= w[pivot]) smallest_char--;
    if(w[smallest_char] == w[smallest_char-1]) while (w[smallest_char] != w[smallest_char-1]) smallest_char--;
    
    swap(w[pivot], w[smallest_char]);
    
    // 4.
    reverse(w.begin() + pivot + 1, w.end());
    return w;
}

However, this code appears to fail on strings like bb. Please can you tell me where I have gone wrong?

If I print out w after the reversal, I get this: (the first line is the number of test cases): input:

5 
ab 
bb 
hefg 
dhck 
dkhc

Then the program prints ba (which means the first one worked) but nothing else is printed.

So the error is in dealing with bb.

Note: My objective is to write this without std::next_permutation() function from <algorithm>.

rici
  • 234,347
  • 28
  • 237
  • 341
Sanjit Raman
  • 53
  • 1
  • 1
  • 10
  • 1
    *Please can you tell me where I have gone wrong?* -- [What is a debugger?](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems). Not debugging your own code *first*, and not being able to give you (or us) any idea of where the code goes against your plan can get you downvoted. – PaulMcKenzie Apr 02 '22 at 16:35
  • Also, [why not see the possible implementation](https://en.cppreference.com/w/cpp/algorithm/next_permutation)? – PaulMcKenzie Apr 02 '22 at 16:39
  • ok. So if I print out w after the reversal, I get this error: first line is number of test cases: ` 5 ab bb hefg dhck dkhc ` then the program prints ba (which means the first one worked) but then nothing else is printed. So error is in dealing with bb. – Sanjit Raman Apr 02 '22 at 16:39
  • 1
    No, you are giving us an end-user version of what's wrong. You're supposed to go beyond the end-user, and actually technically tell us, in your program, where you believe the error is (after debugging). We're not asking you to give us the solution, just an effort in trying to figure out *in your own code* where things are going wrong. – PaulMcKenzie Apr 02 '22 at 16:41
  • I have a feeling that it is in the process of obtaining the correct index for suffix_start. – Sanjit Raman Apr 02 '22 at 16:43
  • 4
    OK, now instead of guessing what it may be, go to the "what is a debugger" link I posted and debug the code to see where it goes wrong. Sorry to be harsh, but too many posters post code, say it doesn't work, and wait for one the volunteers here to debug the code. This gets really frustrating when the poster shows up hours later (after one of us debugs the code), and replies with "Thanks!" with an upvote. That indicates that the poster did nothing except sit back, have lunch, and wait for us to do the work they're supposed to do. – PaulMcKenzie Apr 02 '22 at 16:45
  • @PaulMcKenzie -- regards to the cpp::reference implementation -- could you explain what all the functions do? everything apart from reverse I am unfamiliar with. – Sanjit Raman Apr 02 '22 at 16:47
  • Thanks. I understand the sentiment -- I'm new to SO haha. I didn't know that you could step through line-by-line. I'll try find how to do that in Visual Studio. – Sanjit Raman Apr 02 '22 at 16:49

2 Answers2

1

I re-implemented your function my own way, if it is not an acceptable answer, then at least it is benefitial for educational purpose. Maybe by my code you can figure out what's wrong in yours.

If this is last permutation, like "bb" case then first lexicographical permutation is returned, same as in std::next_permutation().

Try it online!

#include <algorithm>
#include <iostream>
#include <string>

std::string next_permutation(std::string x) {
    if (x.size() <= 1)
        return x;
    std::ptrdiff_t i = 0, j = 0;
    for (i = x.size() - 2; i >= 0 && x[i] >= x[i + 1]; --i);
    if (i >= 0) {
        for (j = i + 1; j < x.size() && x[i] < x[j]; ++j);
        --j;
        std::swap(x[i], x[j]);
    }
    std::reverse(x.begin() + (i + 1), x.end());
    return x;
}

int main() {
    auto Test = [](auto const & s){
        std::cout << "'" << s << "' -> '"
            << next_permutation(s) << "'" << std::endl;
    };
    Test("ab");
    Test("bb");
    Test("hefg");
    Test("dhck");
    Test("dkhc");
    Test("abc");
    Test("aabb");
    Test("cba");
}

Output:

'ab' -> 'ba'
'bb' -> 'bb'
'hefg' -> 'hegf'
'dhck' -> 'dhkc'
'dkhc' -> 'hcdk'
'abc' -> 'acb'
'aabb' -> 'abab'
'cba' -> 'abc'
Arty
  • 14,883
  • 6
  • 36
  • 69
  • Hi -- thanks. I read through your code and I find it helpful. I think I found where my error was -- can I ask why you pass by reference into the reverse function? Also why do you start at i = size-2? – Sanjit Raman Apr 02 '22 at 17:14
  • `std::reverse` takes iterators or pointers as parameters. `i=x.size()-2` is necessary since the author use `x[i+1]` – IkarusDeveloper Apr 02 '22 at 17:19
  • 1
    @SanjitRaman `std::reverse(&x[i + 1], &x[x.size()]);` is not passing by reference, but passing two pointers, it is not reference syntax but taking-pointer syntax. Instead you can also pass `std::reverse(x.begin() + (i + 1), x.end());` which is more C++ style, but I passed pointers only because they work faster than iterators always. I start at `size - 2` because I compare `x[i] >= x[i + 1]`, so these two array accesses to be valid (not out of bounds) you have to start `i` from `size - 2`. – Arty Apr 02 '22 at 17:20
  • 1
    @Arty did u test it with an empty string? i think `std::reverse` used with no check about `i>= 0` may cause undefined behavior – IkarusDeveloper Apr 02 '22 at 17:31
  • @IkarusDeveloper Just now added first two lines to function, to solve correctly case of empty string. Also single character string can be solved this way too for free. – Arty Apr 02 '22 at 17:32
1

This is @Arty's solution. So full credit to him.

I added comments to try and explain how it works so that I can understand it better.

#include <string>
#include <iostream>
#include <algorithm>

std::string next_permutation(std::string x) {
    std::ptrdiff_t i = 0, j = 0;
    

    // start with penultimate element
    // as long as i doesn't hit the start and the sequence is non-increasing, keep decreasing i.
    // the value of i we reach is the first element from the right which is not in reverse order (=> the maximum permutation)
    // this is the pivot
    for (i = x.size() - 2; i >= 0 && x[i] >= x[i + 1]; --i);
    

    // if the whole array is reverse order, there is no maximum permutation.
    if (i < 0)
        return {};
    
    // then find the first element after i which is less than x[i].
    for (j = i + 1; j < x.size() && x[i] < x[j]; ++j);
    // stop at the next element -- I like this as it avoids the problem of acccb, if a is the pivot
    // then this code will stop at the first c.
    --j;
    // swap the elements
    std::swap(x[i], x[j]);

    // reverse the remaining array in order to minimise it, as we know it is in descending order.
    std::reverse(&x[i + 1], &x[x.size()]);
    return x;
}

int main() {
    auto Test = [](auto const& s) {
        std::cout << "'" << s << "' -> '"
            << next_permutation(s) << "'" << std::endl;
    };
    Test("abc");
    Test("bb");
    Test("aabb");
    Test("cba");
}
Sanjit Raman
  • 53
  • 1
  • 1
  • 10
  • Thanks a lot. Please put a look that I changed code a bit from time you took it. Specifically, in case of last permutation, now instead of empty string I return first lexicographical permutation, because it is how [std::next_permutation()](https://en.cppreference.com/w/cpp/algorithm/next_permutation) behaves. – Arty Apr 02 '22 at 17:22
  • also what does ptrdiff_t do? – Sanjit Raman Apr 02 '22 at 19:09
  • `ptrdiff_t` is just another way of saying "I need same type as `size_t` but signed". Although sometimes `ptrdiff_t` has different bit-size than `size_t`, but most often it has same bit size. You could use `int` (as it is signed) but int has fixed size and sometimes (on Windows) it is 32 bit in size, while `size_t` is 64 bit (on 64 bit compilation). And `size_t` is designed to access whole available memory, on 32 bit machines it is 32 bit in size, on 64 bit machines it is 64 bit in size. Same size has `ptrdiff_t`, 32 or 64. – Arty Apr 03 '22 at 07:43
  • `size_t` holds the size of objects, which can be limited a lot more than the address space. The standard only demands at least 16 bits. Similar for `ptrdiff_t`, which is only specified within the same object, e.g. two array elements of the same array. That is, why `size_t` is used e.g. for sizes or indices of `std::vector`. For holding pointers or their differences over their full value range as int there are the `uintptr_t` and `intptr_t` types. – Sebastian Apr 03 '22 at 08:13