9

It must be noted here that I performed the mathematics by hand on paper to derive the foregoing proofs. I am not sure if the proofs would have become apparent by solely using the medium of the modern computer.

The definition of "efficiency" as used below meaning completing a discrete portion of the algorithm, or the entire algorithm in the least amount of time. Both as to mathematically and, programmatically, or computationally.

While further examining procedures for generating the next lexicographic permutation from the original set or current permutation, following re-reading the Answer by @chqrlie at Permutations without recursive function call, I began the inquiry by writing the indexes on paper in an attempt to observe any mathematical relationships between the indexes that could be utilized to perform the specific task.

I discovered several interesting truths, the proofs thereof demonstrated below.

When we write, for example, the values

a,b,c

or

abc

or, instead write the indexes representing the values

0,1,2

or

012

Since we know, given a set

abc

that we can generate the next lexicographic permutation by swapping the last two values or indexes of the set

acb

or

021

we can ignore the values, which could be any type of data, and concentrate on using the indexes for our examinations, as discrete numbers are more suited for correlating possible relationships than a possibly infinite amount of diversified values.

Therefore, with the second lexicographic index of the original set

abc

we can denote the values of the indexes as numbers, and observe

0,2,1

or

021

The first indexes being

012

the second being

021

Since we know the .length of the original set is 3, if we remember the .length of the original set of values, we can remove the starting

0

where to reduce the indexes to the number

12

and

21

respectively. Where the 0 can be referenced as the index from original set to get the value at index 0 when the resulting set of the next operation is less than the original set.

When we attempt to graph potential relationships between 12 and 21, we find that

21 - 12 = 9

When we continue, we find that the next lexicographic indexes are

102

subtracting the previous indexes

102 - 21 = 81

where 102 is the next lexicographic permutation, represented as the values

bac

which provides us with the common relationship between the indexes being the number nine, represented numerically as

9

This relationship is evident and reproducible for an infinite set of input values represented as numbers. We can graphs of the relationships, which, depending on the perspective of the observer, can be viewed as two slopes, with an inverted apex offset when beginning the graph from the first value of the set of resulting permutations

// graph 1
0,9,81

// graph 2
abc 012 0
acb 021 1 9
bac 102 2 81
bca 120 3 18
cab 201 4 81
cba 210 5 9

 /\/\
/    \

We can observe here that the number on the graph at the inclination slope is identical to the number at the correpsonding declination slope, following the inversion of the number of the divided total number of possible permutations divided by half, where for example, for a total of six permutation, we subtract one, leaving remainder of five, remembering that we still need the odd set of indexes, we use the number at the inverted apex as a pivot, leaving four indexes, which we denote as inclination and declination slopes, respectively.

We observer here that the numbers on the graph of the declination slope are identical to the correponding adjacent angle of the inclination slope at the same y coordinate.

Thus,below I demonstrate the proof that an infinite set of permutations given an infinite input set can be calculated, generated, filtered, derived by utilizing addition or multiplication of the number nine

9

by matching numbers which include only the number of the index of an input number, without duplicate in the set.

Further, I demonstrate the proof that need only the indexes as numbers on inclination slope, or the total amount of possible permutations divided by two plus one, are needed to derive the total number of permutations of a given input.

As stressed at the preface of this post, this calculations could perhaps not have been possible without many hours of doing the math by hand on paper. The media screen simple does not provide the same medium as composing the characters one by one on paper; with the ability to view the paper from various physical dimensions.

The expression of the algorithm using a coding language is another task unto itself.

The following is a progression of the discoveries, proofs and expressions thereof implemented using the "JavaScript" programming language. The first version has a RegExp that is not accurate as to the expected result, as pointed out by @Tushar, with corrected RegExp, though incorrect RegExp returns same result.

Given input as array

var arr = ["a", "b", "c", "d"];

// version 1

function getNextLexicographicPermutation(arr) {
  var len = arr.length;
  var idx = arr.map(function(_, index) {
    return index
  });
  var re = new RegExp("[" + len + "-9]|(.)(?!=\\1)");
  var n = Math.pow(10, len - 1);
  var p = 9;
  var last = Number(idx.slice().reverse().join(""));
  var res = [idx];
  var curr = Number(res[res.length - 1].join(""));
  while (curr < last) {
    for (var prev = curr, next, k; prev <= last; prev += p) {
      if ((re.test(prev))) {
        k = String(prev);
        if (k.length >= len - 1) { //  && Math.max.apply(Math, j) < len
          next = [];
          for (var i = 0; i < k.length; i++) {
            if (next.indexOf(Number(k[i])) == -1 
              && idx.indexOf(Number(k[i])) !== -1) {
                next.push(Number(k[i]))
            }
          }
          if (prev < n && next.length === len - 1 
             || next.length === len && prev > curr) {
               res[res.length] = next.length < len 
                                 ? [0].concat.apply([0], next) 
                                 : next;
          }
        }
      }
      curr = prev;
    }
  };
  return res.map(function(value) {
    return value.map(function(index) {
      return arr[index]
    })
  })
}

getNextLexicographicPermutation(arr);

The graph for numerical difference between the numbers as indexes for the array arr will be

// graph 3
// reflecting input `abcd`
[9,81,18,81,9,702,9,171,27,72,18,693,18,72,27,171,9,702,9,81,18,81,9]

// version 2.0 without using `RegExp`

function getNextLexicographicPermutation(arr) {
  var len = arr.length;
  var idx = arr.map(function(_, index) {
    return index
  });
  var n = Math.pow(10, len - 1);
  var p = 9;
  var last = Number(idx.slice().reverse().join(""));
  var res = [];
  var curr = Number(idx.join(""));
  var prev, k, next;

  while (curr <= last) {
    prev = curr;            
    k = String(prev).split("").map(Number);
    if (k.every(function(v) {
        return idx.indexOf(v) !== -1
      }) && k.length >= len - 1) {
      next = [];
      for (var i = 0; i < k.length; i++) {
        if (next.indexOf(Number(k[i])) == -1 
          && idx.indexOf(Number(k[i])) !== -1) {
            next.push(Number(k[i]))
        }
      }
      if (prev < n && next.length === len - 1 
          || prev > n && next.length === len)) {
        res[res.length] = next.length < len 
                          ? [0].concat.apply([0], next) 
                          : next;
      }
    }
    prev += p;
    curr = prev;
  };
  return res.map(function(item) {
    return item.map(function(v) {
      return arr[v]
    })
  })
}
getNextLexicographicPermutation(arr)

The efficiency of the second version was greatly improved over the first version by substituting bitmask for RegExp by Answer of @lleaff at Most efficient method to check for range of numbers within number without duplicates.

The relevant profiles generated by DevTools between the RegExp version and bitmask version should be reprodicible at chromium browser, however due to neglecting commenting the exact tests performed, am not able to precisely reproduce the numbers and times posted without devoting more time to verifying the numbers posted at previous Question. Cannot remember precisely, though browser tab may have crashed when .length of input set was ten. What was important was that the bitmask test version was more efficient than RegExp test version.

// version 2.1, substituting bitmask for `RegExp`

function getNextLexicographicPermutation(arr) {
  function checkDigits(min, max, n) {
    var digits = 0;
    while (n) {
      d = (n % 10);
      n = n / 10 >> 0;
      if (d < min || d > max || (digits & (1 << d)))
        return false;
      else
        digits |= 1 << d;
    }
    return true;
  }
  var len = arr.length,
    idx = arr.map(function(_, index) {
      return index
    }),
    p = 9,
    min = 0,
    max = len - 1,
    last = Number(idx.slice().reverse().join("")),
    res = [],
    curr = Number(idx.join("")),
    next;

  while (curr < last) {
    next = (curr += p);
    if (checkDigits(min, max, next)) res[res.length] = next;
    curr = next;
  };

  return res.map(function(item) {
    var item = String(item).split("").map(Number);
    item = item.length < arr.length ? [0].concat(item) : item;
    return item.map(function(index) {
      return arr[index]
    }).filter(Boolean)
  })
}    
getNextLexicographicPermutation(arr);

The notes and process took the better part of a year, over a year ago, to show and prove. Have mainly simply thought how to get indexes for either side of slope simultaneously, using only inclination slope indexes, rather than coding the algorithm therefore.

The bulk of the math was in trying to derive a further correlation between the adjacent multiples of the number nine, for the ability to calaculate the exact next multiple of the number nine

9 

for incrementing a number by nine then filtering duplicate values from the resulting number. I have not yet been able to decipher the inter-relationships between the adjacent multiples of nine on the inclination slope to the degree that multiplication or division could be substituted for addition and exclusion.

