Как написать функцию для общих чисел?

Я новичок в F # и считаю, что вывод типа действительно классный. Но в настоящее время кажется, что это также может привести к дублированию кода, что не очень круто. Я хочу суммировать цифры такого числа:

let rec crossfoot n =
  if n = 0 then 0
  else n % 10 + crossfoot (n / 10)

crossfoot 123

Это правильно печатает 6. Но теперь мой номер ввода не соответствует int 32 бит, поэтому мне нужно его преобразовать.

let rec crossfoot n =
  if n = 0L then 0L
  else n % 10L + crossfoot (n / 10L)

crossfoot 123L

Тогда a BigInteger подходит мне и догадывается, что...

Конечно, я мог бы иметь только bigint версию и вводить параметры ввода и выводить параметры по мере необходимости. Но сначала я предполагаю, что использование BigInteger над int имеет некоторые нарушения производительности. Второй let cf = int (crossfoot (bigint 123)) просто не читается.

Разве нет общего способа написать это?

Ответ 1

Основываясь на Брайане и Стивене, ответьте, вот какой полный код:

module NumericLiteralG = 
    let inline FromZero() = LanguagePrimitives.GenericZero
    let inline FromOne() = LanguagePrimitives.GenericOne
    let inline FromInt32 (n:int) =
        let one : ^a = FromOne()
        let zero : ^a = FromZero()
        let n_incr = if n > 0 then 1 else -1
        let g_incr = if n > 0 then one else (zero - one)
        let rec loop i g = 
            if i = n then g
            else loop (i + n_incr) (g + g_incr)
        loop 0 zero 

let inline crossfoot (n:^a) : ^a =
    let (zero:^a) = 0G
    let (ten:^a) = 10G
    let rec compute (n:^a) =
        if n = zero then zero
        else ((n % ten):^a) + compute (n / ten)
    compute n

crossfoot 123
crossfoot 123I
crossfoot 123L


ОБНОВЛЕНИЕ: простой ответ

Здесь выполняется автономная реализация без модуля NumericLiteralG и чуть менее ограничивающий вывод:

let inline crossfoot (n:^a) : ^a =
    let zero:^a = LanguagePrimitives.GenericZero
    let ten:^a = (Seq.init 10 (fun _ -> LanguagePrimitives.GenericOne)) |> Seq.sum
    let rec compute (n:^a) =
        if n = zero then zero
        else ((n % ten):^a) + compute (n / ten)
    compute n

Объяснение

В F # существует два типа генериков: 1) полиморфизм типа выполнения через интерфейсы/наследование .NET и 2) обобщенные обобщенные данные. Компиляционные дженерики необходимы для размещения таких вещей, как общие числовые операции и что-то вроде утиного ввода (явные ограничения элементов). Эти функции являются неотъемлемой частью F #, но не поддерживаются в .NET, поэтому в процессе компиляции необходимо обрабатывать F #.

Каретка (^) используется для дифференциации статически разрешенных (параметры времени компиляции) из обычных (которые используют апостроф). Короче говоря, 'a обрабатывается во время выполнения ^a во время компиляции, поэтому функция должна быть отмечена inline.

Я никогда раньше не пытался писать что-то подобное. Это оказалось неуклюже, чем я ожидал. Самое большое препятствие, которое я вижу для написания общего числового кода в F #, - это создание экземпляра общего номера, отличного от нуля или одного. См. Реализацию FromInt32 в этом ответе, чтобы понять, что я имею в виду. GenericZero и GenericOne встроены, и они реализованы с использованием техник, которые недоступны в коде пользователя. В этой функции, поскольку нам понадобилось лишь небольшое число (10), я создал последовательность из 10 GenericOne и суммировал их.

Я также не могу объяснить, почему нужны все аннотации типов, за исключением того, что он появляется каждый раз, когда компилятор сталкивается с операцией на родовом типе, похоже, считает, что он имеет дело с новым типом. Таким образом, он заканчивает вывод какого-то странного типа с дублирующимися перераспределениями (например, может потребоваться (+) несколько раз). Добавление аннотаций типа позволяет узнать, что мы имеем дело с одним и тем же типом. Код отлично работает без них, но добавление их упрощает выводимую подпись.

Ответ 2

В дополнение к методу kvb, использующему числовые литералы (ссылка Брайана), я имел большой успех, используя другой метод, который может давать более строгие сигнатуры структурного типа, а также может использоваться для создания предварительно вычисляемых функций типа для лучшего производительность, а также контроль над поддерживаемыми числовыми типами (поскольку вы часто захотите, например, поддерживать все интегральные типы, но не рациональные типы): F # Статические ограничения типа пользователя.

