【问题标题】:How to infer the properties of objects based on how they were sorted?如何根据对象的排序方式推断对象的属性?
【发布时间】:2020-11-13 23:39:50
【问题描述】:

如果你运行下面的代码sn-p,它会生成一个随机的人列表,每个人都有一个唯一的orig属性,你可以假装是他们在机场排队到达的顺序(请多多包涵)。

船长不公平,不让人们按照他们到达的顺序坐在座位上。他喜欢某些名字胜过其他名字,而某些名字同样喜欢。

prefs 对象说明了他的偏好。 BobSueSal 是他最喜欢的名字,但他同样喜欢它们。 IanSam 是他最不喜欢的,但他同样不喜欢它们。

所以这个不公平的船长根据他喜欢他们名字的程度来对待人们。

这意味着人员列表首先按照他们到达的顺序排序,然后再按照船长对他们姓名的偏好进行排序。

当您运行代码 sn-p 时,它会生成一个对象列表,每个对象只有一个 nameorig(原始顺序)属性,按上述排序。

假装船长的喜好未知。如果你生成一个足够长的列表,或者一个足够短的列表,你应该可以推导出prefs 对象。

如何推断prefs 对象,仅给出列表?

我需要一个基于许多短列表的解决方案,而不是基于一个非常长的列表的解决方案。

const prefs = {
  Bob: { pref: 1 },
  Sue: { pref: 1 },
  Sal: { pref: 1 },
  Jim: { pref: 2 },
  Jon: { pref: 2 },
  Lyn: { pref: 2 },
  Ian: { pref: 3 },
  Sam: { pref: 3 }
};

const names = Object.keys(prefs);
const randomName = () => names[~~(Math.random() * names.length)];

const list = new Array(5).fill().map((_, orig) => {
  const name = randomName();
  return { name, orig };
}).sort((a, b) => prefs[a.name].pref > prefs[b.name].pref ? 1 : -1);

console.log(list);

这不是我的实际问题,但我希望这个简化版本易于理解。如果我能解决这个问题,那么我就能解决我真正的问题。

