I don't know if this is a more mathematical object, but I lurked mathexchange and doesn't look algorithm oriented so I prefer to ask here.
I would like to know if the following problem, was already resolved:
let's say we have 10 objects and that we want to sort them preferences based. If the sort pertains a single person, no problem, we ask him to answer to our questions (using bubblesort or similar) and answering, after a bunch of questions, he will receive the final ranking.
Now let's say that there are 10 persons. And we want to make a global rank. It becomes difficult, and anyone can have its way to solve the problem (for example, asking for the "first favourite three" to everyone and assigning points, then make a ranking);
I would like to be more scientific and therefore more algorithmic, so, in other words, use bubble sort (whose implementation, is like a series of question 1vs1 objects and asking what's your favourite, then make a ranking) for the ten people, minimizing the questions to ask.
So we should have a way to global rank the objects, and in the meanwhile assigning to the people who will sort, major importance, and if possible, don't wait for anyoone making his ranking but on percentages and statistics basis.
Hope to have explained well my question, please if you don't feel it's for this group, let me know and transfer on another service. Thanks!
You question is the subject of Arrow's Theorem. In short, what you are trying to do is impossible in general.
If you still want to try, I suggest using directed edges in a directed graph to represent preferences; something like majority prefers A to B, include edge A->B, and no edge in case of ties. If the result is a Directed Acyclic Graph, congratulations, you can order the items with a toposort. Otherwise use Tarjan's Algorithm to identify strongly connected components, which are the trouble spots.
In general, the best way out of this conundrum in my opinion is to obtain scores rather than ranking pairs of items. Then you just average the scores.
After the unpromising results of my previous answer, I decided to get started on a practical aspect of the question: how to optimally ask the questions to establish a person's preference.
skipping unnecessary questions
If there are 10 items to order, there are 45 pairs of items which have to be compared. These 45 decisions make up a triangular matrix:
0 1 2 3 4 5 6 7 8
1 >
2 > <
3 < > =
4 = > < =
5 > < < < >
6 < > > < > <
7 < > < = = < >
8 < < = > = < < <
9 = > > < < > > = >
In the worst case scenario, you'd have to ask a person 45 questions before you can fill out the whole matrix and know his ranking of the 10 items. However, if a person prefers item 1 to item 2, and item 2 to item 3, you can deduce that he prefers item 1 to item 3, and skip that question. In fact, in the best case scenario, just 9 questions will be enough to fill out the whole matrix.
Answering binary questions to deduce an item's place in an ordered list is very similar to filling a binary search tree; however, in a 10-item b-tree, the best-case scenario is 16 questions instead of our theoretical minimum of 9; so I decided to try and find another solution.
Below is an algorithm based on the triangular matrix. It asks the questions in random order, but after every answer it checks which other answers can be deduced, and avoids asking unnecessary questions.
In practice, the number of questions needed to fill out the 45-question matrix is on average 25.33, with 90.5% of instances in the 20-30 range, a minimum value of 12 and a maximum of 40 (tested on 100,000 samples, random question order, no "=" answers).
When the questions are asked systematically (filling the matrix from top to bottom, left to right), the distribution is quite different, with a lower average of 24.44, a strange cutoff below 19, a few samples going up to the maximum of 45, and an obvious difference between odd and even numbers.
I wasn't expecting this difference, but it has made me realise that there are opportunities for optimisation here. I'm thinking of a strategy linked to the b-tree idea, but without a fixed root. That will be my next step. (UPDATE: see below)
function PrefTable(n) {
this.table = [];
for (var i = 0; i < n; i++) {
this.table[i] = [];
for (var j = 0; j < i; j++) {
this.table[i][j] = null;
}
}
this.addAnswer = function(x, y, pref, deduced) {
if (x < y) {
var temp = x; x = y; y = temp; pref *= -1;
}
if (this.table[x][y] == null) {
this.table[x][y] = pref;
if (! deduced) this.deduceAnswers();
return true;
}
else if (this.table[x][y] != pref) {
console.log("INCONSISTENT INPUT: " + x + ["<", "=", ">"][pref + 1] + y);
}
return false;
}
this.deduceAnswers = function() {
do {
var changed = false;
for (var i = 0; i < this.table.length; i++) {
for (var j = 0; j < i; j++) {
var p = this.table[i][j];
if (p != null) {
for (var k = 0; k < j; k++) {
var q = this.table[j][k];
if (q != null && p * q != -1) {
changed |= this.addAnswer(i, k, p == 0 ? q : p, true);
}
}
for (var k = i + 1; k < this.table.length; k++) {
var q = this.table[k][j];
if (q != null && p * q != 1) {
changed |= this.addAnswer(i, k, p == 0 ? -q : p, true);
}
}
for (var k = j + 1; k < i; k++) {
var q = this.table[i][k];
if (q != null && p * q != 1) {
changed |= this.addAnswer(j, k, p == 0 ? q : -p, true);
}
}
}
}
}
}
while (changed);
}
this.getQuestion = function() {
var q = [];
for (var i = 0; i < this.table.length; i++) {
for (var j = 0; j < i; j++) {
if (this.table[i][j] == null) q.push({a:i, b:j});
}
}
if (q.length) return q[Math.floor(Math.random() * q.length)]
else return null;
}
this.getOrder = function() {
var index = [];
for (i = 0; i < this.table.length; i++) index[i] = i;
index.sort(this.compare.bind(this));
return(index);
}
this.compare = function(a, b) {
if (a > b) return this.table[a][b]
else return 1 - this.table[b][a];
}
}
// CREATE RANDOM ORDER THAT WILL SERVE AS THE PERSON'S PREFERENCE
var fruit = ["orange", "apple", "pear", "banana", "kiwifruit", "grapefruit", "peach", "cherry", "starfruit", "strawberry"];
var pref = fruit.slice();
for (i in pref) pref.push(pref.splice(Math.floor(Math.random() * (pref.length - i)),1)[0]);
pref.join(" ");
// THIS FUNCTION ACTS AS THE PERSON ANSWERING THE QUESTIONS
function preference(a, b) {
if (pref.indexOf(a) - pref.indexOf(b) < 0) return -1
else if (pref.indexOf(a) - pref.indexOf(b) > 0) return 1
else return 0;
}
// CREATE TABLE AND ASK QUESTIONS UNTIL TABLE IS COMPLETE
var t = new PrefTable(10), c = 0, q;
while (q = t.getQuestion()) {
console.log(++c + ". " + fruit[q.a] + " or " + fruit[q.b] + "?");
var answer = preference(fruit[q.a], fruit[q.b]);
console.log("\t" + [fruit[q.a], "whatever", fruit[q.b]][answer + 1]);
t.addAnswer(q.a, q.b, answer);
}
// PERFORM SORT BASED ON TABLE
var index = t.getOrder();
// DISPLAY RESULT
console.log("LIST IN ORDER:");
for (var i in index) console.log(i + ". " + fruit[index[i]]);
update 1: asking the questions in the right order
If you ask the questions in order, filling up the triangular matrix from top to bottom, what you're actually doing is this: keeping a preliminary order of the items you've already asked about, introducing new items one at a time, comparing it with previous items until you know where to insert it in the preliminary order, and then moving on to the next item.
This algorithm has one obvious opportunity for optimisation: if you want to insert a new item into an ordered list, instead of comparing it to each item in turn, you compare it with the item in de middle: that tells you which half to new item goes into; then you compare it with the item in the middle of that half, and so on... This limits the maximum number of steps to log2(n)+1.
Below is a version of the code that uses this method. In practice, it offers very consistent results, and the number of questions needed is on average 22.21, less than half of the maximum 45. And all the results are in the 19 to 25 range (tested on 100,000 samples, no "=" answers).
The advantage of this optimisation becomes more pronounced as the number of items increases; for 20 items, out of a possible 190 questions, the random method gives an average of 77 (40.5%), while the optimised method gives an average of 62 (32.6%). At 50 items, that is 300/1225 (24.5%) versus 217/1225 (17.7%).
function PrefList(n) {
this.size = n;
this.items = [{item: 0, equals: []}];
this.current = {item: 1, try: 0, min: 0, max: 1};
this.addAnswer = function(x, y, pref) {
if (pref == 0) {
this.items[this.current.try].equals.push(this.current.item);
this.current = {item: ++this.current.item, try: 0, min: 0, max: this.items.length};
} else {
if (pref == -1) this.current.max = this.current.try
else this.current.min = this.current.try + 1;
if (this.current.min == this.current.max) {
this.items.splice(this.current.min, 0, {item: this.current.item, equals: []});
this.current = {item: ++this.current.item, try: 0, min: 0, max: this.items.length};
}
}
}
this.getQuestion = function() {
if (this.current.item >= this.size) return null;
this.current.try = Math.floor((this.current.min + this.current.max) / 2);
return({a: this.current.item, b: this.items[this.current.try].item});
}
this.getOrder = function() {
var index = [];
for (var i in this.items) {
index.push(this.items[i].item);
for (var j in this.items[i].equals) {
index.push(this.items[i].equals[j]);
}
}
return(index);
}
}
// PREPARE TEST DATA
var fruit = ["orange", "apple", "pear", "banana", "kiwifruit", "grapefruit", "peach", "cherry", "starfruit", "strawberry"];
var pref = fruit.slice();
for (i in pref) pref.push(pref.splice(Math.floor(Math.random() * (pref.length - i)),1)[0]);
pref.join(" ");
// THIS FUNCTION ACTS AS THE PERSON ANSWERING THE QUESTIONS
function preference(a, b) {
if (pref.indexOf(a) - pref.indexOf(b) < 0) return -1
else if (pref.indexOf(a) - pref.indexOf(b) > 0) return 1
else return 0;
}
// CREATE TABLE AND ASK QUESTIONS UNTIL TABLE IS COMPLETE
var t = new PrefList(10), c = 0, q;
while (q = t.getQuestion()) {
console.log(++c + ". " + fruit[q.a] + " or " + fruit[q.b] + "?");
var answer = preference(fruit[q.a], fruit[q.b]);
console.log("\t" + [fruit[q.a], "whatever", fruit[q.b]][answer + 1]);
t.addAnswer(q.a, q.b, answer);
}
// PERFORM SORT BASED ON TABLE
var index = t.getOrder();
// DISPLAY RESULT
console.log("LIST IN ORDER:");
for (var i in index) console.log(i + ". " + fruit[index[i]]);
I think this is as far as you can optimise the binary question process for a single person. The next step is to figure out how to ask several people's preferences and combine them without introducing conflicting data into the matrix.
update 2: sorting based on the preferences of more than one person
While experimenting (in my previous answer) with algorithms where different people would answer each question, it was clear that the conflicting preferences would create a preference table with inconsistent data, which wasn't useful as a basis for comparison in a sorting algorithm.
The two algorithms earlier in this answer offer possibilities to deal with this problem. One option would be to fill out the preference table with votes in percentages instead of "before", "after" and "equal" as the only options. Afterwards, you could search for inconsistencies, and fix them by changing the decision with the closest vote, e.g. if apples vs. oranges was 80/20%, oranges vs. pears was 70/30%, and pears vs. apples was 60/40%, changing the preference from "pears before apples" to "apples before pears" would be the best way to resolve the inconsistency.
Another option would be to skip unnecessary questions, thereby removing the chance of inconsistencies in the preference table. This would be the easiest method, but the order in which the questions are asked would then have a greater impact on the end result.
The second algorithm inserts each item into a preliminary order by first checking whether it goes in the first or last half, then whether it goes in the first or last half of that half, and so on... steadily zooming in on the correct position in ever decreasing steps. This means the sequence of decisions used to determine the position of each item are of decreasing importance. This could be the basis of a system where more people are asked to vote for important decisions, and less people for less important decisions, thus reducing the number of questions that each person has to answer.
If the number of people is much greater than the number of items, you could use something like this: with every new item, the first question is put to half of the people, and every further question is then put to half of the remaining people. That way, everyone would have to answer at most one question per item, and for the whole list everyone would answer at most the number of questions equal to the number of items.
Again, with large groups of people, there are possibilities to use statistics. This could decide at which point a certain answer has developed a statistically significant lead, and the question can be considered as answered, without asking any more people. It could also be used to decide how close a vote has to be to be considered an "equal" answer.
update 3: ask subgroups based on importance of questions
This code version reduces the number of questions per person by asking important questions to a large subgroup of the population and less important questions to a smaller subgroup, as discussed in update 2.
e.g. When finding the position of the eighth item in a list already containing 7 items, a maximum number of 3 questions is needed to find the correct position; the population will therefor be split into 3 groups, whose relative sizes are 4:2:1.
The example orders 10 items based on the preferences of 20 people; the maximum number of questions any person is asked is 9.
function GroupPref(popSize, listSize) { // CONSTRUCTOR
if (popSize < steps(listSize)) return {};
this.population = popSize;
this.people = [];
this.groups = [this.population];
this.size = listSize;
this.items = [{item: 0, equals: []}];
this.current = {item: 1, question: 0, try: 0, min: 0, max: 1};
this.getQuestion = function() {
if (this.current.item >= this.size) return null;
if (this.current.question == 0) this.populate();
var group = this.people.splice(0, this.groups[this.current.question++]);
this.current.try = Math.floor((this.current.min + this.current.max) / 2);
return({people: group, a: this.current.item, b: this.items[this.current.try].item});
}
this.processAnswer = function(pref) {
if (pref == 0) {
this.items[this.current.try].equals.push(this.current.item);
} else {
if (pref < 0) this.current.max = this.current.try
else this.current.min = this.current.try + 1;
if (this.current.min == this.current.max) {
this.items.splice(this.current.min, 0, {item: this.current.item, equals: []});
} else return;
}
this.current = {item: ++this.current.item, question: 0, try: 0, min: 0, max: this.items.length};
this.distribute();
}
function steps(n) {
return Math.ceil(Math.log(n) / Math.log(2));
}
this.populate = function() {
for (var i = 0; i < this.population; i++) this.people.splice(Math.floor(Math.random() * (i + 1)), 0, i);
}
this.distribute = function() {
var total = this.population, groups = steps(this.current.item + 1);
this.groups.length = 0;
for (var i = 0; i < groups; i++) {
var size = Math.round(Math.pow(2, i) * total / (Math.pow(2, groups) - 1));
if (size == 0) ++size, --total;
this.groups.unshift(size);
}
}
this.getOrder = function() {
var index = [];
for (var i in this.items) {
var equal = [this.items[i].item];
for (var j in this.items[i].equals) {
equal.push(this.items[i].equals[j]);
}
index.push(equal);
}
return(index);
}
}
// PREPARE TEST DATA
var fruit = ["orange", "apple", "pear", "banana", "kiwifruit", "grapefruit", "peach", "cherry", "starfruit", "strawberry"];
var pref = [];
for (i = 0; i < 20; i++) {
var temp = fruit.slice();
for (j in temp) temp.push(temp.splice(Math.floor(Math.random() * (temp.length - j)), 1)[0]);
pref[i] = temp.join(" ");
}
// THIS FUNCTION ACTS AS THE PERSON ANSWERING THE QUESTIONS
function preference(person, a, b) {
if (pref[person].indexOf(a) - pref[person].indexOf(b) < 0) return -1
else if (pref[person].indexOf(a) - pref[person].indexOf(b) > 0) return 1
else return 0;
}
// CREATE LIST AND ANSWER QUESTIONS UNTIL LIST IS COMPLETE
var t = new GroupPref(20, 10), c = 0, q;
while (q = t.getQuestion()) {
var answer = 0;
console.log(++c + ". ask " + q.people.length + " people (" + q.people + ")\n\tq: " + fruit[q.a] + " or " + fruit[q.b] + "?");
for (i in q.people) answer += preference(q.people[i], fruit[q.a], fruit[q.b]);
console.log("\ta: " + [fruit[q.a], "EQUAL", fruit[q.b]][answer != 0 ? answer / Math.abs(answer) + 1 : 1]);
t.processAnswer(answer);
}
// GET ORDERED LIST AND DISPLAY RESULT
var index = t.getOrder();
console.log("LIST IN ORDER:");
for (var i = 0, pos = 1; i < index.length; i++) {
var pre = pos + ". ";
for (var j = 0; j < index[i].length; j++) {
console.log(pre + fruit[index[i][j]]);
pre = " ";
}
pos += index[i].length;
}
I'm trying to use LINQ to transform the following list. LINQ should multiply each element against the next as long as the product is less than 15. Additionally we should save the number of elements used to form the product.
int[] values = { 1, 3, 4, 2, 7, 14 }; //assume Largest value will never be >= 15
1x3x4 = 12
2x7 = 14
14 = 14
{ {12,3}, {14,2}, {14,1} }
My ultimate goal is to take the geometric average of a very large list of numbers. This is normally done by multiplying each element in the list together (1x3x4x2x7x14) then taking the nth root (in this case 1/6).
The obvious problem in using the "normal" method is that you will quickly find yourself using numbers beyond the maximum allowable number. You can workaround this by using the old divide and conquer method and with a little help from the natural log function.
I don't think there is something like that build into standard LINQ method library. But you can easily create your own extension method. I called it AggregateUntil:
public static class EnumerableExtensions
{
public static IEnumerable<TResult> AggregateUntil<TSource, TAccumulate, TResult>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func,
Func<TAccumulate, bool> condition,
Func<TAccumulate, TResult> resultSelector
)
{
TAccumulate acc = seed;
TAccumulate newAcc;
foreach(var item in source)
{
newAcc = func(acc, item);
if(!condition(newAcc))
{
yield return resultSelector(acc);
acc = func(seed, item);
}
else
{
acc = newAcc;
}
}
yield return resultSelector(acc);
}
}
And now let's use it. First, take multiplications only, as long as they met < 15 condition:
var grouped
= values.AggregateUntil(1, (a,i) => a * i, a => a < 15, a => a).ToList();
Returns List<int> with 3 items: 12, 14, 14. That's what you need. But now lets take number of items which were aggregated into each multiplication. That's easy using anonymous type::
int[] values = { 1, 3, 4, 2, 7, 14 };
var grouped
= values.AggregateUntil(
new { v = 1, c = 0 },
(a, i) => new { v = a.v * i, c = a.c + 1 },
a => a.v < 15,
a => a).ToList(); ;
Returns exactly what you need:
My ultimate goal is to take the geometric average of a very large list of numbers.
Then just take the nth root of each number and multiply afterwards. Then you don't need to worry about splitting the list into groups:
double mean = 1.0;
foreach(int i in values)
{
mean *= Math.Pow(i, 1.0 / values.Length);
}
Which could also be done in Linq with Aggregate:
mean = values.Aggregate(1.0, (prev, i) => prev * Math.Pow(i, 1.0 / values.Length ));
Well my solution is not quite as elegant as #MarcinJuraszek, but it's fast and it works within your constraints.
int[] values = {1, 3, 4, 2, 7, 14};
int product = 1;
int elementsMultiplied = 0;
List<Tuple<int,int>> allElements = new List<Tuple<int,int>>();
for(int i = 0; i < values.Length ; i++)
{
product = product * values[i];
elementsMultiplied++;
if(i == values.Length - 1 || product * values[i+1] >= 15)
{
allElements.Add(new Tuple<int,int>(product, elementsMultiplied));
product = 1;
elementsMultiplied = 0;
}
}
foreach(Tuple<int,int> pair in allElements)
{
Console.WriteLine(pair.Item1 + "," + pair.Item2);
}
I am working on some code that will allow assessors to assess something (vague, right?). Before assessing can occur, a random sampling needs to be taken of the submitted items. That part is rather simple.
The part that is fouling me up is the requirement that each item needs to be assessed by two different assessors and that we want the final number of assessments that each assessor performs to be as evenly distributed as possible.
Example: If I have 10 items, that should come out to 20 assessments (2 assessments per item). 20 assessments divided by 4 assessors comes out to 5 assessments per assessor. Obviously the numbers won't always come out this clean (11 items would still come out to 5 per assessor, with the remaining two to get assigned on top after everyone has evened out).
Just looking for some algorithmic help here. The closest I can get ended up being more of a bell curve than I would have liked.
It's not difficult. Let's say you have A accessors and I items. Just run the following loop (everything is zero-based indexing):
a = 0
for 0 <= r < 2:
for 0 <= i < I:
while (assessor a is already assessing item i):
a = (a + 1) mod A
assessor a will assess item i on round r
a = (a + 1) mod A
This will simply allocate the assessors in round-robin fashion, but will skip over those cases where the same assessor would assess the same item twice.
For me it looks like you need to distribute 2N assessments of N items between M assessors so that every assessor will get equal his share or as close to is as possible.
There's identity:
2N = ceil(2N/M) + ceil((2N-1)/M) + ... + ceil((2N-M+1)/M)
which can be used for that purpose. ceil here is the closest non-lesser integer: ceil(2.3) = 3, ceil(4) = 4
For you example of 11 items you will have 22 = 5 + 5 + 4 + 4 + 4.
How it works? I will refer you to "Concrete mathematics" by Knuth, Patashnik & Graham, chapter 3, part 4 for explanation :)
I've coded Anttis' approach and the one described in "Concrete math":
public static void main(String[] args) {
wayOne(5, 7);
System.out.println("======");
wayTwo(5, 7);
}
private static void wayOne(int assessors, int items) {
Integer assessments[][] = new Integer[2][items];
int assessor = 0;
for (int pass = 0; pass < 2; pass++) {
for (int item = 0; item < items; item++) {
while (assessments[pass][item] != null)
assessor = (assessor + 1) % assessors;
assessments[pass][item] = assessor;
assessor = (assessor + 1) % assessors;
}
}
for (int pass = 0; pass < assessments.length; pass++) {
for (int item = 0; item < assessments[pass].length; item++)
System.out.println("Pass " + pass + " item " + item + " is assessed by " + assessments[pass][item]);
}
}
private static void wayTwo(int assessors, int items) {
Integer distribution[][] = new Integer[2][items];
int assessments = 2 * items;
int step = 0, prevBatch = 0;
while (assessments > 0) {
int batch = (int) Math.ceil(( 2.0 * items - step) / assessors);
assessments -= batch;
for (int i = prevBatch; i < batch + prevBatch; i++) {
distribution[i / items][i % items] = i % assessors;
}
prevBatch += batch;
step++;
}
for (int pass = 0; pass < distribution.length; pass++) {
for (int item = 0; item < distribution[pass].length; item++)
System.out.println("Pass " + pass + " item " + item + " is assessed by " + distribution[pass][item]);
}
}
If I'm correct, second way will give more desired output. For example, try it for 7 items and 5 assessors. Or 11 items and 4 assessors.
UPDATE After I fixed bug pointed out by Antti, two routines give same results.
We're given a string and a permutation of the string.
For example, an input string sandeep and a permutation psdenae.
Find the position of the given permutation in the sorted list of the permutations of the original string.
The total number of permutation of a given string of length n would be n! (if all characters are different), thus it would not be possible to explore all the combinations.
This question is actually like the mathematics P & C question
Find the rank of the word "stack" when arranged in dictionary order.
Given the input string as NILSU
Take a word which we have to find the rank. Take "SUNIL" for example.
Now arrange the letter of "SUNIL" in alphabetical order.
It will be. "I L N S U".
Now take the first letter. Its "I". Now check, is the letter "I" the
first letter of "SUNIL"? No. The number of words that can be formed
starting with I will be 4!, so we know that there will be 4! words
before "SUNIL".
I = 4! = 24
Now go for the second letter. Its "L". Now check once again if this
letter we want in first position? No. So the number of words can be
formed starting with "L" will be 4!.
L = 4! = 24
Now go for "N". Is this we want? No. Write down the number of words
can be formed starting with "N", once again 4!
N = 4! = 24
Now go for "S". Is this what we want? Yes. Now remove the letter from
the alphabetically ordered word. It will now be "I L N U"
Write S and check the word once again in the list. Is we want SI? No.
So the number of words can be formed starting with SI will be 3!
[S]:I-> 3! = 6
Go for L. is we want SL? No. So it will be 3!.
[S]:L-> 3! = 6
Go for N. is we want SN? No.
[S]:N-> 3! = 6
Go for SU. Is this we want? Yes. Cut the letter U from the list and
then it will be "I L N". Now try I. is we want SUI? No. So the number
of words can be formed which starts from SUI will be 2!
[SU]:I-> 2! = 2 Now go for L. Do we want "SUL". No. so the number of
words starting with SUL will be 2!.
[SU]:L-> 2! = 2
Now go for N. Is we want SUN? Yes, now remove that letter. and this
will be "I L". Do we want "SUNI"? Yes. Remove that letter. The only
letter left is "L".
Now go for L. Do we want SUNIL? Yes. SUNIL were the first options, so
we have 1!. [SUN][I][L] = 1! = 1
Now add the whole numbers we get. The sum will be.
24 + 24 + 24 + 6 + 6 + 6 + 2 + 2 + 1 = 95.
So the word SUNIL will be at 95th position if we count the words that can be created using the letters of SUNIL arranged in dictionary order.
Thus through this method you could solve this problem quite easily.
Building off #Algorithmist 's answer, and his comment to his answer, and using the principle discussed in this post for when there are repeated letters, I made the following algorithm in JavaScript that works for all letter-based words even with repeated letter instances.
function anagramPosition(string) {
var index = 1;
var remainingLetters = string.length - 1;
var frequencies = {};
var splitString = string.split("");
var sortedStringLetters = string.split("").sort();
sortedStringLetters.forEach(function(val, i) {
if (!frequencies[val]) {
frequencies[val] = 1;
} else {
frequencies[val]++;
}
})
function factorial(coefficient) {
var temp = coefficient;
var permutations = coefficient;
while (temp-- > 2) {
permutations *= temp;
}
return permutations;
}
function getSubPermutations(object, currentLetter) {
object[currentLetter]--;
var denominator = 1;
for (var key in object) {
var subPermutations = factorial(object[key]);
subPermutations !== 0 ? denominator *= subPermutations : null;
}
object[currentLetter]++;
return denominator;
}
var splitStringIndex = 0;
while (sortedStringLetters.length) {
for (var i = 0; i < sortedStringLetters.length; i++) {
if (sortedStringLetters[i] !== splitString[splitStringIndex]) {
if (sortedStringLetters[i] !== sortedStringLetters[i+1]) {
var permutations = factorial(remainingLetters);
index += permutations / getSubPermutations(frequencies, sortedStringLetters[i]);
} else {
continue;
}
} else {
splitStringIndex++;
frequencies[sortedStringLetters[i]]--;
sortedStringLetters.splice(i, 1);
remainingLetters--;
break;
}
}
}
return index;
}
anagramPosition("ARCTIC") // => 42
I didn't comment the code but I did try to make the variable names as explanatory as possible. If you run it through a debugger process using your dev tools console and throw in a few console.logs you should be able to see how it uses the formula in the above-linked S.O. post.
I tried to implement this in js. It works for string that have no repeated letters but I get a wrong count otherwise. Here is my code:
function x(str) {
var sOrdinata = str.split('').sort()
console.log('sOrdinata = '+ sOrdinata)
var str = str.split('')
console.log('str = '+str)
console.log('\n')
var pos = 1;
for(var j in str){
//console.log(j)
for(var i in sOrdinata){
if(sOrdinata[i]==str[j]){
console.log('found, position: '+ i)
sOrdinata.splice(i,1)
console.log('Nuovo sOrdinata = '+sOrdinata)
console.log('\n')
break;
}
else{
//calculate number of permutations
console.log('valore di j: '+j)
//console.log('lunghezza stringa da permutare: '+str.slice(~~j+1).length);
if(str.slice(j).length >1 ){sub = str.slice(~~j+1)}else {sub = str.slice(j)}
console.log('substring to be used for permutation: '+ sub)
prep = nrepC(sub.join(''))
console.log('prep = '+prep)
num = factorial(sub.length)
console.log('num = '+num)
den = denom(prep)
console.log('den = '+ den)
pos += num/den
console.log(num/den)
console.log('\n')
}
}
}
console.log(pos)
return pos
}
/* ------------ functions used by main --------------- */
function nrepC(str){
var obj={}
var repeats=[]
var res= [];
for(x = 0, length = str.length; x < length; x++) {
var l = str.charAt(x)
obj[l] = (isNaN(obj[l]) ? 1 : obj[l] + 1);
}
//console.log(obj)
for (var i in obj){
if(obj[i]>1) res.push(obj[i])
}
if(res.length==0){res.push(1); return res}
else return res
}
function num(vect){
var res = 1
}
function denom(vect){
var res = 1
for(var i in vect){
res*= factorial(vect[i])
}
return res
}
function factorial (n){
if (n==0 || n==1){
return 1;
}
return factorial(n-1)*n;
}
A bit too late but just as reference... You can use this C# code directly.
It will work but...
The only important thing is that usually, you should have unique values as your starting set. Otherwise you don't have n! permutations. You have something else (less than n!). I have a little doubt of any useful usage when item could be duplicate ones.
using System;
using System.Collections.Generic;
namespace WpfPermutations
{
public class PermutationOuelletLexico3<T>
{
// ************************************************************************
private T[] _sortedValues;
private bool[] _valueUsed;
public readonly long MaxIndex; // long to support 20! or less
// ************************************************************************
public PermutationOuelletLexico3(T[] sortedValues)
{
if (sortedValues.Length <= 0)
{
throw new ArgumentException("sortedValues.Lenght should be greater than 0");
}
_sortedValues = sortedValues;
Result = new T[_sortedValues.Length];
_valueUsed = new bool[_sortedValues.Length];
MaxIndex = Factorial.GetFactorial(_sortedValues.Length);
}
// ************************************************************************
public T[] Result { get; private set; }
// ************************************************************************
/// <summary>
/// Return the permutation relative to the index received, according to
/// _sortedValues.
/// Sort Index is 0 based and should be less than MaxIndex. Otherwise you get an exception.
/// </summary>
/// <param name="sortIndex"></param>
/// <returns>The result is written in property: Result</returns>
public void GetValuesForIndex(long sortIndex)
{
int size = _sortedValues.Length;
if (sortIndex < 0)
{
throw new ArgumentException("sortIndex should be greater or equal to 0.");
}
if (sortIndex >= MaxIndex)
{
throw new ArgumentException("sortIndex should be less than factorial(the lenght of items)");
}
for (int n = 0; n < _valueUsed.Length; n++)
{
_valueUsed[n] = false;
}
long factorielLower = MaxIndex;
for (int index = 0; index < size; index++)
{
long factorielBigger = factorielLower;
factorielLower = Factorial.GetFactorial(size - index - 1); // factorielBigger / inverseIndex;
int resultItemIndex = (int)(sortIndex % factorielBigger / factorielLower);
int correctedResultItemIndex = 0;
for(;;)
{
if (! _valueUsed[correctedResultItemIndex])
{
resultItemIndex--;
if (resultItemIndex < 0)
{
break;
}
}
correctedResultItemIndex++;
}
Result[index] = _sortedValues[correctedResultItemIndex];
_valueUsed[correctedResultItemIndex] = true;
}
}
// ************************************************************************
/// <summary>
/// Calc the index, relative to _sortedValues, of the permutation received
/// as argument. Returned index is 0 based.
/// </summary>
/// <param name="values"></param>
/// <returns></returns>
public long GetIndexOfValues(T[] values)
{
int size = _sortedValues.Length;
long valuesIndex = 0;
List<T> valuesLeft = new List<T>(_sortedValues);
for (int index = 0; index < size; index++)
{
long indexFactorial = Factorial.GetFactorial(size - 1 - index);
T value = values[index];
int indexCorrected = valuesLeft.IndexOf(value);
valuesIndex = valuesIndex + (indexCorrected * indexFactorial);
valuesLeft.Remove(value);
}
return valuesIndex;
}
// ************************************************************************
}
}
My approach to the problem is sort the given permutation.
Number of swappings of the characters in the string will give us the position of the pemutation in the sorted list of permutations.
An inefficient solution would be to successively find the previous permutations until you reach a string that cannot be permuted anymore. The number of permutations it takes to reach this state is the position of the original string.
However, if you use combinatorics you can achieve the solution faster. The previous solution will produce a very slow output if string length exceeds 12.