高效,惯用的方式将列表中没有的两个字符连接到字符串中

时间:2019-01-31 22:32:47

标签: f#

我已经在C#中完成了大部分开发工作,并且正在学习F#。这是我想在C#中做的事情:

string AddChars(char char1, char char2) => char1.ToString() + char2.ToString();

编辑:在C#示例中添加了ToString()方法。

我想用F#编写相同的方法,除此之外我不知道该怎么做:

let addChars char1 char2 = Char.ToString(char1) + Char.ToString(char2)

是否有一种方法可以将这些char连接到一个string而不先将它们都转换成string


旁注:

我还考虑过制作一个char数组并将其转换为string,但这似乎也很浪费。

let addChars (char1:char) (char2: char) = string([|char1; char2|])

2 个答案:

答案 0 :(得分:7)

正如我在评论中所说,您的C#代码不会做您想要的事情(即,将字符连接成字符串)。在C#中,添加charchar将得到int。这样做的原因是因为char类型没有定义+运算符,所以C#恢复为最近的兼容类型,恰好是int。 (Source

因此,要实现此行为,您将需要执行与F#中已经尝试执行的操作类似的操作:

char a = 'a';
char b = 'b';

// This is the wrong way to concatenate chars, because the
// chars will be treated as ints and the result will be 195.
Console.WriteLine(a + b);                              

// These are the correct ways to concatenate characters into
// a single string. The result of all of these will be "ab".
// The third way is the recommended way as it is concise and 
// involves creating the fewest temporary objects.
Console.WriteLine(a.ToString() + b.ToString());        
Console.WriteLine(Char.ToString(a) + Char.ToString(b));
Console.WriteLine(new String(new[] { a, b }));

(请参阅https://dotnetfiddle.net/aEh1FI)

F#与连接两个或更多char不会导致String相同。与C#不同,它代替的结果是另一个char,但是过程是相同的-char的值被视为int并加在一起,结果是char和的表示。

实际上,在F#中将char连接成String的方式就是您已经拥有的,并且是C#等价物的直接翻译:

let a = 'a'
let b = 'b'

// This is still the wrong way (prints 'Ã')
printfn "%O" (a + b)

// These are still the right ways (prints "ab")
printfn "%O" (a.ToString() + b.ToString())
printfn "%O" (Char.ToString(a) + Char.ToString(b))
printfn "%O" (String [| a;b |]) // This is still the best way

(请参阅https://dotnetfiddle.net/ALwI3V)

“从字符数组中获取字符串”方法是最佳方法的原因有两个。首先,它是最简洁的,因为您可以看到该方法提供了两种语言中最短的代码行(并且差异仅随着您将越来越多的char加在一起而增加)。其次,在最终String之前仅创建一个临时对象(数组),而其他两种方法涉及制作两个单独的临时String对象以馈入最终结果。

(此外,由于String构造函数被隐藏在外部源中,因此我不确定它是否以此方式工作,但我想传递给该构造函数的数组将用作{{ 1}}的数据支持,因此最终不会浪费。) String是不可变的,但是将传递的数组直接用作创建的String支持数据可能会导致对数组的引用可能保存在程序的其他位置,并危及String的不变性,因此这种猜测在实践中不会奏效。 (来源:@CaringDev)


在F#中可以做得更惯用的另一种选择是使用String函数来组合两个字符(贷方:@rmunn):

sprintf

(请参阅https://dotnetfiddle.net/Pp9Tee)

但是,有关此方法的警告提示是,几乎可以肯定,它会比上面列出的其他三种方法中的任何一种都要慢得多。这是因为let a = 'a' let b = 'b' let s = sprintf "%c%c" a b printfn "%O" s // Prints "ab" 会直接在输出上执行更高级的格式化逻辑,而不是直接处理数组或String数据。 (我目前无法自己进行基准测试,但是将其插入下面的@TomasPetricek的benckmarking代码中,如果您获得10倍或更多的性能命中率,我也不会感到惊讶。)

这可能并不重要,因为对于一次转换而言,它仍然比任何最终用户可能注意到的速度都要快得多,但是请注意,如果将其用于任何对性能要求严格的代码中,则要小心。

答案 1 :(得分:4)

@ Abion47的答案已经列出了我能想到的所有可能的明智方法。如果您对性能感兴趣,则可以使用F#Interactive #time功能进行快速实验:

#time 
open System
open System.Text

let a = 'a'
let b = 'b'

将这三种方法进行比较,事实证明,使用String [| a; b |]的方法的速度大约是使用ToString的方法的两倍。在实践中,这可能不是一个大问题,除非你正在做几百万这样的操作(如我的实验一样),但它是一个有趣的事实就知道:

// 432ms, 468ms, 472ms
for i in 0 .. 10000000 do
  let s = a.ToString() + b.ToString()
  ignore s

// 396ms 440ms, 458ms
for i in 0 .. 10000000 do
  let s = Char.ToString(a) + Char.ToString(b)
  ignore s

// 201ms, 171ms, 170ms
for i in 0 .. 10000000 do
  let s = String [| a;b |]
  ignore s