Decided to finally create proof of concept for the proposition of generating an infinite number of permutations from an infinite input set, using only the inclination slope, or only the indexes as numbers, of the first half of possible permutations plus one.

// version 3, generate second half of permutations using indexes of first 
// half of permutations

function getNextLexicographicPermutation(arr) {

    for (var l = 1, i = k = arr.length; l < i ; k *= l++);

    function checkDigits(min, max, n) {
        var digits = 0;
        while (n) {
            d = (n % 10);
            n = n / 10 >> 0;
            if (d < min || d > max || (digits & (1 << d)))
                return false;
            else
                digits |= 1 << d;
        }
        return true;
    }

    var len = arr.length
    , idx = arr.map(function(_, index) {
        return index
    })
    , p = 9
    , min = 0
    , max = len - 1
    , last = Number(idx.slice().reverse().join(""))
    , curr = Number(idx.join(""))
    , res = []
    , diff = []
    , result = []
    , next;

    while (res.length < (k / 2) + 1) {
        next = (curr += p);
        if (checkDigits(min, max, next)) res[res.length] = next;      
        curr = next;
    };

    for (var i = 0; i < res.length; i++) {
      var item = res[i];
      item = String(item).split("").map(Number);
      item = (item.length < arr.length ? [0].concat(item) : item)
             .map(function(index) {
                return arr[index]
             }).filter(Boolean);
      result.push(item)
    }

    res.reduce(function(a, b) {
      diff.push(b - a);
      return b
    });

    for (var i = 0, curr = res[res.length - 1], n = diff.length - 2
        ; result.length < k;  i++, n--) {
          curr = curr + diff[n];
          result.push(
            String(curr).split("")
            .map(function(index) {
                return arr[index]
            })
          );
    }
    return result;
}
getNextLexicographicPermutation(arr);

Another eventual step in development of the algorithm will be given an arbitrary .length input, to be able to calculate the indexes, and thus the values of the nth permutation of the set mathematically; by using only a single formula of multiplication, division, algebra, trigonotetry or calculus.

Please include reproducible benchmarks within Answer. The reason being is that cannot remember exactly how I derive the numbers for Profiles at DevTools, though if remember correctly used console.time(), console.timeEnd() and console.profile() at beginning and end of respective portions where used. Backup your experiments; you never know if or when a hard drive or OS will crash. You can generally retrieve the data from the disk, though at cost of time and effort to do so. Save your tests in same fashion you save versions of algorithms as well, for ability to reproduce yourself and for others to reproduce. The full gamut of the original tests are lost.

The surviving remnants of the test for how many permutations could be derived before browser tab crashed are only comments retrieved from a different OS

// 362880 876543210 876543219 
var arr = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];

where if recollect accurately, when "j" was added to the array, the active tab at chromium crashed. The first number is the total amount of permutations for the set "a" through "i"; the next two number are probably, though not certain, the results of two tests for one of the versions before version 3, which composed today.

That is another reason for now posting the above disclosure here at stackoverflow.com, to preserve the principals of the algorithm and work on the code done so far, less some catastrophe destroy all of the original notes and work; for example, being awake for several days in a row trying to interpret patterns and relationships between numbers; neglecting to document all of the specific test patterns tried attempting to port the algorithm to code at comments within code; or as @JaromandaX described the circumstance "PEBCAK".

Fresh eyes can probably see the algorithm from a different perspective as to efficiency.

We can reproduce a graph of the results from some of the preserved code versions above, for example using console.time(), console.timeEnd(), performance.now() or other appropriate tests involving time to complete the function, which can be reproduced.

// console.time("version N");
// getNextLexicographicPermutation(arr);
// console.timeEnd("version N");

var tests = [];

for (var i = 0; i < 10000; i++) {
  var from = window.performance.now();
  getNextLexicographicPermutation(arr);
  tests.push(window.performance.now() - from);
}

for (var i = 0, k = 0; i < tests.length; i++) {
  k += tests[i];
}

var avg = k/tests.length;

// version 1 `avg`: 0.2989265000001993
// version 2.0 `avg`: 0.8271295000007376
// version 2.1 `avg`: 0.17173000000003666
// version 3 `avg`: 0.12989749999987543

As a footnote, I will mention that I used the principals of the algorithm to derive the expected results at Fastest way to generate all binary strings of size n into a boolean array?, where again, the number nine appeared as a key number within the inclination slope, and the matching angle of declination is also observable. Though did not perform futher tests and automation of the specific form of input and result described at that Question. The Answer was a proof of concept as to the viability of the approach of ignoring the values and using only a wave-like incrementing pattern applied to a single number initial number, zero, to derive infinite permutations of a set.

