当前位置: 首页 > 知识库问答 >
问题:

在Java中高效地创建组合而不存在内存问题

裴泰平
2023-03-14

一些背景

我正在解决一个问题,我将集合存储在hashmap中,其中键是集合名,即Set1--

该程序的目的是从用户那里取一个值作为“阈值”,即2,它用于集合之间的最小共性。如果达到或超过阈值,程序建议集合之间合并。

我已经创建了一个组合生成器,它将生成集合名称之间的所有可能组合,以便与未考虑的顺序进行比较,即(Set1,Set2,),(Set1,Set3),(Set2,Set3),(Set1,Set2,Set3)。

然后使用这些组合集来实际比较这些集合。如果满足阈值,该组合将存储在单独的列表中,以作为可能的合并输出给用户。在输出之前,这些是删除子组合的逻辑,也就是说,如果(Set1,Set2,Set3)是一个可能的合并,那么你可以忽略其他3个子组合,因为这个超级组合已经覆盖了它。然后输出建议的合并。

问题所在

当我们达到一定数量的集合进行比较时,比如说超过17个集合,我们就会出现内存不足的问题,因为有数百万个组合正在被创建。我希望您能帮助我理解其他方法,或者我们如何改进这种方法。它可以工作,但效率不够:(

组合创建者

/**
 * Iterates through the setsToBeCompared ArrayList and gets all the combinations
 *
 * @return - ArrayList with all the possible combinations
 */
public ArrayList<String> generateCombinations(ArrayList<String> setsToBeCompared) {
    List<List<String>> temp = new ArrayList<>();
    ArrayList<String> a = new ArrayList<>();
    for (int i = 2; i <= 3; i++) {
        temp = calculateCombinations(setsToBeCompared, i);
        for (List<String> list : temp) {
            a.add(list.toString());
        }                       
    }
    return a;
        }

/**
 * Calculates all the combination given by the parameters
 *
 * @param values - the names of the sets to be compared
 * @param size   - where to start from
 * @return - List of all possible calculated combinations
 */
private List<List<String>> calculateCombinations(List<String> values, int size) {

    if (0 == size) {
        return Collections.singletonList(Collections.<String>emptyList());
    }

    if (values.isEmpty()) {
        return Collections.emptyList();
    }

    List<List<String>> combination = new LinkedList<List<String>>();

    String actual = values.iterator().next();
    List<String> subSet = new LinkedList<String>(values);
    subSet.remove(actual);
    List<List<String>> subSetCombination = calculateCombinations(subSet, size - 1);
    for (List<String> set : subSetCombination) {
        List<String> newSet = new LinkedList<String>(set);
        newSet.add(0, actual);
        combination.add(newSet);
    }

    combination.addAll(calculateCombinations(subSet, size));

    return combination;
}

共有2个答案

李奕
2023-03-14

所以,总结一下我作为评论发表的观点。

在您的情况下,生成集合的所有子集绝对不是一个选项,因为此类子集的数量将为~2N。当N=50时,它比地球在纳秒内存在的时间还要长。

我假设从集合的子集切换到它们的项的子集。比如说,M子集有N不同的项,合并阈值是T。因此,您需要尝试大小的~NTk-组合,看看哪些子集可以通过项的组合合并,这对于小T是可以接受的。

算法将如下:

let D - collection of initial sets
let S - collection of distinct elements in sets across D

for each k-combination c over S {
   M = new M(c)          // merge object, which stores subset of merged sets and k-combination by which they are merged
   for each (s in D) {
      if (s.containsAll(c))
         M.sets.add(s)
   }
   if (M.sets.size > 0)  // some sets was merged through c
       merges.add(M)
} 

之后,获取所有可能的合并对,删除那些被其他合并完全覆盖的:

for each m in merges {
    for each m1 in merges {
        if (m.sets.containsAll(m1.sets))
            m1.markDeleted()
    }
}
林念
2023-03-14

像这样的东西怎么样(将使用更少的内存,但仍然需要检查大量的值-2^N)

import static java.util.stream.IntStream.range;

public class Subsets implements Iterator<List<Integer>> {

    private final int level;
    private final LinkedList<List<Integer>> queue = new LinkedList<>();


    public Subsets(int level) {
        this.level = level;
        range(0, level).forEach(i -> queue.add(Arrays.asList(i)));
    }

    @Override
    public boolean hasNext() {
        return !queue.isEmpty();
    }

    public List<Integer> next() {
        List<Integer> list = queue.removeFirst();
        int maxValue = list.get(list.size() - 1);

        if(list.size() < level) {

            for (int k = maxValue+1; k < level; k++) {
                List<Integer> newList = new ArrayList<>(list);
                newList.add(k);
                queue.addFirst(newList);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        Subsets s4 = new Subsets(4);
        while (s4.hasNext()) {
            System.err.println(s4.next());

        }
    }
}

要使用它,您需要将集合(键)的名称映射为整数。示例输出:

[0]
[0, 3]
[0, 2]
[0, 2, 3]
[0, 1]
[0, 1, 3]
[0, 1, 2]
[0, 1, 2, 3]
[1]
[1, 3]
[1, 2]
[1, 2, 3]
[2]
[2, 3]
[3]
 类似资料:
  • 我正在Windows服务器上使用C#处理存储在IIS服务器上的web应用程序。 null

  • 问题内容: (关于省时的稀疏数组存在一些问题,但我正在寻找内存效率。) 我需要一个相当于或哪些 只需设置一个比以前遇到的密钥大的密钥即可按需增长。(可以假定键为非负数。) 与大多数索引不是(即实际数据不是很稀疏时)的情况下的内存效率差不多。 当索引稀疏时,消耗的空间与非索引的数量成正比。 使用的内存少于(因为这会使键自动装箱并且可能不利用标量键类型)。 可以获取或设置摊销log(N)时间中的元素,

  • 我正在尝试使用Micronaut中的可组合存储库实现一个方法。 我有: 在这里,EmployeeRepositoryCustom是一个带有“list()”方法的接口: 然后我有一个实现接口方法的EmployeeRepositoryCustomImpl类: 当我使用以下方法调用该方法时: 我得到以下消息: 我尝试在EmployeeRepositoryCustom和EmployeeRepository

  • 问题内容: 我的磁盘中有40MB的文件,我需要使用字节数组将其“映射”到内存中。 最初,我认为将文件写入ByteArrayOutputStream是最好的方法,但我发现在复制操作期间的某个时刻它会占用约160MB的堆空间。 有人知道不使用三倍于RAM的文件大小的更好方法吗? 更新: 感谢您的回答。我注意到我可以减少内存消耗,告诉ByteArrayOutputStream初始大小比原始文件的大小稍大

  • 如何使用itext7在内存流中创建PDF而不是物理文件?我不知道如何在最新版本中做到这一点,有帮助吗? 我尝试了以下代码,但pdfSM没有正确填充: 完整的测试代码如下所示供您参考,它在通过filePath进入PdfWriter时有效,但不适用于内存流:

  • 问题内容: 我刚刚接受采访,并被要求使用Java 造成内存泄漏。 不用说,我对如何开始创建它一无所知。 一个例子是什么? 问题答案: 这是在纯Java中创建真正的内存泄漏(运行代码无法访问但仍存储在内存中的对象)的好方法: 该应用程序将创建一个长期运行的线程(或使用线程池更快地泄漏)。 线程通过(可选,自定义)加载类。 该类分配大量的内存(例如),在静态字段中存储对它的强引用,然后在中存储对自身的