【问题标题】:How to pick unique pairs from a single list如何从单个列表中选择唯一对
【发布时间】:2013-11-09 11:55:11
【问题描述】:

我正在创建一个程序,其中涉及一段代码,我需要在其中从单个项目(名称)列表中选择对。

初始列表来自单个ArrayList<String>,其中包含所有唯一的人名。

我的尝试如下:

//Performance is not really a focus as the lists are small (20 ~ 60 elements),
//thus I use a SecureRandom instead of a Random.
SecureRandom rnd = new SecureRandom();

//List of names
ArrayList<String> Names = new ArrayList<>();

//Names populated somewhere here..

//Make a secondary array which houses the available names...
ArrayList<String> AvailNames = new ArrayList<>();
AvailNames.addAll(Names);

LinkedHashMap<String, String> NamePair = new LinkedHashMap<>();
Iterator<String> Iter = Names.iterator();

// LOOP A
while(Iter.hasNext()){
    String name = Iter.next();
    int index;

   /*
    * LOOP B
    * Find a unique pair randomly, looping if the index is the same.
    * Not the most efficient way, but gets the job done...
    */
    while(true){
        index = rnd.nextInt(AvailNames.size());
        if(!AvailNames.get(index).equals(name)){
            break;
        }
    }

    NamePair.put(name, AvailNames.remove(index));
}

当名字的数量是奇数时,我遇到了LOOP B见上文)无限期运行的问题。

我发现问题在于,有时,当所有对都被取走时,剩下的姓氏对是非唯一的,导致 if 语句永远不会为真。

以列表为例:

  1. 一个
  2. B
  3. C
  4. D
  5. 电子

程序在执行期间可能会先从 A 到 D 排序,创建一个名称对,如下所示:

  1. A - B
  2. B - C
  3. C - D
  4. D - C

留下E - E 作为最后一对,不允许作为一对(因为项目/名称不是唯一)。由于配对分配是随机的,因此有时会起作用,有时会不起作用,坦率地说,这很烦人……

我确信解决方案非常简单,但由于某种原因,我似乎无法找到解决此问题的方法。任何帮助表示赞赏。

【问题讨论】:

  • 这是一个有趣的问题。当 AvailNames.size 为 2 并手动配对最后两个时,也许可以通过退出循环来保证不会发生这种情况?

标签: java list sorting


【解决方案1】:

您可以检测到何时进入这种情况,只需将最后一个 AvailName 与随机选择的前一对中的第二个元素交换即可。例如,如果您选择了第二对,则将其更改为 B-E,然后您的最后一对将是 E-C。

这将始终给出两个对,每个对具有不同的第一个和第二个元素:所选对不能将 E 作为其第一个元素(您将生成唯一的对)或作为其第二个元素(否则 E不会在AvailName)。

【讨论】:

  • 嗯...这可以工作...但增加了相当多的额外代码。我希望我的代码本身有问题。如果没有其他人指出我犯的一些严重错误,我将使用您的解决方案。感谢您的意见。
  • 我能想到的另一种选择是在倒数第二步检查:如果AvailNames 中的最后两个名字包含Names 中的姓氏,则选择它。这意味着您将生成 D - E(强制选择最后一个)和 E - C,而不是示例中的最后两对。
  • (恐怕这两种解决方案都可能导致所有可能的有效安排的分布略有不均匀。)
  • Ehh... 有点希望均匀分布...但再次...这不是一些严肃的应用程序,所以我想它就足够了。我想我会“预先洗牌”列表以进一步“随机化”它......要实施您的解决方案,如果它有效(逻辑上有效),我会接受。
【解决方案2】:

只是一个想法;您可以计算所有个可能的唯一对,然后从中随机抽样。

需要注意的是,这是 O(n2),因此对于大量名称可能会变慢;而且新的List 会变得相当大,因为它包含 n(n2 -1)/2 个元素。

这是一个例子:

public static void main(String args[]) {
    final List<String> in = new ArrayList<String>() {
        {
            add("A");
            add("B");
            add("C");
            add("D");
            add("E");
        }
    };
    final List<String[]> pairs = new ArrayList<>();
    for (int i = 0; i < in.size(); ++i) {
        for (int j = i + 1; j < in.size(); ++j) {
            pairs.add(new String[]{in.get(i), in.get(j)});
        }
    }
    Collections.shuffle(pairs);
    for (final String[] pair : pairs) {
        System.out.println(Arrays.toString(pair));
    }
}

