2015-12-04 48 views
0

在開發玩具項目時,我遇到了生成一組N 2d點的問題,其中每個點都在距離A和B之間從集合中的每個其他點(以及在某個絕對範圍內)。Java功能流:在距離A和B之間生成一組隨機點

我更喜歡使用java流和lambdas進行練習,因爲它們的優雅和易於並行化的可能性,所以我不是問如何以命令的方式解決這個問題!

,首先來考慮的解決方案是:

  1. 與隨機矢量
  2. 直到組種子的集合(或列表)到達尺寸N:
    1. 創建一個隨機向量A和B之間的長度並將其添加到隨機「父」向量
    2. 如果它在集合外或比集合中的任何向量A都更接近,則丟棄它,否則將其添加到集合
    3. 重複

這樣的功能性的方式時,由於流在新生成的元素在同一個依賴於先前生成的元素這將與命令式編程(循環),平凡的我,但我很困惑流。

下面是我想到的 - 注意開始時的噁心循環。

while (pointList.size() < size) { 
    // find a suitable position, not too close and not too far from another one 
    Vec point = 
      // generate a stream of random vectors 
      Stream.generate(vecGen::generate) 
        // elongate the vector and add it to the position of one randomly existing vector 
        .map(v -> listSelector.getRandom(pointList).add(v.mul(random.nextDouble() * (maxDistance - minDistance) + minDistance))) 
        // remove those that are outside the borders 
        .filter(v -> v.length < diameter) 
        // remove those that are too close to another one 
        .filter(v -> pointList.stream().allMatch(p -> Vec.distance(p, v) > minDistance)) 
        // take the first one 
        .findAny().get(); 


    pointList.add(point); 
} 

我知道,這個循環可能永遠不會終止,取決於參數 - 真正的代碼有額外的檢查。

想到的一個工作功能解決方案是生成N個向量的完全隨機集合,直到其中一個集合滿足條件,但性能會很糟糕。此外,這將繞過我面臨的問題:是否有可能在流中添加新元素的同時處理已經生成的元素(很確定這會違反一些基本原則,所以我想答案是否定的) ?

有沒有辦法做到這一點的功能 - 而不是太浪費 - 方式?

+0

發現這個別處:「Java的8流圖書館向分裂主要是面向流轉換爲並行處理更小的塊,所以狀態管道階段是相當有限的,做這樣的事情讓當前流元素的索引和**不支持訪問相鄰流元素**。「 @ http://stackoverflow.com/a/20507988/88070 猜測這意味着它是不可能的。 – stefs

+0

流不應該用於這種方式。不要僅僅因爲它們有很好的語法而使用流,而是因爲它們實際上解決了你的問題。 – Daniel

+0

@丹尼爾,在這種情況下,我完全可以放棄流,因爲它們不會添加任何內容 - 除了可讀性。據我所知,這(訪問相鄰的流元素)在其他語言(例如haskell)中是可能的。 – stefs

回答

1

一個簡單的解決方案如下所示。 Pair類可以在Apache commons lang3中找到。

public List<Pair<Double, Double>> generate(int N, double A, double B) { 
    Random ySrc = new Random(); 

    return new Random() 
      .doubles(N, A, B) 
      .boxed() 
      .map(x -> Pair.of(x, (ySrc.nextDouble() * (B - A)) + A)) 
      .collect(Collectors.toList()); 
} 

我的原溶液(見上文)錯過了A和B所代表的任何兩點之間的最小和最大距離的點。所以我會提出一個不同的解決方案(方式更復雜),它依賴於在單位圓上生成點。我用一個隨機距離來縮放(乘以)表示該點的單位矢量,最小值爲-1/2 B,最大值爲1/2 B.這種方法將點均勻分佈在半徑爲1/2 B的圓所包圍的區域內。這解決了點之間的最大距離限制。如果A和B之間有足夠的差別,那麼A和B不會太大,那麼最小距離限制也可能得到滿足。最大距離限制的提供可以用純函數代碼完成(即無副作用)。

