Как получить пары элементов из бесконечных списков в Haskell?

Общие Проблемы

у меня есть бесконечный список, и я хочу выбрать пару (a,b) здесь a и b из списка и пара удовлетворяет какую-то собственность. Используя списочные включения не работает, потому что список бесконечный.

Конкретного Экземпляра

я пытаюсь найти пару простых чисел, которые складываются в заданное число (см. этот код гольф проблема).

я определил primes, которым это бесконечный список простых чисел, но когда я наивно пытаюсь выбрать пару простых чисел, как показано ниже, процесс никогда не заканчивается.

goldbach n = head [(a,b) | a<-primes, b<-primes, a+b==n]

я понимаю, что это потому, что список генерируемых простых чисел [(2,2), (2,3), (2,5)...]. В основном, a становится первым элементом из primes, а после b исчерпан, он перейдет на второй элемент. Потому что primes бесконечен, он никогда не будет исчерпан!

есть ли простой способ использовать понимание списка для решить эту проблему? В противном случае, есть ли простое решение?

4 ответов


goldbach n = head [(a,b) | let ps = takeWhile (<n) primes, a<-ps, b<-ps, a+b==n]

это было бы намного быстрее, хотя.

goldbach2 n = aux ps (reverse ps) where
    ps = takeWhile (<n) primes
    aux [] _ = []
    aux _ [] = []
    aux (a:as) (b:bs)  
      | a > b = []
      | otherwise = case compare (a+b) n of
        EQ -> (a,b):aux as bs
        LT -> aux as (b:bs)
        GT -> aux (a:as) bs

самый хороший способ-это использовать ширина-первый список монады. Поскольку список можно рассматривать как просто синтаксический сахар монады (так же, как do), что позволяет вам сделать это выглядеть ровно как то, что вы сейчас:

{-# LANGUAGE MonadComprehensions #-}
import Control.Monad.Omega

goldbach n = head $ runOmega [(a,b) | a<-ps, b<-ps, a+b==n]
  where ps = each primes

жаль, что вы не приняли решение @leftaroundabout. В этом есть много чего еще. Например, другое решение имеет эвристику "все числа должны быть меньше n" (что вы делаете для проблем, где у вас нет этой эвристики?), и несколько других шагов, которые затрудняют доказательство того, что это действительно решение проблемы Гольдбаха - например, можете ли вы продемонстрировать, что способ перечисления простых чисел собирается собрать все полезные пары простых чисел? (Да, но можете ли вы это продемонстрировать? Это слабость это решение)

Итак, здесь я покажу, как вы можете построить решение @leftaroundabout, не говоря слова "монада".

проблема с пониманием списка, которое вы построили сначала, заключается в том, что он ищет решение "глубина-сначала" - возьмите один элемент из первого списка, а затем попробуйте все комбинации с этим элементом. Однако, если существует бесконечное количество комбинаций, вы никогда не сможете перечислить какие-либо пары со вторым элементом из первого списка. Именно здесь нам нужно поговорить о поиске решений" широты первой".

скажем gen генератор решений:

gen x = map (\y -> (x,y)) -- construct pairs for a given element x

скажем gens является генератором генераторов решений:

gens xs ys = map (\x -> gen x ys) xs

все, что нам нужно сделать сейчас, это перечислить решения, взяв по одному решению от каждого генератора за раз. Имейте в виду, если число генераторов бесконечно, мы не можем просто взять их один за другим - это таким образом, мы никогда не получим второе решение от первого генератора, поэтому нам нужно" шагнуть " их как-то:

om xs = f [] [] xs where -- this is your omega enumerating solutions
                         -- from generator of generators
  f [] [] [] = [] -- this is where the pot stops cooking
  f [] gs [] = f gs [] []
  f [] gs (g:gens) = f (g:gs) [] gens -- ok, took one solution from all generators -
                          -- add one generator to the list of generators we enumerate
                          -- next time - this is how we "step" the generators
  f ([]:gs) gs' gens = f gs gs' gens  -- generator eliminator: last solution was taken
                          -- from one generator
  f ((x:xs):gs) gs' gens = x : f gs (xs:gs') gens -- take one solution from the first
                          -- generator, and move the rest of the generator to the list
                          -- for the next iteration

вот и все.

> find (\(x,y) -> x+y==190) $ om $ gens primes primes
Just (179,11)

сейчас, к эффективности. Это все в gens и gen. Добавьте условие gens to takeWhile ((<=n) . (x+)), и это будет работать, даже если гипотеза Гольдбаха неверна (но вам не нужно добавлять ее вообще). Добавьте условие gen до takeWhile (<=x), и вы будете перечислять только пары, где первое простое число больше, чем другой.


или лямбда, дающая бесконечный список пар из бесконечного списка чего угодно

(\x->let p=(\(a:b:oo)->(a,b):p oo) in p x)

на ghci let p=(\x->let p=(\(a:b:oo)->(a,b):p oo) in p x) take 10 (p [1..])