PairWise匹配数百万条记录

时间:2013-11-21 09:14:01

标签: java algorithm bigdata

我手边有一个算法问题。为了便于解释这个问题,我将使用一个简单的类比。 我有一个输入文件

Country,Exports
Austrailia,Sheep
US, Apple
Austrialia,Beef

结束目标: 我必须找到这对国家之间的共同产品

{"Austrailia,New Zealand"}:{"apple","sheep}
{"Austrialia,US"}:{"apple"}
{"New Zealand","US"}:{"apple","milk"}

流程:

我读入输入并将其存储在TreeMap>在List中,字符串由于许多重复而被实现。 基本上,我按国家汇总。 其中Key是country,值是Exports。

{"austrailia":{"apple","sheep","koalas"}}
{"new zealand":{"apple","sheep","milk"}}
{"US":{"apple","beef","milk"}}

我有大约1200个密钥(国家),总值(出口)总共是8000万。 我对每个键的所有值进行排序:

{"austrailia":{"apple","sheep","koalas"}} -- > {"austrailia":{"apple","koalas","sheep"}}

这很快,因为只有1200个列表需要排序。

for(k1:keys)
   for(k2:keys)
        if(k1.compareTo(k2) <0){ //Dont want to double compare
    List<String> intersectList = intersectList_func(k1's exports,k2's exports);
        countriespair.put({k1,k2},intersectList)
}

这个代码块需要很长时间。我意识到它是O(n2)和大约1200 * 1200的比较。因此,运行了近3个小时到现在.. 有什么办法,我可以加快速度或优化它。 算法明智是最佳选择,还是需要考虑其他技术。

修改由于两个List都是事先排序的,所以intersectList是O(n),其中n是floor的长度(listOne.length,listTwo.length)和NOT O(n2),如下所述

private static List<String> intersectList(List<String> listOne,List<String> listTwo){
        int i=0,j=0;
        List<String> listResult = new LinkedList<String>(); 
        while(i!=listOne.size() && j!=listTwo.size()){
            int compareVal = listOne.get(i).compareTo(listTwo.get(j));
            if(compareVal==0){
                listResult.add(listOne.get(i));
                i++;j++;}               }
            else if(compareVal < 0) i++;
            else if (compareVal >0) j++;   
        }
        return listResult;
    }

11月22日更新 我目前的实施仍然运行了近18个小时。 :|

11月25日更新 我按照Vikram和其他几个人的建议运行了新的实现。这个星期五一直在运行。 我的问题是,如何通过出口而不是国家进行分组可以节省计算复杂性。我发现复杂性是一样的。正如Groo所说,我发现第二部分的复杂性是O(E * C ^ 2),其中E是出口,C是国家。

6 个答案:

答案 0 :(得分:2)

存储类似于以下数据结构的内容: - (以下是伪代码)

ValuesSet ={
apple = {"Austrailia","New Zealand"..}
sheep = {"Austrailia","New Zealand"..}  

}

for k in ValuesSet 
    for k1 in k.values() 
       for k2 in k.values()   
           if(k1<k2)
              Set(k1,k2).add(k)

时间复杂:O(没有与同类产品不同的对)

注意:我可能错了,但我不认为你可以减少这个时间的复杂性

以下是针对您的问题的java实现: -

public class PairMatching {

    HashMap Country;
    ArrayList CountNames;
    HashMap ProdtoIndex;
    ArrayList ProdtoCount;
    ArrayList ProdNames;
    ArrayList[][] Pairs;

    int products=0;
    int countries=0;