Вслед за обсуждением Дэниэлом и мной были о предполагаемых типах сигнатур, полученных различными методами, вот обзор:

Метод NumericLiteralG

module NumericLiteralG = 
    let inline FromZero() = LanguagePrimitives.GenericZero
    let inline FromOne() = LanguagePrimitives.GenericOne
    let inline FromInt32 (n:int) =
        let one = FromOne()
        let zero = FromZero()
        let n_incr = if n > 0 then 1 else -1
        let g_incr = if n > 0 then one else (zero - one)
        let rec loop i g = 
            if i = n then g
            else loop (i + n_incr) (g + g_incr)
        loop 0 zero 

Crossfoot без добавления аннотаций типа:

let inline crossfoot1 n =
    let rec compute n =
        if n = 0G then 0G
        else n % 10G + compute (n / 10G)
    compute n

val inline crossfoot1 :
   ^a ->  ^e
    when ( ^a or  ^b) : (static member ( % ) :  ^a *  ^b ->  ^d) and
          ^a : (static member get_Zero : ->  ^a) and
         ( ^a or  ^f) : (static member ( / ) :  ^a *  ^f ->  ^a) and
          ^a : equality and  ^b : (static member get_Zero : ->  ^b) and
         ( ^b or  ^c) : (static member ( - ) :  ^b *  ^c ->  ^c) and
         ( ^b or  ^c) : (static member ( + ) :  ^b *  ^c ->  ^b) and
          ^c : (static member get_One : ->  ^c) and
         ( ^d or  ^e) : (static member ( + ) :  ^d *  ^e ->  ^e) and
          ^e : (static member get_Zero : ->  ^e) and
          ^f : (static member get_Zero : ->  ^f) and
         ( ^f or  ^g) : (static member ( - ) :  ^f *  ^g ->  ^g) and
         ( ^f or  ^g) : (static member ( + ) :  ^f *  ^g ->  ^f) and
          ^g : (static member get_One : ->  ^g)

Crossfoot добавляет некоторые аннотации типов:

let inline crossfoot2 (n:^a) : ^a =
    let (zero:^a) = 0G
    let (ten:^a) = 10G
    let rec compute (n:^a) =
        if n = zero then zero
        else ((n % ten):^a) + compute (n / ten)
    compute n

val inline crossfoot2 :
   ^a ->  ^a
    when  ^a : (static member get_Zero : ->  ^a) and
         ( ^a or  ^a0) : (static member ( - ) :  ^a *  ^a0 ->  ^a0) and
         ( ^a or  ^a0) : (static member ( + ) :  ^a *  ^a0 ->  ^a) and
          ^a : equality and  ^a : (static member ( + ) :  ^a *  ^a ->  ^a) and
          ^a : (static member ( % ) :  ^a *  ^a ->  ^a) and
          ^a : (static member ( / ) :  ^a *  ^a ->  ^a) and
          ^a0 : (static member get_One : ->  ^a0)

Тип записи