输出:

[B, E]
[A, C]
[A, E]
[A, B]
[B, D]
[C, D]
[C, E]
[B, C]
[A, D]
[D, E]

您创建一个新的List&lt;String[]&gt; pairs,然后循环输入List。在每次迭代中,您都会遍历输入的其余部分 - 这可以保证您永远不会再次反转相同的对。

一旦您填充了pairs,您就可以简单地使用shuffle,然后您可以随意选择多对。鉴于Random 的样本来自均匀分布,您最终应该以相同的概率在pairs 中进行任何排序。您也可以将您的SecureRandom 传递到other shuffle method

【讨论】:

  • 我同意你的观点,这与我建议的解决方案基本相同。
  • 一件事是你没有找到问题中提到的所有对,例如:C - DD - C
  • 你可能是对的 - 我认为它们是相同的,但从问题来看它们不是。
【解决方案3】:

可能类似于以下内容:

更新:

为了消除误解,以下代码将确保:

  • 独特的对
  • 随机顺序

假设源列表中没有重复元素,您将拥有 n!/(n-k)! 唯一对,其中 n 是名称的数量,k2,因为我们正在创建对。

创建唯一对后,我将它们随机排列以获得随机性。

要使用更好的随机播放算法,可以使用Collections.shuffle(pairs, new SecureRandom())

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class NamePairTest {


  private static class NamePair {
    private final String one;
    private final String two;

    public NamePair(String one, String two) {
      super();
      this.one = one;
      this.two = two;
    }


    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((one == null) ? 0 : one.hashCode());
      result = prime * result + ((two == null) ? 0 : two.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null) return false;
      if (getClass() != obj.getClass()) return false;
      NamePair other = (NamePair) obj;
      if (one == null) {
        if (other.one != null) return false;
      } else if (!one.equals(other.one)) return false;
      if (two == null) {
        if (other.two != null) return false;
      } else if (!two.equals(other.two)) return false;
      return true;
    }

    @Override
    public String toString() {
      return one + " - " + two;
    }
  }


  public List<NamePair> createPairs(List<String> names) {
    List<NamePair> pairs = new ArrayList<>();
    // create all possible unique pairs, given the names list.
    for (String one : names) {
      for (String two : names) {
        // a pair must not have the same name twice
        if (!one.equals(two)) {
          NamePair newPair = new NamePair(one, two);
          // only add the pair if it is not already in the list of pairs.
          // this test will only be necessary if the names list contains duplicates.
          if (!pairs.contains(newPair)) pairs.add(newPair);
        } // if
      } // for
    } // for
    // now shuffle the list, as currently it is ordered.
    Collections.shuffle(pairs);
    return pairs;
  }


  public static void main(String[] args) {
    List<String> availableNames = Arrays.asList("A", "B", "C", "D", "E");
    NamePairTest namePairTest = new NamePairTest();
    for (NamePair pair : namePairTest.createPairs(availableNames)) {
      System.out.println(pair);
    }
  }

}

【讨论】:

  • 我希望实际的配对映射是唯一的,而不是配对的顺序。如果我读错了您的代码,请原谅,但它似乎以可预测的模式定义了对,并且只在最后使用 Collections.shuffle(pairs) 随机化了对的顺序。
  • 没错,我创建了所有可能的配对,然后将它们随机播放。如有必要,您可以使用 `Collections.shuffle(pairs, new SecureRandom());`。也许我在某个地方错过了重点?但你会得到随机对:)
  • 我认为你的循环有点低效。您循环遍历 all 值,然后测试您在每次迭代中是否具有相同的元素,以及在每次迭代中遍历 all 先前的对。这是O(n^3)。如果您查看我的答案,您将了解如何正确找到唯一对。
  • 您对!pairs.contains(newPair) 中的一件事是正确的,但是评论统计表明您不必这样做,如果源列表中没有重复项.如问题A-BB-A 中所述,需要找到。查看3. C - D4. D -C(在原始问题中)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-25
相关资源
最近更新 更多