F #: хотите повторно инициализировать перечислитель

У меня есть последовательность делителей простых чисел, которую я хочу перебрать для каждого кандидата на простые числа. Я использую GetEnumerator (), MoveNext () и Current. Я не могу повторно инициализировать перечислитель, чтобы он начал с самого начала. Я попробовал Reset (), который скомпилирован, но выдает ошибку выполнения: не реализовано.

Я использую F # 2.0 Interactive build 4.0.40219.1

Какие-либо предложения?

С уважением, Дуг


Чтобы прояснить проблему: для каждого основного кандидата N я хочу перебрать последовательность простых делителей (примерно до sqrt N) и полностью разложить N на множители или определить, является ли оно простым. Используя GetEnumerator, MoveNext, подход Current работает для первого основного кандидата, но для второго основного кандидата я хочу повторить свою последовательность делителей с самого начала. Похоже, что единственный способ сделать это - создать новый итератор (что неудобно для большого числа основных кандидатов) или создать новую последовательность простых чисел (чего я не хочу делать).

Предложение использовать что-то вроде «divisors in seqPrimes», кажется, исчерпывает все делители перед остановкой, но я хочу остановиться, как только простой делитель делит первого кандидата.

Если в приведенных выше утверждениях есть ошибка в моей логике, пожалуйста, дайте мне знать.

Я исследовал Seq.cache, и у меня это сработало. Полученный код выглядит следующим образом:


// Recursive isprime function (modified from MSDN)
let isPrime n =
    let rec check i =
        i > n/2 || (n % i <> 0 && check (i + 2))
    if n = 2 then true
    elif (n%2) = 0 then false
    else check 3

let seqPrimes = seq { for n in 2 .. 100000 do if isPrime n then yield n }

// Cache the sequence to avoid recomputing the sequence elements.
let cachedSeq = Seq.cache seqPrimes


// find the divisors of n (or determine prime) using the seqEnum enumerator 
let rec testPrime n (seqEnum:System.Collections.Generic.IEnumerator<int>) =
  if n = 1 then printfn "completely factored"
  else
    let nref = ref n
    if seqEnum.MoveNext() then
      let divisor = seqEnum.Current
      //printfn "trial divisor %A" divisor
      if divisor*divisor > n then printfn "prime %A" !nref
      else
        while ((!nref % divisor) = 0) do
          printfn "divisor %A" divisor
          nref := !nref / divisor
        testPrime !nref seqEnum

// test
for x = 1000000 to 1000010 do
  printfn "\ndivisors of %d = " x
  let seqEnum = cachedSeq.GetEnumerator()
  testPrime x seqEnum
  seqEnum.Dispose()   // not needed

person DougT    schedule 16.01.2012    source источник
comment
Я бы обнаружил, что крайне редко действительно нужно явно вызывать элементы в IEnumerator, когда можно просто использовать синтаксис for x in y. Если вам нужно; просто создайте новый перечислитель. Публикация кода будет полезна, потому что я думаю, что мы сможем найти лучшую альтернативу.   -  person vcsjones    schedule 16.01.2012
comment
Вы можете привести конкретный пример, демонстрирующий ошибку?   -  person pad    schedule 16.01.2012
comment
Вы пробовали использовать Seq.cache?   -  person Huusom    schedule 16.01.2012


Ответы (1)


Если вы имеете в виду, что причиной вашей попытки сбросить Enumerator является высокая стоимость восстановления вашей последовательности простых чисел, вы можете рассмотреть кэширование вашей последовательности. Такой способ использования вашей последовательности был бы идиоматическим для F #. Чтобы показать вам, как это сделать, я отсылаю вас к следующему фрагменту, взятому из этот контекст:

let rec primes = 
    Seq.cache <| seq { yield 2; yield! Seq.unfold nextPrime 3 }
and nextPrime n =
    if isPrime n then Some(n, n + 2) else nextPrime(n + 2)
and isPrime n =
    if n >= 2 then
        primes 
        |> Seq.tryFind (fun x -> n % x = 0 || x * x > n)
        |> fun x -> x.Value * x.Value > n
    else false

Вы можете поиграть с этим фрагментом, чтобы увидеть, что штраф за повторное перечисление здесь становится незначительным.

Говоря о Reset() методе IEnumerator, я напоминаю, что он не реализован в текущем F #, т.е. выбрасывает System.NotSupportedException. Обоснование см. В справочнике MSDN.

ДОПОЛНЕНИЕ. Чтобы проверить это с помощью теста, который вы предложили ниже:

for x in [1000000..1000010] do
    printfn "\ndivisors of %d" x
    primes
    |> Seq.takeWhile ((>) (int(sqrt(float x))))
    |> Seq.iter (fun n -> if x%n = 0 then printf "%d " n)

На моем ноутбуке выполнение теста занимает всего 3 мс.

person Gene Belitski    schedule 16.01.2012
comment
Спасибо за все комментарии. Похоже, что для сброса счетчика (чтобы он начинался с начала последовательности) вам необходимо создать новый счетчик (что кажется неудобным для тестирования большого количества кандидатов на простые числа) или создать новую последовательность (чего я не делаю). хочу сделать). - person DougT; 17.01.2012
comment
Я воспользовался ответом на ваш вопрос и отправил его, но он еще не появился. - person DougT; 17.01.2012
comment
@DougT: Верно, сброс перечислителя эквивалентен созданию нового перечислителя; последнее нормально и идиоматично для F #. Использование кэшируемой последовательности позволит вам не платить за производительность за этот курс действий. Вы можете поиграть с приведенным выше кодом в fsi, чтобы убедиться в этом сами. - person Gene Belitski; 17.01.2012
comment
Джин: Спасибо за вашу помощь. - Дуг - person DougT; 17.01.2012