module LP =
    let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a>
    let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a>
    let inline two_of (target:'a) : 'a = one_of(target) + one_of(target)
    let inline three_of (target:'a) : 'a = two_of(target) + one_of(target)
    let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target)

    let inline any_of (target:'a) (x:int) : 'a =
        let one:'a = one_of target
        let zero:'a = zero_of target
        let xu = if x > 0 then 1 else -1
        let gu:'a = if x > 0 then one else zero-one

        let rec get i g = 
            if i = x then g
            else get (i+xu) (g+gu)
        get 0 zero 

    type G<'a> = {
        negone:'a
        zero:'a
        one:'a
        two:'a
        three:'a
        any: int -> 'a
    }    

    let inline G_of (target:'a) : (G<'a>) = {
        zero = zero_of target
        one = one_of target
        two = two_of target
        three = three_of target
        negone = negone_of target
        any = any_of target
    }

open LP

Crossfoot, никаких аннотаций, требуемых для красивой выводной сигнатуры:

let inline crossfoot3 n =
    let g = G_of n
    let ten = g.any 10
    let rec compute n =
        if n = g.zero then g.zero
        else n % ten + compute (n / ten)
    compute n

val inline crossfoot3 :
   ^a ->  ^a
    when  ^a : (static member ( % ) :  ^a *  ^a ->  ^b) and
         ( ^b or  ^a) : (static member ( + ) :  ^b *  ^a ->  ^a) and
          ^a : (static member get_Zero : ->  ^a) and
          ^a : (static member get_One : ->  ^a) and
          ^a : (static member ( + ) :  ^a *  ^a ->  ^a) and
          ^a : (static member ( - ) :  ^a *  ^a ->  ^a) and  ^a : equality and
          ^a : (static member ( / ) :  ^a *  ^a ->  ^a)

Crossfoot, no annotations, принимает предварительно вычисленные экземпляры G:

let inline crossfootG g ten n =
    let rec compute n =
        if n = g.zero then g.zero
        else n % ten + compute (n / ten)
    compute n

val inline crossfootG :
  G< ^a> ->  ^b ->  ^a ->  ^a
    when ( ^a or  ^b) : (static member ( % ) :  ^a *  ^b ->  ^c) and
         ( ^c or  ^a) : (static member ( + ) :  ^c *  ^a ->  ^a) and
         ( ^a or  ^b) : (static member ( / ) :  ^a *  ^b ->  ^a) and
          ^a : equality

Я использую вышеописанное на практике с тех пор, как я могу сделать предварительно рассчитанные типы конкретных версий, которые не страдают от стоимости производительности общих языковых преимуществ:

let gn = G_of 1  //int32
let gL = G_of 1L //int64
let gI = G_of 1I //bigint

let gD = G_of 1.0  //double
let gS = G_of 1.0f //single
let gM = G_of 1.0m //decimal

let crossfootn = crossfootG gn (gn.any 10)
let crossfootL = crossfootG gL (gL.any 10)
let crossfootI = crossfootG gI (gI.any 10)
let crossfootD = crossfootG gD (gD.any 10)
let crossfootS = crossfootG gS (gS.any 10)
let crossfootM = crossfootG gM (gM.any 10)

Ответ 3

Поскольку вопрос о том, как сделать сигнатуры типа менее волосатыми при использовании обобщенных числовых литералов, я подумал, что поставлю свои два цента. Основная проблема заключается в том, что операторы F # могут быть асимметричными, так что вы можете делать такие вещи, как System.DateTime.Now + System.TimeSpan.FromHours(1.0), что означает, что вывод типа F # добавляет переменные промежуточного типа при выполнении арифметических операций.

В случае числовых алгоритмов эта потенциальная асимметрия обычно не бывает полезной, и возникающий в результате взрыва сигнатур типа довольно уродлив (хотя, как правило, это не влияет на способность F # правильно применять функции при заданных конкретных аргументах). Одним из возможных решений этой проблемы является ограничение типов арифметических операторов в пределах области действия, о которой вы заботитесь. Например, если вы определяете этот модуль:

module SymmetricOps =
  let inline (+) (x:'a) (y:'a) : 'a = x + y
  let inline (-) (x:'a) (y:'a) : 'a = x - y
  let inline (*) (x:'a) (y:'a) : 'a = x * y
  let inline (/) (x:'a) (y:'a) : 'a = x / y
  let inline (%) (x:'a) (y:'a) : 'a = x % y
  ...

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

module NumericLiteralG = 
  open SymmetricOps
  let inline FromZero() = LanguagePrimitives.GenericZero
  let inline FromOne() = LanguagePrimitives.GenericOne
  let inline FromInt32 (n:int) =
      let one = FromOne()
      let zero = FromZero()
      let n_incr = if n > 0 then 1 else -1
      let g_incr = if n > 0 then one else (zero - one)
      let rec loop i g = 
          if i = n then g
          else loop (i + n_incr) (g + g_incr)
      loop 0 zero

и

open SymmetricOps
let inline crossfoot x =
  let rec compute n =
      if n = 0G then 0G
      else n % 10G + compute (n / 10G)
  compute x

а предполагаемый тип - относительно чистый

val inline crossfoot :
   ^a ->  ^a
    when  ^a : (static member ( - ) :  ^a *  ^a ->  ^a) and
          ^a : (static member get_One : ->  ^a) and
          ^a : (static member ( % ) :  ^a *  ^a ->  ^a) and
          ^a : (static member get_Zero : ->  ^a) and
          ^a : (static member ( + ) :  ^a *  ^a ->  ^a) and
          ^a : (static member ( / ) :  ^a *  ^a ->  ^a) and  ^a : equality

в то время как мы по-прежнему получаем хорошее, читаемое определение для crossfoot.

Ответ 5

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

open System.Numerics
// optional
open MathNet.Numerics

module NumericLiteralG = 
    type GenericNumber = GenericNumber with
        static member instance (GenericNumber, x:int32, _:int8) = fun () -> int8 x
        static member instance (GenericNumber, x:int32, _:uint8) = fun () -> uint8 x
        static member instance (GenericNumber, x:int32, _:int16) = fun () -> int16 x
        static member instance (GenericNumber, x:int32, _:uint16) = fun () -> uint16 x
        static member instance (GenericNumber, x:int32, _:int32) = fun () -> x
        static member instance (GenericNumber, x:int32, _:uint32) = fun () -> uint32 x
        static member instance (GenericNumber, x:int32, _:int64) = fun () -> int64 x
        static member instance (GenericNumber, x:int32, _:uint64) = fun () -> uint64 x
        static member instance (GenericNumber, x:int32, _:float32) = fun () -> float32 x
        static member instance (GenericNumber, x:int32, _:float) = fun () -> float x
        static member instance (GenericNumber, x:int32, _:bigint) = fun () -> bigint x
        static member instance (GenericNumber, x:int32, _:decimal) = fun () -> decimal x
        static member instance (GenericNumber, x:int32, _:Complex) = fun () -> Complex.op_Implicit x
        static member instance (GenericNumber, x:int64, _:int64) = fun () -> int64 x
        static member instance (GenericNumber, x:int64, _:uint64) = fun () -> uint64 x
        static member instance (GenericNumber, x:int64, _:float32) = fun () -> float32 x
        static member instance (GenericNumber, x:int64, _:float) = fun () -> float x
        static member instance (GenericNumber, x:int64, _:bigint) = fun () -> bigint x
        static member instance (GenericNumber, x:int64, _:decimal) = fun () -> decimal x
        static member instance (GenericNumber, x:int64, _:Complex) = fun () -> Complex.op_Implicit x
        static member instance (GenericNumber, x:string, _:float32) = fun () -> float32 x
        static member instance (GenericNumber, x:string, _:float) = fun () -> float x
        static member instance (GenericNumber, x:string, _:bigint) = fun () -> bigint.Parse x
        static member instance (GenericNumber, x:string, _:decimal) = fun () -> decimal x
        static member instance (GenericNumber, x:string, _:Complex) = fun () -> Complex(float x, 0.0)
        // MathNet.Numerics
        static member instance (GenericNumber, x:int32, _:Complex32) = fun () -> Complex32.op_Implicit x
        static member instance (GenericNumber, x:int32, _:bignum) = fun () -> bignum.FromInt x
        static member instance (GenericNumber, x:int64, _:Complex32) = fun () -> Complex32.op_Implicit x
        static member instance (GenericNumber, x:int64, _:bignum) = fun () -> bignum.FromBigInt (bigint x)
        static member instance (GenericNumber, x:string, _:Complex32) = fun () -> Complex32(float32 x, 0.0f)
        static member instance (GenericNumber, x:string, _:bignum) = fun () -> bignum.FromBigInt (bigint.Parse x)

    let inline genericNumber num = Inline.instance (GenericNumber, num) ()

    let inline FromZero () = LanguagePrimitives.GenericZero
    let inline FromOne () = LanguagePrimitives.GenericOne
    let inline FromInt32 n = genericNumber n
    let inline FromInt64 n = genericNumber n
    let inline FromString n = genericNumber n

эта реализация выполняется без сложной итерации во время трансляции. Он использует FsControl для модуля Instance.

http://www.fssnip.net/mv

Ответ 6

Является ли перекрестная атака именно тем, что вы хотите сделать, или просто суммирует цифры длинного числа?

потому что если вы просто хотите суммировать цифры, то:

let crossfoot (x:'a) = x.ToString().ToCharArray()
                       |> (Array.fold(fun acc x' -> if x' <> '.' 
                                                    then acc + (int x')
                                                    else acc) 0)

... И все готово.

В любом случае, Можете ли вы преобразовать материал в строку, отбросить десятичную точку, вспомнить, где находится десятичная точка, интерпретировать ее как int, запустить кроссфут?

Вот мое решение. Я не уверен точно, как вы хотите, чтобы "crossfoot" работал, когда вы добавили десятичную точку.

Например, вы хотите: crossfoot(123.1) = 7 или crossfoot(123.1) = 6.1? (Я предполагаю, что вы хотите последнего)

В любом случае, код позволяет работать с числами как generics.

let crossfoot (n:'a) = // Completely generic input

    let rec crossfoot' (a:int) =       // Standard integer crossfoot
        if a = 0 then 0 
        else a%10 + crossfoot' (a / 10)

    let nstr = n.ToString()

    let nn   = nstr.Split([|'.'|])    // Assuming your main constraint is float/int

    let n',n_ = if nn.Length > 1 then nn.[0],nn.[1] 
                else nn.[0],"0"

    let n'',n_' = crossfoot'(int n'),crossfoot'(int n_)

    match n_' with
    | 0 -> string n''
    | _ -> (string n'')+"."+(string n_')

Если вам нужно вводить большие целые числа или объекты int64, то, как работает кроссфут, вы можете просто разделить большое число на куски bitesize (строки) и передать их в эту функцию и добавить их вместе.