爲了確保滿足最小約束條件,需要一些命令式的代碼(即副作用)。爲此,我使用了一個帶有副作用的謂詞。謂詞積累符合最小約束條件的點,並在N點累計時返回true。

注意運行時間未知,因爲點是隨機生成的。在N = 100,A = 1.0和B = 30.0的情況下,測試代碼運行得很快。我嘗試了B值爲10和20,並沒有等待它結束。如果你想要更緊密的點,你可能需要加快這個代碼或者開始看線性求解器。

public class RandomPoints { 
    /** 
    * The stop rule is a predicate implementation with side effects. Not sure 
    * about the wisdom of this approach. The class does not support concurrent 
    * modification. 
    * 
    * @author jgmorris 
    * 
    */ 
    private class StopRule implements Predicate<Pair<Double, Double>> { 
     private final int N; 
     private final List<Pair<Double, Double>> points; 

     public StopRule(int N, List<Pair<Double, Double>> points) { 
      this.N = N; 
      this.points = points; 
     } 

     @Override 
     public boolean test(Pair<Double, Double> t) { 
      // Brute force test. A hash based test would work a lot better. 
      for (int i = 0; i < points.size(); ++i) { 
       if (distance(t, points.get(i)) < dL) { 
        // List size unchanged, continue 
        return false; 
       } 
      } 
      points.add(t); 
      return points.size() >= N; 
     } 

    } 

    private final double dL; 
    private final double dH; 
    private final double maxRadius; 
    private final Random r; 

    public RandomPoints(double dL, double dH) { 
     this.dL = dL; 
     this.dH = dH; 
     this.maxRadius = dH/2; 
     r = new Random(); 
    } 

    public List<Pair<Double, Double>> generate(int N) { 
     List<Pair<Double, Double>> points = new ArrayList<>(); 
     StopRule pred = new StopRule(N, points); 

     new Random() 
       // Generate a uniform distribution of doubles between 0.0 and 
       // 1.0 
       .doubles() 
       // Transform primitive double into a Double 
       .boxed() 
       // Transform to a number between 0.0 and 2&piv; 
       .map(u -> u * 2 * Math.PI) 
       // Generate a random point 
       .map(theta -> randomPoint(theta)) 
       // Add point to points if it meets minimum distance criteria. 
       // Stop when enough points are gathered. 
       .anyMatch(p -> pred.test(p)); 

     return points; 
    } 

    private final Pair<Double, Double> randomPoint(double theta) { 
     double x = Math.cos(theta); 
     double y = Math.sin(theta); 
     double radius = randRadius(); 
     return Pair.of(radius * x, radius * y); 
    } 

    private double randRadius() { 
     return maxRadius * (r.nextDouble() - 0.5); 
    } 

    public static void main(String[] args) { 
     RandomPoints rp = new RandomPoints(1.0, 30.0); 
     List<Pair<Double, Double>> points = rp.generate(100); 

     for (int i = 0; i < points.size(); ++i) { 
      for (int j = 1; j < points.size() - 1; ++j) { 
       if (i == j) { 
        continue; 
       } 

       double distance = distance(points.get(i), points.get(j)); 

       if (distance < 1.0 || distance > 30.0) { 
        System.out.println("oops"); 
       } 
      } 
     } 
    } 

    private static double distance(Pair<Double, Double> p1, Pair<Double, Double> p2) { 
     return Math.sqrt(Math.pow(p1.getLeft() - p2.getLeft(), 2.0) + Math.pow(p1.getRight() - p2.getRight(), 2.0)); 
    } 

} 
+0

謝謝,但我不認爲這是對我的問題的有效答案;如果我理解這個權利,這可能會產生相同的向量 - 或者至少是比A更接近的點。 – stefs

+1

謝謝你指出我的誤解。我添加了第二個解決方案,我相信它能正確處理這些約束。 –

+0

謝謝。一個有趣的方法,即使它不是我正在尋找的(我現在認爲不可能的純功能解決方案)。 – stefs

相關問題