    public void readfile(String filename) {
        try {
            BufferedReader br = new BufferedReader(new FileReader(new File(filename)));
            String line;
            CountNames = new ArrayList();
            Country = new HashMap<String,Integer>();
            ProdtoIndex = new HashMap<String,Integer>();
            ProdtoCount = new ArrayList<ArrayList>();
            ProdNames = new ArrayList();
            products = countries = 0;
            while((line=br.readLine())!=null) {
                String[] s = line.split(",");
                s[0] = s[0].trim();
                s[1] = s[1].trim();
                int k;
                if(!Country.containsKey(s[0])) {
                    CountNames.add(s[0]);
                    Country.put(s[0],countries);
                    k = countries;
                    countries++;
                } 
                else {
                    k =(Integer) Country.get(s[0]);
                }
                if(!ProdtoIndex.containsKey(s[1])) {
                    ProdNames.add(s[1]);
                    ArrayList n = new ArrayList();
                    ProdtoIndex.put(s[1],products);
                    n.add(k);
                    ProdtoCount.add(n);
                    products++;
                }
                else {
                    int ind =(Integer)ProdtoIndex.get(s[1]);
                    ArrayList c =(ArrayList) ProdtoCount.get(ind);
                    c.add(k);
                }
            }
            System.out.println(CountNames);
            System.out.println(ProdtoCount);
            System.out.println(ProdNames);

        } catch (FileNotFoundException ex) {
            Logger.getLogger(PairMatching.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(PairMatching.class.getName()).log(Level.SEVERE, null, ex);
        }


    }

    void FindPairs() {
        Pairs = new ArrayList[countries][countries];
        for(int i=0;i<ProdNames.size();i++) {
            ArrayList curr = (ArrayList)ProdtoCount.get(i);
            for(int j=0;j<curr.size();j++) {
                for(int k=j+1;k<curr.size();k++) {
                    int u =(Integer)curr.get(j);
                    int v = (Integer)curr.get(k);
                    //System.out.println(u+","+v);
                    if(Pairs[u][v]==null) {
                        if(Pairs[v][u]!=null)
                            Pairs[v][u].add(i);
                        else {
                            Pairs[u][v] = new ArrayList();
                            Pairs[u][v].add(i);
                        }
                    }
                    else Pairs[u][v].add(i);
                }
            }
        }
        for(int i=0;i<countries;i++) {
            for(int j=0;j<countries;j++) {
                if(Pairs[i][j]==null)
                    continue;
                ArrayList a = Pairs[i][j];
                System.out.print("\n{"+CountNames.get(i)+","+CountNames.get(j)+"} : ");
                for(int k=0;k<a.size();k++) {
                    System.out.print(ProdNames.get((Integer)a.get(k))+" ");
                }
            }
        }
    }

    public static void main(String[] args) {
       PairMatching pm = new PairMatching();
       pm.readfile("Input data/BigData.txt");
       pm.FindPairs();


    }

}

答案 1 :(得分:2)

这可以在一个语句中使用SQL作为自联接来完成:

测试数据。首先创建一个测试数据集:

Lines <- "Country,Exports
Austrailia,Sheep
Austrailia,Apple
New Zealand,Apple
New Zealand,Sheep
New Zealand,Milk
US,Apple
US,Milk
"
DF <- read.csv(text = Lines, as.is = TRUE)

sqldf 现在我们已DF发出此命令:

library(sqldf)
sqldf("select a.Country, b.Country, group_concat(Exports) Exports
   from DF a, DF b using (Exports) 
   where a.Country < b.Country
   group by a.Country, b.Country
")

给出这个输出:

      Country     Country     Exports
1  Austrailia New Zealand Sheep,Apple
2  Austrailia          US       Apple
3 New Zealand          US  Apple,Milk
带索引的

如果速度太慢,请在“国家/地区”列中添加索引(请务必不要忘记main.部分:

sqldf(c("create index idx on DF(Country)",
   "select a.Country, b.Country, group_concat(Exports) Exports
   from main.DF a, main.DF b using (Exports) 
   where a.Country < b.Country
   group by a.Country, b.Country
"))

如果你耗尽内存,那么添加dbname = tempfile() sqldf参数,使其使用磁盘。

答案 2 :(得分:1)

[更新]与OP的原始算法相比,此处提供的算法不应该提高时间复杂度。两种算法都具有相同的渐近复杂度,并且通过排序列表进行迭代(如OP所做的)通常应该比使用哈希表更好。

您需要按product而不是country对项目进行分组,以便能够快速获取属于某个产品的所有国家/地区。

这将是伪代码:

inputList contains a list of pairs {country, product}

// group by product 
prepare mapA (product) => (list_of_countries)
for each {country, product} in inputList
{      
   if mapA does not contain (product)
      create a new empty (list_of_countries) 
      and add it to mapA with (product) as key

   add this (country) to the (list_of_countries)
}

// now group by country_pair  
prepare mapB (country_pair) => (list_of_products)       
for each {product, list_of_countries} in mapA
{   
   for each pair {countryA, countryB} in list_of_countries
   {
      if mapB does not countain country_pair {countryA, countryB}
         create a new empty (list_of_products) 
         and add it to mapB with country_pair {countryA, countryB} as key

      add this (product) to the (list_of_products)
   }
}

如果您的输入列表长度为N,并且您有不同的C国家和P个不同的产品,则此算法的运行时间应为O(N)第一部分,O(P*C^2)为第二部分。由于您的最终列表需要国家/地区映射到产品列表,因此我认为您不会失去P*C^2复杂性情况下。

我没有用Java编写太多代码,所以我添加了一个C#示例,我相信你可以很容易地移植它:

// mapA maps each product to a list of countries
var mapA = new Dictionary<string, List<string>>();
foreach (var t in inputList)
{
    List<string> countries = null;
    if (!mapA.TryGetValue(t.Product, out countries))
    {
        countries = new List<string>();
        mapA[t.Product] = countries;
    }
    countries.Add(t.Country);
}

// note (this is very important):
// CountryPair tuple must have value-type comparison semantics, 
// i.e. you need to ensure that two CountryPairs are compared
// by value to allow hashing (mapping) to work correctly, in O(1).

// In C# you can also simply use a Tuple<string,string> to 
// represent a pair of countries (which implements this correctly),
// but I used a custom class to emphasize the algorithm

// mapB maps each CountryPair to a list of products
var mapB = new Dictionary<CountryPair, List<string>>();
foreach (var kvp in mapA)
{
    var product = kvp.Key;
    var countries = kvp.Value;

    for (int i = 0; i < countries.Count; i++)
    {
        for (int j = i + 1; j < countries.Count; j++)
        {
            var pair = CountryPair.Create(countries[i], countries[j]);
            List<string> productsForCountryPair = null;
            if (!mapB.TryGetValue(pair, out productsForCountryPair))
            {
                productsForCountryPair = new List<string>();
                mapB[pair] = productsForCountryPair;
            }
            productsForCountryPair.Add(product);
        }*
    }
}

答案 3 :(得分:0)

这是使用Map Reduce的一个很好的例子。

  • 在您的地图阶段,您只需收集属于每个国家/地区的所有出口。
  • 然后,减速机对产品进行分类(产品属于同一国家,因为映射器)

您将受益于可以分布到群集中的分布式并行算法。

答案 4 :(得分:0)

你实际上正在服用O(1 ^相交需要n ^ 2 *。

让我们看看我们是否可以改善交叉时间。我们可以为每个存储相应产品的国家/地区维护地图,因此您有n个国家/地区的n个哈希映射。只需要通过所有产品迭代一次进行初始化。如果要快速查找,请将地图地图维护为:

    HashMap<String,HashMap<String,Boolean>> countryMap = new HashMap<String, HashMap<String,Boolean>>();

现在,如果您想查找国家/地区str1和str2的常见产品,请执行以下操作:

    HashMap<String,Boolean> map1 = countryMap.get("str1");
    HashMap<String,Boolean> map2 = countryMap.get("str2");

    ArrayList<String > common = new ArrayList<String>();
    Iterator it = map1.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry<String,Boolean> pairs = (Map.Entry)it.next();

        //Add to common if it is there in other map
        if(map2.containsKey(pairs.getKey()))
            common.add(pairs.getKey());
    }

所以,如果在一个地图中有k个条目,假设哈希映射查找实现是O(1)(我猜它是java的log k),那么它总是O(n ^ 2 * k)。

答案 5 :(得分:0)

在必要时使用散列图加快速度:

1)浏览数据并使用键创建地图项目和值与该项目相关联的国家/地区列表。所以例如绵羊:澳大利亚,美国,英国,新西兰....

2)创建一个带有每对国家/地区的键的哈希映射,并且(最初)将一个空列表作为值。

3)对于每个项目,检索与其关联的国家/地区列表以及该列表中的每对国家/地区,将该项目添加到步骤(2)中为该对创建的列表中。

4)现在输出每对国家的更新列表。

最大的成本在步骤(3)和(4)中,并且这两种成本在产生的产出量上是线性的,所以我认为这与最优成本相差不远。