Questions:

  1. How can the algorithm be improved as to efficiency; both computationally and mathematically?

  2. Can we create the indexes for both inclination and declination slope at the same time; rather than determining the declination slope after the inclination slope has been calculated?

    1. Are there a mathematical relationships or patterns between the indexes as numbers, that is, for example, the set of numbers at graph 1, graph 2 or graph 3, derived from the input of any four values, for example

      abcd

    or

    ["a", "b", "c", "d"]
    

    that the author has not yet to recognize; which could be used to further reduce the number of computations currently implemented to derive results?

Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
  • 2
    @ibrahimmahrir It is. I chose stackoverflow.com to publish my discoveries, progressions of implementations using `javascript`, and findings. The hand written notes and mathematical calculations are too numerous and varied to attempt to compile for now. The Question itself took around seven hours to compose. – guest271314 Apr 08 '17 at 05:23
  • If a loops take too long to run, the Clients Browser will have issues. – StackSlave Apr 08 '17 at 05:37
  • @mplungjan Not seeking review of the code. The algorithm is language agnostic. Happen to be currently most familiar with the JavaScript programming language, which is used to implement the code at Question. Seeking how to improve efficiency of the algorithm; whether using JavaScript, or other programming languages. A few fresh sets of eyes to perhaps glean different perspectives as to the relationships between the numbers and the wave pattern on the graph itself. The efficiency as to code is a nominal part of the Question; though possible improvements at the code level will also be helpful. – guest271314 Apr 08 '17 at 05:50
  • 1
    I'm voting to close this question as off-topic because questions about improving the design of code belong on CodeReview.stackexchange.com. – Barmar Apr 08 '17 at 06:01
  • @Barmar The Question is only nominally about code. The gist of the Question is how the algorithm itself can be improved. JavaScript just happens to be the language I am currently most familiar with. Though improvements as to the efficiency of code portion will be most welcome. At the moment, nearly spent as to novel approaches to improve the mathematical portion, following over a year of just thinking about the components before attempting to improve upon the algorithm. Thought about using only the inclination slope to derive all permutations for nearly a year before writing today. – guest271314 Apr 08 '17 at 06:03
  • I don't think it makes a difference whether it's about code or the underlying algorithm. SO is for help with fixing code that doesn't work, CR is for getting advice on improving code that does work. – Barmar Apr 08 '17 at 06:08
  • 2
    @Barmar Then all questions posted where topic is "efficiency" should be removed to codereview, though they clearly are not. Every users' vote is theirs alone to do with as they select. Posted the Question here as the users at SO are highly intuitive and apt at implementing various algorithms. If removed to codereview may as well delete the question and publish the math portions in some peer review journal or just compose a white paper. The code is not that important to this user. Though am interested in finding the route to results which consume the least amount of discrete resources, and time – guest271314 Apr 08 '17 at 06:15
  • Lots of efficiency questions are of the form "Why is X more efficient than Y in language Z?", those don't belong on CR. And some are about basic programming techniques like adding indexes in databases. But questions like yours really should be moved to CR. You have a well thought out algorithm and you're looking for people to review it and suggest improvements. That's exactly the purpose of CR. I'm not sure why you're viewing this as a criticism. – Barmar Apr 08 '17 at 06:23
  • @Barmar Did not state that viewed your vote as a criticism. Your vote is yours alone to do with, or do nothing with as you please. Only applying comparative analysis and logic to the evaluation of reason described for the vote, for example searching stackoverflow.com for "efficiency" and "algorithm". Researched perhaps hundreds of university and discrete math articles relating to permutations. Can not recall one which used the number `9` to generate the set of permutations. Decided to share the findings here, implemented in JavaScript, as the algorithm Questions and Answers here at SO are A1 – guest271314 Apr 08 '17 at 06:39
  • All I can say is that I haven't run into them, I guess because questions about discrete math aren't in my favorite tags. You tagged this Javascript, so it showed up in my search, and I commented. – Barmar Apr 08 '17 at 06:43
  • @Barmar Considered publishing the findings at journals, though ironically was reminded by @ jonrsharpe, at the relevant portion of commentary _"SO is for reference-quality material on an engineering subject"_ . To an appreciable degree it does not matter if the Question is moved or not at this point; the relevant wave length has already been emitted into the universe. The algorithm is already published here at stackoverflow.com at 2017-04-08 05:18:12Z – guest271314 Apr 08 '17 at 06:43
  • @Barmar _"You tagged this Javascript, so it showed up in my search, and I commented."_ Then do share your experience with JavaScript in implementing the most efficient JavaScript version of the algorithm. – guest271314 Apr 08 '17 at 06:46
  • NO, the whole point of my comment is that this isn't the appropriate place to "share my experience". I have no idea how to implement this algorithm, I haven't even read through your huge treatise or thought about the algorithm. – Barmar Apr 08 '17 at 06:50
  • @Barmar _"I haven't even read through your huge treatise or thought about the algorithm."_ ? Though you vote to "close"? Do you also "vote" on other matters without reading the full text of the document? I had best stop now, as that last revelation is, to me, repugnant to civil responsibility and conduct. – guest271314 Apr 08 '17 at 06:53
  • I voted to close because I believed it was off-topic, based on the general type of question you were asking. You made it very clear at the outset what you were asking, I didn't think I needed to read the whole thing. – Barmar Apr 08 '17 at 06:55
  • @Barmar _"I didn't think I needed to read the whole thing."_ You always need to "read the whole thing". If I had not "read the whole thing" I would not be aware of the root of many of the words people wantonly use which they know not the meaning of whatsoever; and the significance of "tenants at halves" being reduced to progressively worse conditions due to contracts they perhaps did not read themselves, if they knew how to read at all. Though many do not read those parts. At least you provided your reason for your vote, which does demonstrate transparency as to your position. – guest271314 Apr 08 '17 at 07:03
  • @Barmar Note also Question 2). Where I have not yet, and am still attempting to, derive both inclination slope permutations and declination slope permutations at the same. For example, since we know that `cab 201 4 81` is at the corresponding y coordinate at the declination slope, we should be able to determine `cab 201 4 81` at the same time that we determine `bac 102 2 81`; without having to use the `.reduce()` and following `for` loop portion of JavaScript at version 3. There is still an unresolved coding portion of original Question. – guest271314 Apr 08 '17 at 07:36
  • As a suggest for long questions. I find that if I add a `Supplement` or `TL;DR` section to my long questions they tend to get a better reception e.g. [How to enumerate combinations using DCGs with CLP(FD) and multiple constraints](http://stackoverflow.com/q/42672512/1243762) or [How is a integer created as a character code constant?](http://stackoverflow.com/q/41637402/1243762) – Guy Coder Apr 08 '17 at 11:33
  • When I generate permutations it is so that I can use them for test cases or input into some other algorithm. It is the algorithm that uses the permutations that I want optimized, why are you so interested in optimizing the generation of the next permutation? – Guy Coder Apr 08 '17 at 11:58
  • Of interest: [Prolog - Nth Permutation of 3 elements](http://stackoverflow.com/q/18306324/1243762) – Guy Coder Apr 08 '17 at 12:35
  • I don't know if you are aware of Prolog and definitive clause grammar [DCG](https://stackoverflow.com/tags/dcg/info) but when in comes to generating permutations or data based on BNF, it is very handy and faster than writing imperative code. – Guy Coder Apr 08 '17 at 12:36
  • @GuyCoder Have not tried `DCG`, `prolog`. – guest271314 Apr 08 '17 at 17:15
  • Note, version 1 implementation is not accurate, where resulting `.length` is `25` instead of `24` for input of set of four elements. Correction is `var res = [];var curr = Number(idx.join(""));`, as evidenced at version 2. – guest271314 Apr 08 '17 at 17:49
  • @procrastinator The values of the set are ignored at algorithm at Question. There exists a relationship between the indexes of the input set. That is, the indexes can be viewed as a single whole number which increases in numerical value. Filter the indexes as whole number for numbers which match original input indexes. – guest271314 Apr 08 '17 at 19:07
  • It's obvious that the indexes can be viewed as an increasing number since permutations are sorted in lexicographic order :-3 Sorry for not being clear, let me rephrase my question. Did you find a way to get the next multiple of nine (9, 81, 18, 81, etc...) for arbitrary numbers made of consecutive digits (21, 123, 324, etc...)? In other words, do you have a function that returns the multiple of nine required to reach the next permutation? Something like `diffWithNextPermutation(324) -> 18`? –  Apr 08 '17 at 20:01
  • @procrastinator _"do you have a function that returns the multiple of nine required to reach the next permutation?"_ Not directly. Indirectly by incrementing the initial indexes as a whole number using `RegExp` then binary mask approach, which checks current number to determine if any all of the individual numbers within the whole number are each discrete indexes contained within original array, and without duplicate individual numbers within the whole number; if `true`, each digit of the whole number is used as the reference index for the current permutation, if `false` increment until `true` – guest271314 Apr 08 '17 at 22:10
  • @procrastinator It would be more efficient to not have to increment and check for condition described above, but rather, determine the precise numeric relationship between the current whole number and the next whole number which corresponds to the indexes of the next lexicographic permutation of original input. The graph between for example, `9, 702` is not linear, and changes depending on the `.length` of input. Tried a number of mathematical approaches trying to decipher the precise relationship between the difference of adjacent whole numbers. So far have discerned the proofs at OP. – guest271314 Apr 08 '17 at 22:17
  • @procrastinator There are more truths to be discerned mathematically as to the pattern. So far I have determined that all of the whole numbers corresponding to the permutations are divisible by the number `9`, though have not yet been able to calculate the precise wave pattern which would allow the direct number to add or multiple of nine to increment from for example, `9` directly to `702`, without current need to filter each increment of nine between the discrete numbers. There is a pattern there. It is wave-like, having apparent symmetrical and asymmetrical properties expressed on a graph – guest271314 Apr 08 '17 at 22:27
  • I did the same observations, but it seems to me that the sequence of multiples of nine depends more on the distance between the letters than on the length of the input. That's why I prefer to work with *numbers made of consecutive digits*, it is easier to predict. Anyway, considering your last comments, I believe that my answer is finally perfectly acceptable. –  Apr 08 '17 at 22:27
  • @procrastinator _"I believe that my answer is finally perfectly acceptable."_ How is your Answer related to algorithm at Question? Your Answer presently does not return expected result depending on the input values as demonstrated at jsfiddle links at last comment at your Answer. Nor does your Answer address algorithm at Question whatsoever. Your approach uses the input value itself within the processing model, the algorithm at the Question does not use input values within processing model. – guest271314 Apr 08 '17 at 23:04
  • @procrastinator Interestingly for input `[1, 5, 2, 7]` version 3 returns result having `.length` `24`, though with incorrect values, including `undefined`. Version 2.1 returns result `.length` that is `23`, though should be `24`. Your last updated Answer returns `.length` `22` – guest271314 Apr 08 '17 at 23:57
  • @procrastinator Correction for version 2.1 is `res = [idx]`, to return `.length` of `24` for input `[1, 5, 2, 7]` – guest271314 Apr 09 '17 at 00:18
  • @Barmar There is a bug at version 3 – guest271314 Apr 09 '17 at 00:27
  • @Barmar Found bug, at version 3, correction is `res = [curr]` – guest271314 Apr 09 '17 at 00:38
  • @procrastinator Correction for bug at version 3 is `curr` as first element of `res`; `res = [curr]`, else `NaN` is returned at last permutation, resulting in one or more values being `undefined` at returned array, as `res[0]` would be an array, not a number, when `.reduce()` is reached to get difference between numbers. After correction, input of `[1, 5, 2, 7]` should return resulting array having `.length` `24`. The updated `javascript` at your Answer still returns resulting array having `.length` `22` for input `[1, 5, 2, 7]`, where the resulting `.length` should be `24`. – guest271314 Apr 09 '17 at 00:50
  • 4
    @OK you've worn me down, I retracted my close vote. Now stop sending comments to me about this. – Barmar Apr 09 '17 at 01:27
  • "*How is your Answer related to algorithm at Question?*". Read *TL;DR* again at http://stackoverflow.com/a/43302308/1636522. I had the same feeling but hard to express myself (lack of maths and english skills). –  Apr 09 '17 at 06:23
  • What portion of what Question asks is not clear? – guest271314 Apr 10 '17 at 05:09
  • It should be noted here that "graph 3" is not a consequence of the relationship between permutations. "graph 3" is a map of the generation of permutations using a whole number beginning at 123, continuing until 3210, by adding the number nine to the initial number, accruing the sum if a function call returns `true` at each new summation. We can derive 132 from 123 by simple addition 123 + 9 = 132 at indexes 0 to 1. The question is how do we mathematically determine that we need to, and how to add precisely 702 to 321 at index 5 to derive sum 1023 at index 6, instead of accruing sum by only 9? – guest271314 Apr 11 '17 at 04:02
  • The expected result is that for the maximum possible number of permutations `N`, `N` mathematical calculations be made to derive all `N` permutations. Ideally only `(N/2)+1` calculations should be need to be made, so far; since we know that we can generate the permutations on the declination slope using the graph of accruals mapped at the inclination slope. I.e.g, a `for` loop, not containing nested loops, and iterates a maximum of `N` times to derive `N` permutations. We have several numerical variables which can be used to meet requirement. The expected result can be achieved. – guest271314 Apr 11 '17 at 05:25
  • 2
    As I don't plan to answer this I will share some information that might be of values to others. The sequence `9,81,18,81,9,702,9,171,27,72,18,693,18,72,27,171,9,702,9,81,18,81,9` can be put into a [search](https://oeis.org/search?q=9%2C81%2C18%2C81%2C9%2C702%2C9%2C171%2C27%2C72%2C18%2C693%2C18%2C72%2C27%2C171%2C9%2C702%2C9%2C81%2C18%2C81%2C9&language=english&go=Search) at On-Line Encyclopedia of Integer Sequences® (OEIS®). This will then provide links to other references, other related sequences, insights into the sequences and even some source code for generating sequences. – Guy Coder Apr 11 '17 at 12:32
  • @GuyCoder The link is helpful. Verifies own independent findings. Will browse through the links for formulas to solve present Question, which appears to be, at first glance, previously formulated mathematically. – guest271314 Apr 12 '17 at 00:38
  • 2
    To bad one cannot earn bounty points on good comments. – Guy Coder Apr 12 '17 at 11:58
  • @GuyCoder Though still have not found how to read the full original articles at the site you linked to, would encourage you to post your comment as an Answer. In lieu of an Answer being posted which attempts to, or does, mathematically solve the inquiry at OP, your comment, at this point, is the most acceptable Answer to the actual Question, where the linked articles verify what I found independently; and you put forth effort to actually researching the propositions presented at Question. – guest271314 Apr 12 '17 at 14:46
  • 1
    On note: [Talk:A030299](https://oeis.org/wiki/Talk:A030299) – Guy Coder Apr 12 '17 at 15:18
  • @GuyCoder There do not appear to be a "Talk" page for "A219664" or "A220664"? – guest271314 Apr 12 '17 at 15:36
  • I only learned of OEIS a few months ago and myself am still becoming aware of what is there. I would not expect there to be a talk page for every sequence. The reason I noted the talk page for that one is because I am still trying to understand the differences between some of the four sequences related to the search I posted. The talk notes that if you keep the sequence truly lexicographical then when you get to `10`, if you do a true lexicographical sort it is not what you might want in some cases, and likewise for your question, thus other related sequences that start the same be differ. – Guy Coder Apr 12 '17 at 15:44
  • 1
    If you look at the sequence you will see that [Antti Karttunen](http://oeis.org/wiki/User:Antti_Karttunen) has posted the most info related to your question and also has source code for some the sequences as [Intseq Scheme library](https://github.com/karttu/IntSeq). In particular I think [triangles-core.ss](https://github.com/karttu/IntSeq/blob/master/src/Seqs/Triangles/triangles-core.ss) will have example code that should shed light on what you seek. I plan to check out the code, and look for the [recurrence relation](https://en.wikipedia.org/wiki/Recurrence_relation) – Guy Coder Apr 12 '17 at 15:49
  • @GuyCoder Have not yet reached generating `3628800` permutations using the algorithm at Question. Will adjust algorithm accordingly when reach that point. Will read your most recent links, and contact the individual you mentioned, if cannot make further progress as to Question here. Again, your input has been helpful. – guest271314 Apr 12 '17 at 15:49
  • 1
    I am slowly getting around to answering this. The reason I am not rushing is that I want to give more than a regurgitation of my comments. Generating permutations is what I have been spending time on for a few weeks but not in the same way you do, so I will need to adjust gears to hopefully give a good answer within the time frame. – Guy Coder Apr 13 '17 at 15:34
  • Of interest: [Code-Golf: Permutations](https://codegolf.stackexchange.com/questions/5056/code-golf-permutations) – Guy Coder Apr 14 '17 at 15:19
  • Of interest: [Heap's algorithm](https://en.wikipedia.org/wiki/Heap%27s_algorithm#cite_note-3) Note: This is not lexicographic but should be more efficient. – Guy Coder Apr 14 '17 at 15:23
  • Of interest: [Steinhaus–Johnson–Trotter algorithm](https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm) related to Heap's algorithm – Guy Coder Apr 14 '17 at 15:27
  • 1
    Of interest: [Gray code](https://en.wikipedia.org/wiki/Gray_code) Another way to look at generating permutations. – Guy Coder Apr 14 '17 at 15:29
  • Of interest: [Heap's algorithm paper](https://oup.silverchair-cdn.com/oup/backfile/Content_public/Journal/comjnl/6/3/10.1093/comjnl/6.3.293/2/6-3-293.pdf?Expires=1492529362&Signature=U6nTAi5S7wJGtwUvQa91hMmQmYc8wt8O3LeTjIYxPt0YmfgBxeyp-uMe1M~oOnurEc6TdH5W5mwO3Df7TYTvgbO4y8GOkcjRTLs-Wb5rSir2vu0AW0qRstR4o~1gGpteQF11qJsBXAHYLUKHyBvzVBiBYQhEKw9kISfZ4V6wMrQTuZCjFxtjBS-KD5BQfWUls1WXMhw0RUFDGm4AoZgYfw-rY5YgCefqylpQC8MwbiJEZtPj~azk0txc-G9y1Xj86yI6XnskjGEHC~V3ZS~brcBtIo-xNhCOvONnH~W-ezQxwk39AP775KzCf1jkywKl7a5ppXPmswGu9zscTnQCBA__&Key-Pair-Id=APKAIUCZBIA4LVPAVW3Q) – Guy Coder Apr 14 '17 at 15:30
  • 1
    Of interest: Talk by Robert Sedgewick of [Permutations](http://www.cs.princeton.edu/~rs/talks/perms.pdf) Gives reasons why permutations are useful, and the paper [Permutation Generation Methods](http://homepage.divms.uiowa.edu/~goodman/22m150.dir/2007/Permutation%20Generation%20Methods.pdf) the talk is based. – Guy Coder Apr 14 '17 at 15:38
  • 1
    Of interest: [Catalan Numbers](http://www.geometer.org/mathcircles/catalan.pdf) by Tom Davis. Often you see images related to permutations and then scratch your head as how to interpret the image, this paper will help to clear some of it up. You might like mountain ranges. – Guy Coder Apr 14 '17 at 15:44
  • Of interest: OEIS - Heap's algorithm [A280318](https://oeis.org/search?q=A280318&language=english&go=Search) Every time I think I have all the related sequences I find another. – Guy Coder Apr 14 '17 at 15:54
  • Of interest: OEIS [index page](https://oeis.org/wiki/Index_to_OEIS:_Section_Per) contacting permutations – Guy Coder Apr 14 '17 at 15:57
  • Of interest: [Next lexicographical permutation algorithm](https://www.nayuki.io/page/next-lexicographical-permutation-algorithm) and JavaScript [source code](https://www.nayuki.io/res/next-lexicographical-permutation-algorithm/nextperm.js) – Guy Coder Apr 14 '17 at 16:06
  • 1
    Of interest: [Enumerative Combinatorics](http://www-math.mit.edu/~rstan/ec/ec1.pdf) by Richard P. Stanley. An entire book on the subject by a MIT professor. This one will keep you busy for some time. – Guy Coder Apr 14 '17 at 16:17
  • 1
    Of interest: [Factorial number system](https://en.wikipedia.org/wiki/Factorial_number_system) – Guy Coder Apr 14 '17 at 16:30
  • 1
    Of interest: "The Art Of Computer Programming" A draft of section 7.2.1.2 [Generating all permutations](http://www.cs.utsa.edu/~wagner/knuth/fasc2b.pdf) by Donald E. Knuth. A classic. – Guy Coder Apr 14 '17 at 16:43
  • 1
    Of interest: [Combinatorial Generation by Fusing Loopless Algorithms](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.490.1604&rep=rep1&type=pdf) – Guy Coder Apr 14 '17 at 16:48
  • Of interest: [Combinatorial Generation](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.93.5967&rep=rep1&type=pdf) by Frank Ruskey. Another book. – Guy Coder Apr 14 '17 at 16:55
  • Of interest: [CombinatorialAlgorithms](https://www.math.upenn.edu/~wilf/website/CombinatorialAlgorithms.pdf) by Albert Hijenhuis and Herbert S. Wilf. Another book, old but still worth a look. – Guy Coder Apr 14 '17 at 17:00
  • Of interest: [Combinatorial Analysis and Computers](http://poncelet.math.nthu.edu.tw/disk5/js/computer/hall-knuth.pdf) by Hall M. and Knuth D.E. Referenced by Sedgewick paper. – Guy Coder Apr 14 '17 at 20:07
  • Of interest: [Efficient Algorithms to Rank and Unrank Permutations in Lexicographic Order](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.211.8434&rep=rep1&type=pdf) – Guy Coder Apr 14 '17 at 20:13
  • Of interest: [Revisiting Lexicographical Permutation Methods](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.55.9899&rep=rep1&type=pdf) - For each approach there will be a short description of the algorithm, an analysis of the running time, an analysis of the memory requirements, an example implementation, and results of empirical testing. – Guy Coder Apr 14 '17 at 20:16
  • Of interest: [The months in a year, A215940, A217626, and A101301](http://oeis.org/w/images/2/26/DaysYearsPermute.pdf) – Guy Coder Apr 15 '17 at 12:02
  • Of interest: OEIS [A215940](https://oeis.org/A215940) - Difference between the n-th and the first (identity) permutation of (1,...,m), interpreted as a decimal number, divided by 9 (for any m for which m! >= n). – Guy Coder Apr 15 '17 at 12:13

3 Answers3

14

TL;DR version

Unfortunately, the only way to improve performance of this algorithm is to get rid of it and use something better instead.

Are the "truths" obvious? (Spoiler alert: yes, they are)

In your long text I see two "truths" that you've found:

  1. If you write arrays indices as strings and then reinterpret those strings as numbers for two consecutive permutations, the difference will a multiply of 9

  2. The "graph of slopes" is symmetric

Unfortunately both of these facts are quite obvious and one of them is not even really true.

First fact is true as long the length of the array is less than 10. If it is more than 10 i.e. some indices are "mapped" to 2 characters, it stops being true. And it is obviously true if you know a divisibility rule for 9 (in decimal system): sum of digits should be a multiply of 9. Obviously if both of the numbers have the same digits they have the same reminder module 9 and thus their difference is a multiply of 9. Moreover, if you interpret your string in any system with base more than length of the array, the difference will be a multiply of base - 1 for the same reason. For example, let's use 8-based system (columns are: permutation, permutation index, indices string, indices string converted from 8-based to decimal, difference):

abc 0 012 10  
acb 1 021 17   7
bac 2 102 66  49
bca 3 120 80  14
cab 4 201 129 49
cba 5 210 136  7

If you always use based of the system that is greater than the length of the array, this fact will be true (but you might need to come up with new digits)

The second statement is obvious as well and is direct consequences of how "lexicographic order" is defined. For every index i, if I sum indices array of the first i-th permutation and the last i-th permutation, the sum will always be the same: array with all values equal to the length of the array. Example:

1. abc 012 - cba 210 => 012 + 210 = 222  
2. acb 021 - cab 201 => 021 + 201 = 222
3. bac 102 - bca 120 => 102 + 120 = 222

This is easy to see if you consider permutations of an array of negative indices i.e. [-N, -(N-1), ..., -1, 0]. Obviously i-th permutation from the start of this array is the same as i-th permutation of the [0, 1, 2, ... N] from the end with just negated signs.

Other questions

  1. Are there a mathematical relationships or patterns between the indexes as numbers, that is, for example, the set of numbers at graph 1, graph 2 or graph 3, derived from the input of any four values, for example

Yes, there are. Actually this is exactly the reason why the answer you linked in your question Permutations without recursive function call works in the first place. But I doubt there is an algorithm significantly more efficient than the one provided in that answer. Effectively that answer tries to convert the position of the requested permutation into a value in a variable-base numerical system with bases ranging from 1 to the length of the array. (For a more widespread example of a variable-base numerical system consider how you convert milliseconds into days-hours-minutes-seconds-milliseconds. You effectively use numerical system with bases 1000-60-60-24-unlimited. So when you see 12345 days 8 hours 58 minutes 15 seconds 246 milliseconds you convert it to milliseconds as ((((12345 * 24 + 8) * 60) + 58 * 60) + 15) * 1000 + 246 i.e. you treat that notation as 12345 (no base/unlimited) days 8 (24-base) hours 58 (60 base) minutes 15 (60 base) seconds 246 (1000-base) milliseconds).

With permutations there are two different tasks that you might need to do:

  1. Generate i-th permutation. The algorithm you linked in the SO answer is reasonably efficient. And I doubt there is anything much better

  2. Generate all permutations or a stream of permutations or next permutation for given one. This seems to be the one you are trying to do with your code. In this case simple algorithm that analyses given permutation, finds the first place where the permutations is not sorted, and does the switch + sorting is reasonably efficient (this is what procrastinator seems to implement but I didn't look into details). And again I doubt there is anything much better. On of major obstacles why numeric-based algorithm will not be more efficient is because for it to work in general case (i.e. length >= 10), you'll need to do divisions using long arithmetic in large bases and those operations are not O(1) anymore.


Update (answer to comment)

What I claim is that there is no way to calculate the sequence of numbers that would be more efficient than a direct calculation of the sequence of permutations.

I disagree as to that proposition. Can you show and prove as to that claim?

No, I don't even know how to state this claim in a formal way (how to define the class of algorithms that doesn't calculate that sequence of numbers?). Still I have some evidence to support this point.

First of all, you are probably not the smartest man in the known Universe, and this is relatively old and well known topic. Thus chances that you have discovered an algorithm that is much faster than existing ones are low. And the fact that nobody uses this techique is an evidence against you.

Another point is less arbitrary: the algorithm I suggested at #2 for generating all permutations in sequence is actually reasonably efficient and thus it will be hard to beat.

Consider some step to find next permutation. First you need to find the first position from the end where the order is not descending. Assume it would be k. It would take k comparisions to find it. Then you need to do one swap and sort. But if you are a bit smart, you might notice that "sort" here might be done much faster because the list is already sorted (but in reverse order). Thus sort here is just reverse with finding place for the k-th element. And taking into account that array is sorted, you can use binary search with O(log(k)) complexity. So you need to move k+1 elements in memory and less than k comparisions. Here is some code:

// generates array of all permutatations of array of integers [0, 1, .., n-1]
function permutations(n) {
    // custom version that relies on a fact that all values are unique i.e. there will be no equality
    var binarySearch = function (tgt, arr, start, end) {
        // on small ranges direct loop might be more efficient than actual binary search
        var SMALL_THRESHOLD = 5;
        if (start - end < SMALL_THRESHOLD) {
            for (var i = start; i <= end; i++) {
                if (arr[i] > tgt)
                    return i;
            }
            throw new Error("Impossible");
        }
        else {
            var left = start;
            var right = end;
            while (left < right) {
                var middle = (left + right) >> 1; //safe /2
                var middleV = arr[middle];
                if (middleV < tgt) {
                    left = middle + 1;
                }
                else {
                    right = middle;
                }
            }

            return left;
        }
    };


    var state = [];
    var allPerms = [];
    var i, swapPos, swapTgtPos, half, tmp;
    for (i = 0; i < n; i++)
        state [i] = i

    //console.log(JSON.stringify(state));
    allPerms.push(state.slice()); // enfroce copy
    if (n > 1) {
        while (true) {
            for (swapPos = n - 2; swapPos >= 0; swapPos--) {
                if (state[swapPos] < state[swapPos + 1])
                    break;
            }

            if (swapPos < 0) // we reached the end
                break;

            // reverse end of the array
            half = (n - swapPos) >> 1; // safe /2
            for (i = 1; i < half + 1; i++) {
                //swap [swapPos + i] <-> [n - i]
                tmp = state[n - i];
                state[n - i] = state[swapPos + i];
                state[swapPos + i] = tmp;
            }

            // do the final swap
            swapTgtPos = binarySearch(state[swapPos], state, swapPos + 1, n - 1);
            tmp = state[swapTgtPos];
            state[swapTgtPos] = state[swapPos];
            state[swapPos] = tmp;
            //console.log(JSON.stringify(state));
            allPerms.push(state.slice()); // enfroce copy
        }
    }
    //console.log("n = " + n + " count = " + allPerms.length);
    return allPerms;
}

Now imagine that you do the same with your number-based approach and for a moment assume that you can calculate the number to add for each step instantly. So how much time do you use now? As you have to use long arithmetics and we known that highest digit that will be changed by your addition is k-th, you'll need to perform at least k additions and k comparisions for overflow. And of course you'll still have to do at least k writes to the memory. So to be more efficient than described above "usual" algorithm, you need a way to calculate a k-digits long number (the one you will add) in a time that takes less than to perform a binary search in array of size k. This sounds to me as a quite tough job. For example, multiplication of 9 (or rather N-1) by corresponding coefficient alone will probably take more time using long arithmetics.

So what other chances do you have? Don't use long arithmetics at all. In this case, the first obvious argument is that mathematically it makes little sense to compare alrgorithms performance on small N (and this is why Big-O notation is used for algorithms complexity). Still it might make sense to fight for performance of a "small" from the pure mathematics' point of view but "big" for real world cases in range up to permutation of array of 20 elements that still would fit into a long (64-bit) integer. So what you can gain by not using long arithmetics? Well your additions and multiplications will take only one CPU instruction. But then you'll have to use division to split your number back to digits and it will take N divisions and N checks (i.e. comparisions) on each step. And N is always greater than k, often much more. So this also doesn't look like a great avenue for performance improvements.

To sum up: suggested alogrithm is efficient and any arithmetics-based algorithm will probably less efficient in arithmetics part.

Community
  • 1
  • 1
SergGr
  • 23,570
  • 2
  • 30
  • 51
  • _"Unfortunately, the only way to improve performance of this algorithm is to get rid of it and use something better instead."_ That is not the present Question. Your suggestion is not the _only_ way performance can be improved. Determining the numerical relationship between the numbers, directly, without filtering through `RegExp` or `binary mask` would decrease total time to complete process. Are you sure the math relating to _1)_ stops being true where index reaches >= 10? Or are you referencing the potential adjustments required to interpreting the number which remains a multiple of nine? – guest271314 Apr 09 '17 at 03:01
  • What is not obvious, from perspective here, is the precise multiple of number nine necessary to derive the numeric difference between each discrete permutation. For example, from your perspective, is there an obvious numeric relationship between `9,702` at indexes `5` and `6` for input `abcd`? – guest271314 Apr 09 '17 at 04:32
  • Thanks for mentionning me :-) You are right, this is exactly what I'm doing. I did not take the time to explain how it works because 1) I thought I was off-topic 2) my english is not very good 3) it's not clear for me that my method will *always* work 4) it's saturday and the sun is shining :-D In other words, I want to prove the algorithm but I've never been good at this job, imagine the pain if I try to do it in english after a long, cloudy and tiring week, while I could go out and enjoy a big healing sun in a perfect sky :-D I'm going to try something today though :-) –  Apr 09 '17 at 06:09
  • @SergGr Where does Answer actually attempt to produce a more efficient `javascript` version of the algorithm at Question? Or, determine the mathematical relationship of the differences between the discrete numbers on the graph, to be able to omit checking for duplicate numbers in a set, and calculate the exact difference between each growth by multiple of nine of the original indexes? – guest271314 Apr 09 '17 at 16:01
  • The current Answer does not meet requirement described at OP and does not answer Question. – guest271314 Apr 10 '17 at 08:37
  • @guest271314, you seem to not get what I mean. What I mean is that I believe that any efficient algorithm for permutations sequence will not calculate that sequence of multiplies of 9. Or to state it otherwise, any efficient algorithm to calculate that sequence will effectively first calculate permutations sequence and then derive the sequence of numbers. If the sequence of numbers (rather than permutations) is what you really want for some strange reason, calculate the sequence permutations and then calculate differences. – SergGr Apr 10 '17 at 18:45
  • @SergGr You are not gathering the algorithm or substance of Question. The present Question is not about "any efficient algorithm for permutations". The Question is specifically about maximizing the efficiency of the algorithm at Question. The permutations are not calculated first using the algorithm at Question. The permutations are derived from the indexes as whole numbers by the algorithm. If you do not intend to attempt to derive the further mathematical relationship between the numbers which derives the permutations, feel free to delete your Answer, as it is not relevant to the Question. – guest271314 Apr 11 '17 at 01:18
  • Your current Answer is not relevant to the actual Question. Specifically, the algorithm generates permutations by incrementing a single number. That single number is a multiple of the number nine. The Question asks how to calculate the next multiple of nine from the current multiple of nine, without adding `9` and performing a filtering function at each increment. The Question is not asking about "any efficient algorithm for permutations", but only the algorithm demonstrated at Question. Perhaps you have misinterpreted the actual Question? – guest271314 Apr 11 '17 at 01:30
  • @guest271314, by defininition you can not improve an algorithm without changing it. **_Any improvement is a change_**. So if you don't want to change the algorithm, there is no way to improve it. And as I commented "Or to state it otherwise, any efficient algorithm to calculate that sequence will effectively first calculate permutations sequence and then derive the sequence of numbers. If the sequence of numbers (rather than permutations) is what you really want for some strange reason, calculate the sequence permutations and then calculate differences."Perhaps you don't understand the answer? – SergGr Apr 11 '17 at 06:05
  • @guest271314, as a side note, the fact that the answers doesn't match your "ideal world answer" is a result of the fact that we live in the real world and the real world has some limitations whether we like them or not. – SergGr Apr 11 '17 at 06:11
  • _"If the sequence of numbers (rather than permutations) is what you really want for some strange reason"_ The sequence of numbers generates the permutations. – guest271314 Apr 11 '17 at 06:12
  • @guest271314, they generate each other. What I claim is that there is no way to calculate the sequence of numbers that would be more efficient than a direct calculation of the sequence of permutations. And from the sequence of permutations you can trivially get the sequence of numbers. – SergGr Apr 11 '17 at 06:12
  • 1
    _"they generate each other. What I claim is that there is no way to calculate the sequence of numbers that would be more efficient than a direct calculation of the sequence of permutations"_ No, the whole number alone generates the permutations. The single whole number is the calculation for the permutation itself. The expected result is for a possible maximum N permutation, a maximum of N calculation should be made. That is, for example a `for` loop which does not contain nested loop, and calculates directly the next number to add to current number. We can to get to N/2 + 1 total calculations – guest271314 Apr 11 '17 at 06:17
  • We are now able to generate the second half of permutations from the whole number when the first half plus one of the permutations are generated. We can determine the precise correlation between the accrued whole number, we will be able to generate N permutations with a maximum of (N/2)+1 distinct operations, being multiplication, or the required form of mathematical computation to return expected result. The requirement described at OP and comments is possible mathematically, and shall be achieved. – guest271314 Apr 11 '17 at 06:22
  • _"they generate each other."_ That is an interesting perspective. Will certainly consider that concept during further inquiry of the subject matter. _"What I claim is that there is no way to calculate the sequence of numbers that would be more efficient than a direct calculation of the sequence of permutations."_ I disagree as to that proposition. Can you show and prove as to that claim? – guest271314 Apr 11 '17 at 06:27
  • @guest271314, _I disagree as to that proposition. Can you show and prove as to that claim?_ No, I can't but I added some details to the end of my answer. – SergGr Apr 16 '17 at 13:19
  • @SergGr Gather the points you are making. They are sound. Current Question is not querying _the_ or _a_ most efficient algorithm to get all lexicographic permutations. The Question is specifically asking how to improve the efficiency of the current algorithm. Improvement as to efficiency being defined as, primarily, using discrete math as improvement - that is, how to improve the blind addition portion of algorithm? How to find the precise number to increment by using current `.length` of resulting array, factorial, or other variables that we have as _numbers_. Secondarily, improvement at code – guest271314 Apr 16 '17 at 15:46
  • How do you know that the algorithm cannot be improved if we do not get to the point where no further improvements can be made? In particular, when we ultimately derive the exact number needed to increment indexes as whole number, we will be able to generate the full set of lexicographic permutations with only `(N/2)+1` total operations; that is once we get half plus one permutations, or rather the corresponding number at that index of result, we use the first half plus one of numbers to get the second half of numbers. Question does ask _if_ this can be done, but is exploring _how_ to achieve – guest271314 Apr 16 '17 at 15:51
  • @SergGr It is not clear where you actually attempted to improve algorithm at Question at your Answer? Instead you appear to dismiss the algorithm as incapable of being improved when compared to other algorithms, which is not what Question poses. It is not relevant if algorithm at Question is less efficient than another algorithm; the inquiry is determining the most efficient manifestation of the algorithm; to be able to verify its limitations absolutely, with proof. Currently, the algorithm _can_ be improved, both mathematically and at code; we have not yet composed the most efficient version. – guest271314 Apr 16 '17 at 16:11
  • @guest271314, yes, as I said time and again your algorithm can be easily improved by first calculating the sequence of permutations with a good existing algorithm and then calculating differences. I also provided some evidence that there is no much better algorithm to calculate that sequence of numbers in other way then this suggestion. But for some reason you seem to ignore that suggestion as well. So what exactly do you want now? – SergGr Apr 16 '17 at 17:52
  • @SergGr _"your algorithm can be easily improved by first calculating the sequence of permutations with a good existing algorithm and then calculating differences"_ The differences between the numbers are what is generating the permutations themselves. We do not need to perform the procedure twice. _"So what exactly do you want now?"_ Ideally, to perform only `(N/2)+1` calculations to derive first half plus one of permutations, then use the current stored number to derive the next half -1 of permutations by algebra, addition, multiplication or other mathematical expression. – guest271314 Apr 16 '17 at 18:14
  • @guest271314, I ask not what you want "ideally" but what you want in the "real world" because evidences suggest that what you want "ideally" is not possible. Unfortunately in battles between what we want and what is possible in the real world, the real world always wins. So is there anything possible that you want? – SergGr Apr 16 '17 at 18:23
  • For example, with a set of four, we begin with the number `123`, or as indexes `0123`. The object is to mathematically calculate that `132` is next number, expressed as indexes as `0132`. That is straightforward, a difference of `9`. For the next number `213` the path is not straightforward, `81`, expressed as indexes as `0213`. What we are trying to determine is how to derive the precise value to add to `132` in a single mathematical equation or expression without the use of `RegExp` (version 1) or binary mask (version 3). We have several variables that we can use. `k`, `p`, `result.length` – guest271314 Apr 16 '17 at 18:24
  • Yes, this is "the real world". Not sure what you mean by "win"? Everything begins with the idea. Until the idea is proven to be not possible, the idea is valid. An example would be fixed wing or rotary propelled flight by earthlings. We have enough variables to solve the equation, though the math is not straightforward. There must be a further correlation which could be derived; the evidence indicates as much. – guest271314 Apr 16 '17 at 18:27
  • 1
    @guest271314, I'll try one more time: yes, there is a math behind calculating the differences but the math is effectively the same as the math behind calculating the permutations itself, so it is more efficiently to do it directly. Adding a layer of doing it using numbers doesn't simplify anything. It just adds a layer of complexity and thus **_reduces_** efficiency. Can you accept this reality? – SergGr Apr 16 '17 at 18:30
  • @SergGr No, cannot presently accept that fact as being the limit of the procedure mathematically. Continuing with variables we have `curr`, the current number after `(k/2)+1` numbers have been derived. There must be a way to get from `132` to `213` in a single mathematical procedure; given the number of variables that we have to arrange as to each other to get a sum or product. "Reality" is based on perspective. From vantage here, the possible arrangements of variables mathematically have not been exhausted. – guest271314 Apr 16 '17 at 18:36
  • @guest271314, yes there is a mathematical procedure. That procedure is actually implemented in the code above: just convert `state` to the number and if you really want, you even can simulate swaps with additions, subtractions and multiplications (obviously signficantly reducing efficiency). If you don't want to accept the reality, I shall give up. Good luck in your imaginary world. – SergGr Apr 16 '17 at 18:42
  • _"If you don't want to accept the reality, I shall give up. Good luck in your imaginary world."_ That portion of comment is simply not necessary. No, do not "accept" what others perceive as _the_ "reality". That is their perspective. Shall not be limited, in any way, by another two-leggeds' perspective, here. Note also, your algorithm appears to compare values of input, where values are explicitly ignored at algorithm at OP. – guest271314 Apr 16 '17 at 18:45
  • The notion of _"I shall give up"_ is not an option, here. – guest271314 Apr 16 '17 at 18:54
  • What is expected input to `permutations` function? – guest271314 Apr 16 '17 at 21:57
  • Ran `permutations` function at Answer and JavaScript version of C `main` function at CoderGuy Answer originally posted by @Siguza twenty times each at 10000 calls for each of twenty loops at chromium. Results as to total time for `10000*20` calls `permutations` : `0.2605399999999979`; `main` : `0.25556250000000047` https://jsfiddle.net/Lhgmtwk9/4/ – guest271314 Apr 17 '17 at 06:27
  • If necessary make adjustments, corrections; and verify tests. – guest271314 Apr 17 '17 at 06:42
2

When solving problems having many different tools in your tool box helps. A tool that applies to this problem is The On-Line Encyclopedia of Integer Sequences® (OEIS®).

As noted in the question is the sequence

9,81,18,81,9,702,9,171,27,72,18,693,18,72,27,171,9,702,9,81,18,81,9

which can be generated by taking the lexicographic permutations of 1,2,3,4 and converting them to numbers

1234,1243,1324,1342,1423,1432,2134,2143,2314,2341,2413,2431,    
3124,3142,3214,3241,3412,3421,4123,4132,4213,4231,4312,4321

then subtracting a permutation from its predecessor, e.g.

1243 - 1234 = 9
1324 - 1243 = 81
1342 - 1324 = 18
...

Now the OP notes all of the difference values are divisible by 9, so dividing by 9 gives

1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2, 77, 2, 8, 3, 19, 1, 78, 1, 9, 2, 9, 1

and with a OEIS search gives

A217626 - ... first differences of permutations of 123...m, divided by 9 (with m sufficiently large, m! > n).

With regards to the OP question

the mathematical formula to derive the precise number which needs to be added, or multiplied by 9, to the current whole number representation of indexes of the current permutation to get the next whole number representation of the indexes of the next lexicographic permutation.

In the links section is

R. J. Cano, Additional information about this sequence.

and clicking on Additional information about this sequence.

brings up a page that talks about the symmetry of the sequence

{ 1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2, 77, 2, 8, 3, 19, 1, 78, 1, 9, 2, 9, 1 }

These elements are the first 4! terms of this sequence. The symmetry center for this case (permutations for N=4) is the 12th term, because by deleting it and the zero starting this sequence, only left an even amount of terms which are repeated once according to their offsets relative to the symmetry center, as shown in the following transformations:

0) { 0, 1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2, 77, 2, 8, 3, 19, 1, 78, 1, 9, 2, 9, 1 }; Delete the zero,

1) { 1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2, 77, 2, 8, 3, 19, 1, 78, 1, 9, 2, 9, 1}; Split apart in three pieces one of them the symmetry center,

2) { 1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2 }; { 77 }; { 2, 8, 3, 19, 1, 78, 1, 9, 2, 9, 1 }; Delete the symmetry center,

3) { 1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2 }; { 2, 8, 3, 19, 1, 78, 1, 9, 2, 9, 1 }; Reflect one of the pieces,

4) { 1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2 }; { 1, 9, 2, 9, 1, 78, 1, 19, 3, 8, 2 }; A new kind of center becomes evident after this step.

Since all the possible permutation sets have all in common some terms from this sequence, we may expect find again these "centers" when developing another cases.

With regards to an code/algorithm there is another reference in the links section

R. J. Cano, Template for a base-independent sequencer in C.

/*
 * ########################################## 
 * # Base independent sequencer for A217626 #
 * ##########################################
 * 
 * This program is free software.
 * Written by R. J. Cano (remy at ula.ve, Or reemmmyyyy at gmail.com)
 * On Jan 9 2014, for educational purposes and released under 
 * the terms of the General Public License 3.0 (GNU-GPL 3.0);
 * 
 * There is NO warranty not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * Note: For a large set of terms (>10!-1) the present program might be prone to data type overflows.
 * 
*/

#include <stdio.h>
#include <stdlib.h>

long _base= 10;
long _showOffset= 1;
/** Standard output field width. An aid for comparisons using MD5 checksums. **/
long _normWidth= 13; 
/** Set this to 0 for print everything in a single line. **/
long _onePerLine= 1;
/** 0 for the vector representation, 1 for the integer representation **/
long _objectToShow= 1;

long permute(long*, long*, long);
long vec2polyEval(long*, long, long);

int main(int argc, char *argv[]) {
  long v1[100],v2[100],v3[100],u[100],n,k,l,offset=0;
  _showOffset*= _onePerLine;
  /* The size of the output (n!-1 items) is no longer read from the standard input. 
     scanf("%li",&n); -- Does stop silently, therefore it is avoided. */
  n= strtol(argv[1], NULL, _base); /* Direct conversion from the command line parameter(s) */
  for(k=0; k<100; k++) {
    v1[k]= (k<n)*(k);
    v2[k]= v1[k];
    v3[k]= 0;
  }
  while(permute(v2,u,n)) {
    for(k=0;k<n-1;k++) {
      v3[k+1]=0;
      for(l=k+1;l<n;l++) {
    v3[k+1]+=(u[l]-v2[l]);
      }
    }
    if (_showOffset) printf("%li ", ++offset);
    if (!_onePerLine) printf(",");
    if (!_objectToShow) {
      for(k=0;k+n<_normWidth;k++) { printf(",0"); }
      for(k=0;k<n;k++) { printf(",%li",v3[k]); }
      printf(";");
    } else {
      printf("%li", vec2polyEval(v3,_base,n));
    }
    if (_onePerLine) printf("\n");
  }
  if (!_onePerLine) printf("\n");
  return EXIT_SUCCESS;
}

long permute(long *data, long *previous, long Size) {
  long tau, rho=Size-1, phi=Size-1;
  for (tau=0;tau<Size;tau++) previous[tau]= data[tau];
  while((rho > 0)&&(data[rho]<= data[rho-1])) rho--;
  rho--;
  if(rho<0) return 0;
  while((phi > rho)&&(data[phi]<=data[rho])) phi--;
  tau= data[rho];
  data[rho]= data[phi];
  data[phi]= tau;
  Size--;
  rho++;
  while(Size>rho) {
    tau= data[Size];
    data[Size]= data[rho];
    data[rho]= tau;
    Size--;
    rho++;
  }
  return 1;
}

long vec2polyEval(long* v, long B, long m) {
  long ans=0, pow=1, k;
  for(k=m-1;k>=0;k--) {
    ans+= v[k]*pow;
    pow*= B;
  }
  return ans;
}

Example

To run the C code I used Visual Studio Community 2015 which is free, and built as a Win32 console project.


C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 2
1 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 3
1 1
2 9
3 2
4 9
5 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 4
1 1
2 9
3 2
4 9
5 1
6 78
7 1
8 19
9 3
10 8
11 2
12 77
13 2
14 8
15 3
16 19
17 1
18 78
19 1
20 9
21 2
22 9
23 1

C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 5
1 1
2 9
3 2
4 9
5 1
6 78
7 1
8 19
9 3
10 8
11 2
12 77
13 2
14 8
15 3
16 19
17 1
18 78
19 1
20 9
21 2
22 9
23 1
24 657
25 1
26 9
27 2
28 9
29 1
30 178
31 1
32 29
33 4
34 7
35 3
36 66
37 2
38 18
39 4
40 18
41 2
42 67
43 1
44 19
45 3
46 8
47 2
48 646
49 1
50 19
51 3
52 8
53 2
54 67
55 1
56 29
57 4
58 7
59 3
60 176
61 3
62 7
63 4
64 29
65 1
66 67
67 2
68 8
69 3
70 19
71 1
72 646
73 2
74 8
75 3
76 19
77 1
78 67
79 2
80 18
81 4
82 18
83 2
84 66
85 3
86 7
87 4
88 29
89 1
90 178
91 1
92 9
93 2
94 9
95 1
96 657
97 1
98 9
99 2
100 9
101 1
102 78
103 1
104 19
105 3
106 8
107 2
108 77
109 2
110 8
111 3
112 19
113 1
114 78
115 1
116 9
117 2
118 9
119 1

A test with 10, which can bring down some algorithms works nicely.


C:\Users\Eric\Documents\Visual Studio 2015\Projects\OEIS_A217626\Debug>OEIS_A217626 10
1 1
2 9
3 2
4 9
5 1
6 78
7 1
8 19
9 3
10 8
...
3628790 8
3628791 3
3628792 19
3628793 1
3628794 78
3628795 1
3628796 9
3628797 2
3628798 9
3628799 1

Note: The number of answers is X!-1 and 10! = 3628800 There is one less than the factorial because this is calculating the differences.

I did not convert this to JavaScript because your efficiency at JavaScript is probably better than mine at present. Currently I am focused on Prolog and F#.

Supplement

If you are visiting this question to learn about enumerating permutations on a computer then two particular papers you should read are

Permutation Generation Methods by Robert Sedgewick

"The Art Of Computer Programming" A draft of section 7.2.1.2 Generating all permutations by Donald E. Knuth.

and the book

Enumerative Combinatorics by Richard P. Stanley

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • _"If there is some reason you are not accepting my answer?"_ Does current Answer determine the next number directly? Have not used *indows in some time, will try to find another way to run `C` code. What is the benchmark? – guest271314 Apr 16 '17 at 18:43
  • _"In lieu of an Answer being posted which attempts to, or does, mathematically solve the inquiry at OP"_ The present bounty has not yet expired. – guest271314 Apr 16 '17 at 19:01
  • 1
    With the studious help of @Siguza, we should be able to create the graph at `JavaScript` using from formula of code at Answer. Added `*9` at line `46` `ret += vec2polyEval(v3, _base, n) * 9`. The initial benchmark, without attempting any adjustments other than one mentioned above, was `0.015065000000000248` https://jsfiddle.net/Lhgmtwk9/1/ – guest271314 Apr 16 '17 at 21:35
  • @guest271314 Thanks. Much appreciated. – Guy Coder Apr 16 '17 at 23:01
  • Ran `main` function at Answer originally posted by @Siguza and `permutations` function at @SergGr Answer twenty times each at 10000 calls for each loop at chromium. Results as to total time for `10000*20` calls `permutations` : `0.2605399999999979`; `main` : `0.25556250000000047` https://jsfiddle.net/Lhgmtwk9/4/ – guest271314 Apr 17 '17 at 06:32
  • If necessary make adjustments, corrections; and verify tests. – guest271314 Apr 17 '17 at 06:43
0

Lack of skills in maths and english makes it hard for me to elaborate on such a question :-| Though I wanted to play with permutations and I did something not so off-topic after all. See TL;DR at https://stackoverflow.com/a/43302308/1636522, I have the same feeling and I'll try to demonstrate the validity of my algorithm later, I need a little time to think of it.

var a = "aceg".split(""); 
do {
  console.log(a.join(""));
} while (a = next(a));

function next (a) {
  var i, c, head, next;
  var b = a.slice();
  var n = b.length;
  for (i = 1; i < n; i++) {
    if (b[n - i] > b[n - (i + 1)]) {
      head = n - (i + 1);
      next = n - i;
      for (i = next + 1; i < n; i++) {
        if (b[i] < b[next] && b[i] > b[head]) {
          next = i;
        }
      }
      c = b[head];
      b[head] = b[next];
      b[next] = c;
      return b.slice(0, head + 1).concat(
        b.slice(head + 1).sort()
      );
    }
  }
}
Community
  • 1
  • 1
  • `a[n - i] > a[n - (i + 1)]` compares the value of the element of the set at the indexes `n - i`, `n - (i + 1)`, yes? Where values of the set are ignored at algorithm at Question. The algorithm at Answer is not related to algorithm at Question. Note also, input which is array of numbers, for example, `[0,1,2,3]` does not appear to return expected result https://jsfiddle.net/veo6mmc1/1, containing duplicate entries within resulting array. – guest271314 Apr 08 '17 at 09:00
  • `.join("")` is apparently necessary to get expected result https://jsfiddle.net/veo6mmc1/2/, though input array is converted to string. – guest271314 Apr 08 '17 at 09:18
  • @guest271314 Yes, the function has a side effect, it modifies the previous array in place to compute the next one, which is bad indeed, but the result (the returned value) is ok. –  Apr 08 '17 at 09:19
  • The side effect could probably be cured; difficult to clearly determine without at least comments describing the steps of the process included at code. Though that would be another inquiry, not relevant to present inquiry. The approach uses the actual value of the element of the set, instead of the indexes of the set, at `a[n - i] > a[n - (i + 1)] `, yes? The previously referenced initial benchmark demonstrates that the result is derived in less time than approaches at Question. Though, again, the algorithm at Answer, is not related to the algorithm at Question; unless missing something? – guest271314 Apr 08 '17 at 09:29
  • @guest271314 Yes, it's easy to fix, we just need to copy the passed array before swapping values. Off-topic answer updated :-D –  Apr 08 '17 at 09:36
  • The code at Answer is dissimilar to algorithm and code at Question. The code at Answer explicitly uses the value of the input as a variable within procedures. The algorithm at Question explicitly does not use the value of input with procedures. Note the different results at jsfiddle version where input `.length` is same, though input values are different https://jsfiddle.net/veo6mmc1/3/ where resulting `.length` is `22` and though should be `24`, https://jsfiddle.net/veo6mmc1/4/;. What is purpose of `.sort()` call? – guest271314 Apr 08 '17 at 18:38
  • @guest271314 "*The code at Answer is dissimilar to algorithm and code at Question.*". Read *TL;DR* again at http://stackoverflow.com/a/43302308/1636522. I had the same feeling but hard to express myself (lack of maths and english skills). –  Apr 09 '17 at 07:14
  • The current Answer does not meet requirement described at OP and does not answer Question. – guest271314 Apr 10 '17 at 08:41