【问题讨论】:

    标签: javascript arrays sorting reverse-engineering


    【解决方案1】:

    这是我的尝试...在 generateList 之外,我无法访问 prefs 对象或 pref 值,我只能获取随机名称列表,然后尝试对列表进行逆向工程:

    let generateList = () => {
      const prefs = {
        Bob: { pref: 1 },
        Sue: { pref: 1 },
        Sal: { pref: 1 },
        Jim: { pref: 2 },
        Jon: { pref: 2 },
        Lyn: { pref: 2 },
        Ian: { pref: 3 },
        Sam: { pref: 3 }
      };
    
      const names = Object.keys(prefs);
      const randomName = () => names[~~(Math.random() * names.length)];
    
      const list = new Array(5).fill().map((_, orig) => {
        const name = randomName();
        return { name, orig };
      }).sort((a, b) => prefs[a.name].pref > prefs[b.name].pref ? 1 : -1);
      return list;
    }
    
    const lists = [];
    for (let i = 0; i < 10000; i++) {
        lists.push(generateList())
    }
    
    let guess = {};
    lists.forEach((list) => {
        list.forEach((item, index) => {
            guess[item.name] = (guess[item.name] || 0) + (list.length - index);
        });
    });
    
    // now we get the minimum
    const min = Math.min(...Object.values(guess))
    const max = Math.max(...Object.values(guess))
    const offset = Math.round(max/min) + 1;
    
    // now we guess the key order (as best we can), and set the pref
    guess = Object.fromEntries(Object.entries(guess).map((item) => {
        item[1] = { pref: offset - Math.round(item[1]/min) };
        return item;
    }).sort((a,b) => a[1].pref - b[1].pref));
    
    console.log(guess)

    【讨论】:

    • 我选择了你的 Best Answer 而不是另一个,因为虽然它们看起来基本相同,但你的却一直在重构 prefs
    【解决方案2】:

    您可以只计算某个名称的索引。结果,您会得到一些代表船长偏好的组。

    function generateSet() {
        const
            prefs = { Bob: { pref: 1 }, Sue: { pref: 1 }, Sal: { pref: 1 }, Jim: { pref: 2 }, Jon: { pref: 2 }, Lyn: { pref: 2 }, Ian: { pref: 3 }, Sam: { pref: 3 } },
            names = Object.keys(prefs),
            randomName = () => names[~~(Math.random() * names.length)];
        return Array
                .from({ length: 5 }, (_, orig) => ({ name: randomName(), orig }))
                .sort((a, b) => prefs[a.name].pref - prefs[b.name].pref);
    }
    
    function count(n) {
        const result = {};
        for (let i = 0; i < n; i++) {
            generateSet().forEach(({ name }, i) => result[name] = (result[name] || 0) + i);
        }
        return result;
    } 
    
    console.log(count(10000));

    【讨论】:

    • 这对我来说是一个令人着迷的答案,因为它可以让人们目睹每次测量的信心。它也非常简单。哇!
    【解决方案3】:

    这是我自己对我的问题的回答。它通过为每个人提供所有inferiorssuperiors 的列表来工作。 inferior 是在列表中出现在他们上方的人,但orig 更大。这可能发生的唯一方法是如果船长更喜欢他们。 superiors 反之亦然。

    然后,任何同时存在于某人的inferiorssuperiors 列表中的人都将从这些列表中删除并放入equals 列表中,这意味着船长已为他们分配了完全相同的偏好,因为这是唯一的这样他们就可以同时成为inferiorsuperior

    然后根据superiors 列表的长度对人们进行排名;没有superiors 的人排在最前面。 superiors 最多的人在底部。

    然后equals 组用于重构prefs 对象(共享相同equals 组的所有人都在同一个组中)。

    const intersect = (a, b) => {
      return new Set([...a].filter(x => b.has(x)));
    }
    
    const setsEqual = (a, b) => [...a].sort().toString() == [...b].sort().toString();
    
    const prefs = {
      Bob: { pref: 1 },
      Sue: { pref: 1 },
      Sal: { pref: 1 },
      Jim: { pref: 2 },
      Jon: { pref: 2 },
      Lyn: { pref: 2 },
      Ian: { pref: 3 },
      Sam: { pref: 3 }
    };
    
    const names = Object.keys(prefs);
    const randomName = () => names[~~(Math.random() * names.length)];
    const randomList = () => new Array(5).fill().map((_, orig) => {
      const name = randomName();
      return { name, orig };
    }).sort((a, b) => prefs[a.name].pref > prefs[b.name].pref ? 1 : -1);
    
    const people = {};
    
    for (let i = 0; i < 100; i++) {
      const list = randomList();
      list.forEach(({ name }) => !people[name] && (people[name] = {
        superiors: new Set(), inferiors: new Set()
      }));
      list.forEach(({ name, orig }, yourPos) => {
        const superiors = people[name].superiors;
        const inferiors = people[name].inferiors;
        list.forEach((person, theirPos) => {
          if (person.name == name) return;
          if (theirPos < yourPos && person.orig > orig) {
            superiors.add(person.name);
          }
          if (theirPos > yourPos && person.orig < orig) {
            inferiors.add(person.name);
          }
        });
      });
    }
    
    Object.entries(people).forEach(([name, { superiors, inferiors }]) => {
      const intersection = intersect(superiors, inferiors);
      for (const elem of intersection) {
        superiors.delete(elem);
        inferiors.delete(elem);
      }
    });
    
    
    Object.entries(people).forEach(([yourName, person]) => {
      Object.entries(people).forEach(([theirName, other]) => {
        const yourInferiors = person.inferiors;
        const yourSuperiors = person.superiors;
    
        const theirInferiors = other.inferiors;
        const theirSuperiors = other.superiors;
    
        if (setsEqual(yourInferiors, theirInferiors) && setsEqual(yourSuperiors, theirSuperiors)) {
          person.equals = person.equals || new Set();
          other.equals = other.equals || new Set();
    
          person.equals.add(theirName);
          other.equals.add(yourName);
    
          person.equals.add(yourName);
          other.equals.add(theirName);
        }
      });
    });
    
    const rankedPeople = Object.entries(people).sort(([, a], [, b]) => {
      return a.superiors.size > b.superiors.size ? 1 : -1;
    }).map(([name, { equals }]) => {
      return { name, equals: [...equals].sort().toString() };
    });
    
    const groups = [...new Set(rankedPeople.map(({ equals }) => equals))]
      .map((group, pref) => ({ pref: pref + 1, group: group.split(',') }));
    
    const deduction = {};
    
    groups.forEach(({ pref, group }) => {
      group.forEach(name => {
        deduction[name] = pref;
      });
    });
    
    console.log(deduction);

    目前,这是唯一可靠地解决问题的答案,只有100 长度为5 的列表。事实上,它只有一半的数量是相当可靠的。

    此外,此解决方案不会混淆与其他人有关系的人;例如如果 Bob 永远不会与 Ian 一起出现,这只会妨碍正确的分组,但不会导致 Bob 和 Ian 出现相对于彼此的顺序混乱。

    其他答案确实存在此缺陷。

    【讨论】:

      猜你喜欢
      • 2021-11-03
      • 1970-01-01
      • 2022-01-05
      • 1970-01-01
      相关资源
      最近更新 更多