Как написать функцию для общих чисел?
Я новичок в 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
.
Ответ 4
См
Скрытые функции F #
Ответ 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 (строки) и передать их в эту функцию и добавить их вместе.