我在网上发现的一些宝石:FizzBuzzEnterpriseEdition
问题描述:
编写一个程序,打印从1到100的数字。但是要是三个的倍数打印“ Fizz”而不是数字,并打印五个“ Buzz”的倍数。对于三个和五个的倍数的数字,打印“ FizzBuzz”
这是我的代码:
public class FizzBuzz {
private static Stream<String> fizzBuzz(final int min, final int max) {
if (min < 0) {
throw new IllegalArgumentException("min is negative: min = " + min);
}
if (min > max) {
throw new IllegalArgumentException("min > max: min = " + min + " / max = " + max);
}
return IntStream.rangeClosed(min, max)
.mapToObj(FizzBuzz::fizzBuzzify);
}
private static String fizzBuzzify(final int value) {
StringBuilder stringBuilder = new StringBuilder();
boolean toDefault = true;
if (value % 3 == 0) {
stringBuilder.append("Fizz");
toDefault = false;
}
if (value % 5 == 0) {
stringBuilder.append("Buzz");
toDefault = false;
}
return (toDefault) ? String.valueOf(value) : stringBuilder.toString();
}
public static void main(String[] args) {
fizzBuzz(1, 100).forEach(System.out::println);
}
}
我是仍然在寻找一种更好的方式编写
fizzBuzzify
,但是我的意图是不像if (value % 15 == 0)
那样对if (value % 3 == 0 && value % 5 == 0)
进行硬编码,因为它会产生一种不合逻辑的操作优先级,因为您绝对需要先编写if (value % 15 == 0)
的情况,然后再写3-情况和5情况(反之亦然)。#1 楼
我用来决定是否使用的准则是,它取决于我是否事先知道将使用多少次。我相信我曾经在某些MSDN页面上遇到过此建议,但我不确定。对于您来说,您知道
StringBuilder
仅有两种可能的用法,并且在循环内未添加任何值,因此我将使用普通的字符串连接:现在您还可以将代码更改为我认为更易于解释的内容:
评论
\ $ \ begingroup \ $
result.length> 0很好,我喜欢!不再需要(愚蠢的)变量。
\ $ \ endgroup \ $
–skiwi
2014年7月12日12:20
\ $ \ begingroup \ $
比result.length> 0稍好于!result.isEmpty()
\ $ \ endgroup \ $
– janos
2014年7月12日在12:22
\ $ \ begingroup \ $
@janos:我不能说我特别喜欢它。从技术上讲,字符串是char的容器,但仍然感觉好像它主要是基本对象而不是数据结构。 .isEmpty()是我与集合关联的东西。
\ $ \ endgroup \ $
– Jeroen Vannevel
2014年7月12日在12:24
\ $ \ begingroup \ $
@JeroenVannevel在我脑海中的逻辑是“如果字符串为空,则执行此操作,否则执行该操作”,而不是“如果字符串的长度大于0”,因此以这种方式编写感觉很自然。顺便说一句,集合也有一个isEmpty方法,但这根本不会打扰我
\ $ \ endgroup \ $
– janos
2014年7月12日在12:31
\ $ \ begingroup \ $
@JeroenVannevel至少用英语来说,零长度的字符串称为空字符串,而isEmpty则很有意义。
\ $ \ endgroup \ $
– David Harkness
2014年7月12日19:18
#2 楼
我认为,如果您要这样做,最好将条件分开。我不太了解Java 8来使用它,但是在较旧的Java中,我会考虑使用类似的东西(请注意,这并不是旨在作为可编译Java,而只是类似Java的伪代码): br />interface Substitute {
Boolean condition(int);
String transform(int);
}
class Fizzer : implements Substitute {
Boolean condition(int x) { return x % 3 == 0; }
String transform(int) { return "fizz"; }
};
class Buzzer : implements Substitute {
Boolean condition(int x) { return x % 5 == 0; }
String transform(int) { return "buzz"; }
};
class Mapper {
List<Substitute> subs;
void add_sub(Substitute sub) {
subs.Add(sub);
}
String execute(int input) {
String result;
Boolean use_default = true;
foreach (sub : subs) {
if (sub.condition(input)) {
result += sub.transform(input);
use_default = false;
}
}
if (use_default) return String.valueOf(input);
return result;
}
}
class FizzBuzz {
static void main() {
Mapper map;
map.add_sub(new Fizzer);
map.add_sub(new Buzzer);
for (int i=0; i<100; i++)
System.out.println(map.execute(i));
}
}
这从各个条件和产生的结果中分离出“如果满足某些条件,则用字符串代替数字”的基本概念。但是,像您一样,在某种程度上仍不能完全避免依赖订购(而且我认为无法避免这种依赖性)。如果不保持替换顺序,最终可能会用“ buzzfizz”而不是必需的“ fizzbuzz”替换15的倍数。 。一种是维护映射对象的有序列表,并针对每个输入依次运行所有对象。如果有两个或多个触发,则将结果连接在一起,并由客户端代码适当地排序。
或者,您可以在第一个“触发”的位置停止(即第一个为此,“条件”返回true)。这就要求客户端代码包含一个“ FizzBuzzer”,它将“激发”为15的倍数。我不想在循环中使用丑陋的条件代码来决定是否已经发生转换,或者使用默认转换(即仅打印数字),而是希望将默认转换添加到列表的末尾的变换:
class DefaultMap : implements Substitute {
Boolean condition(int) { return true; }
String transform(int val) { return String.valueOf(val); }
};
// ... in main:
Mapper map;
map.add_sub(new FizzBuzzer);
map.add_sub(new Fizzer);
map.add_sub(new Buzzer);
map.add_sub(new DefaultMap);
for (int i=0; i<100; i++)
System.out.println(map.execute(i));
就代码本身而言,我编写它的方式相当冗长。尽管我不太了解Java 8来实际编写代码来做到这一点,但我相当有信心将它允许将替代写成lambda表达式,这样可以在不损失一般性的情况下大大减少冗长程度(并且鉴于我的无知Java 8,如果它也不允许其他改进的话,我会感到惊讶。
评论
\ $ \ begingroup \ $
我喜欢这个,实际上我正在尝试创建整个解决方案。我一定会制作一个小的库类来容纳不同的选项,然后我将使用它来创建后续步骤!
\ $ \ endgroup \ $
–skiwi
2014年7月12日在12:36
\ $ \ begingroup \ $
如果将类转换为谦虚方法会更好... java 8,您知道。少企业y。
\ $ \ endgroup \ $
–迈克尔·戴尔德(Michael Deardeuff)
14年7月13日在5:03
\ $ \ begingroup \ $
我认为Java没有foreach。
\ $ \ endgroup \ $
–西蒙·匡(Simon Kuang)
14年7月23日19:00
\ $ \ begingroup \ $
@SimonKuang:“请注意,这并非旨在用作可编译Java,而只是类似于Java的伪代码”。为了避免引入类似于C ++的新关键字,Java只是扩展了for语法以提供类似foreach的语义。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年7月24日14:37
\ $ \ begingroup \ $
OMG ..为什么将简单的事情变得如此复杂?
\ $ \ endgroup \ $
– Bandu
16年4月13日在14:28
#3 楼
对于像这样的简单用例,StringBuilder太过分了。请改用简单的串联:String result = "";
if (value % 3 == 0) {
result = "Fizz";
}
if (value % 5 == 0) {
result += "Buzz";
}
这里的
toDefault
周围的括号是不必要的:仅带有max
参数的便捷生成器,默认情况下使用min=1
:return (toDefault) ? String.valueOf(value) : stringBuilder.toString();
如何添加一些单元测试:
static Stream<String> fizzBuzz(final int max) {
return fizzBuzz(1, max);
}
#4 楼
我将引用此答案,因为您在解释需求时犯了同样的错误。虽然,我不确定有多少名面试官会有所不同。您应该准备好解释为什么在已实现的解决方案上使用value % 3 == 0 && value % 5 ==0
还是对15的值进行硬编码更好。 100.但是对于三倍的倍数,请打印“ Fizz”(而不是数字),对于五倍的倍数,请打印“ Buzz”。对于三到五的倍数的数字,打印“ FizzBuzz” 但是您已经实现了这一点:
编写一个程序,打印从1到100的整数。三个打印数字“ Fizz”(而不是数字)的倍数,以及五个打印文本“ Buzz”的倍数。对于三个和五个的倍数的数字,请同时打印两个的串联。
为什么重要?如果您将问题视为客户提供的业务逻辑,则大约为部署解决方案5秒后,客户会回来:“啊,是的,我忘了,如果它可以被3和5整除,则必须打印FixBugz,因为我们无法更改的一些旧版应用程序中有错字他们的解析代码。”现在,不只是将array(3 =>'Fizz',5 =>'Buzz',15 =>'FizzBuzz')更改为array(3 =>'Fizz',5 =>'Buzz',15 =>'FixBugz ')您必须更改一大堆实现代码和单元测试。
我对Java不太满意,但是我喜欢此答案的实现,因此我将做一点改进对它。 FizzBuzzify应该采用一些参数,而不是对所有这些幻数进行硬编码。我将在另一个FizzBuzz问题的答案中对此进行详细说明。
private static String fizzBuzzify(final int value, final int fizzDivisor, final int buzzDivisor) {
if (value % fizzDivisor == 0) {
return (value % buzzDivisor == 0) ? "FizzBuzz" : "Fizz";
}
return (value % buzzDivisor == 0) ? "Buzz" : Integer.toString(value);
}
我建议将
fizzDivisor
和buzzDivisor
设置为具有默认值的可选参数。我只是不知道在Java中是如何做到的。评论
\ $ \ begingroup \ $
客户同样有可能告诉您Buzz应该更改为Bugz,并期望FizzBuzz的打印内容会相应更改。您想得太多了。
\ $ \ endgroup \ $
– RemcoGerlich
2014年7月12日12:50
\ $ \ begingroup \ $
我想每个人都认为FizzBuzz @RemcoGerlich。那就是为什么我说能够解释您的思考过程很重要;不管是什么
\ $ \ endgroup \ $
–RubberDuck
2014年7月12日13:11
#5 楼
除了StringBuilder,我同意您的解决方案。我写了一个完全不同的解决方案,我认为它更“实用”。它可能不是最有效的解决方案,但是此代码可用于不同的问题。
/**
* @return a function that simply returns its input value, except that it
* returns {@code overWriteValue} each time the function as been
* called a multiple of {@code periodicity} times.
*/
public static <T> Function<T, T> overWriter(int periodicity, T overWriteValue) {
AtomicInteger counter = new AtomicInteger(0);
return value -> (counter.getAndIncrement() % periodicity) == 0
? overWriteValue
: value;
}
public static void main(String[] args) {
Stream<String> ints = IntStream.range(0, 100).mapToObj(Integer::toString);
Function<String, String> fizzBuzzOverWriter =
overWriter(3, "Fizz")
.andThen(overWriter(5, "Buzz")
.andThen(overWriter(15, "FizzBuzz")));
Stream<String> fizzBuzz = ints.map(fizzBuzzOverwriter);
fizzBuzz.forEach(System.out::println);
}
请注意,选择覆盖值时我根本不使用整数值。您必须确保Stream的起始值和初始计数器值一致。 (我也可以将初始计数器值作为参数添加到
overWrite()
。)我也可以将
overWrite
函数设置为:Integer
-> String
。但是,如果有的话,那么代码将很难重用。同样,组成三个不同的重叠条件也不会那么简单。评论
\ $ \ begingroup \ $
至少对我来说,这看起来有点聪明,而且太容易使用,看起来像它们应该工作,但实际上会完全失败。尽管如此,这还是一个有趣的解决方案。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年7月12日在16:38
\ $ \ begingroup \ $
@Jerry Coffin您能解释一下吗?我已经习惯了函数式编程(Scala),所以这对我来说根本不是“聪明”的。那“看起来他们应该工作”呢?这段代码立刻给了我正确的答案。我不必进行任何调试。
\ $ \ endgroup \ $
–toto2
2014年7月12日在16:44
\ $ \ begingroup \ $
@Jerry Coffin顺便说一句,我的代码与您的代码几乎相同,但是由于我使用的是Java 8,所以更简短。函数是您的替代品,函数组成(andThen)几乎等同于您的Mapper。
\ $ \ endgroup \ $
–toto2
2014年7月12日在16:47
\ $ \ begingroup \ $
我说的是OverWriter使用它自己的内部计数器这一事实,因此,如果(例如)您尝试从另一个数字(例如25)而不是1开始获得一个序列,则输出将完全错误。
\ $ \ endgroup \ $
–杰里·科芬(Jerry Coffin)
2014年7月12日17:08
\ $ \ begingroup \ $
是的,这里有四个独立递增的计数器是一种代码味道。
\ $ \ endgroup \ $
– Ben Voigt
14年7月13日在16:35
#6 楼
我个人认为最好在第一个if
中用第二个术语测试可除性(并且您不禁止三进制),因此,private static String fizzBuzzify(final int value) {
if (value % 3 == 0) {
return (value % 5 == 0) ? "FizzBuzz" : "Fizz";
}
return (value % 5 == 0) ? "Buzz" : Integer.toString(value);
}
这种方式没有该方法中的额外临时工。
#7 楼
我将使用enum
来定义替换,例如:private static enum Transformer implements IntFunction<Optional<String>> {
FIZZ {
@Override
public Optional<String> apply(int value) {
return value % 3 == 0 ? Optional.of("Fizz") : Optional.empty();
}
},
BUZZ {
@Override
public Optional<String> apply(int value) {
return value % 5 == 0 ? Optional.of("Buzz") : Optional.empty();
}
};
}
所以现在您可以轻松地向列表中添加新的“转换”。
为了将
int
转换为Scala中正确的String
,我们将简单地使用flatMap
,因为Option
也是一个集合。 Java没有这种运气。我想出了这个东西,这有点丑陋,但我认为它很清楚:然后,我使用transformer
方法将所有当前实例附加到apply
。如果`StringBuilder为空,也只需附加当前整数。另一种方法(不确定它是否更好)是:
private static String transform(final int i) {
final StringBuilder sb = Stream.of(Transformer.values()).
map(t -> t.apply(i)).
collect(StringBuilder::new, (builder, v) -> v.ifPresent(builder::append), StringBuilder::append);
if (sb.length() == 0) {
sb.append(i);
}
return sb.toString();
}
这里我们执行
Stream<Optional<String>>
得到collect
,然后将StringBuilder
和map
输出到Stream<Optional<String>>
。然后,我们使用(通常被忽略)filter
absent
首先生成结果map
,然后如果连接的Stream<String>
为空,则返回collectingAndThen
。现在的主要工作变得很简单:
private static String transform(final int i) {
return Stream.of(Transformer.values()).
map(t -> t.apply(i)).
filter(Optional::isPresent).
map(Optional::get).
collect(collectingAndThen(joining(), s -> s.isEmpty() ? Integer.toString(i) : s));
}
#8 楼
它说“打印”,所以我将使用sysout。两种解决方案都是恕我直言,而且也相对简短且易于阅读。可能不正确,但我认为这会更好。 br />FizzBuzzConsumer
采用int值并确定要打印的内容。不管int-Value是否为3和5的倍数,因为该条件可以分别由每个条件满足。整数,每个整数都被输入到
FizzBuzzConsumer
中。 /> class FizzBuzzConsumer implements IntConsumer {
@Override
public void accept(int value) {
if (value % 3 == 0) {
System.out.print("Fuzz");
}
if (value % 5 == 0) {
System.out.print("Buzz");
}
if (value % 3 != 0 && value % 5 != 0) {
System.out.print(value);
}
System.out.println();
}
}
再次创建整数流。它看起来(“窥视”)每个对象,以决定是否打印什么,然后换行。它和上面差不多,但是比较冗长。
对我来说似乎都不是很有趣。在这两种情况下,我都认为针对相反条件(
peek
)重复当前int值的比较将获得最高的可读性。评论
\ $ \ begingroup \ $
谢谢,我添加了一些有关编写这些确切代码的动机。我想知道对他们来说,什么不是特别好,如果不是很麻烦的话。
\ $ \ endgroup \ $
– HS_Tri
14年7月14日在10:29
\ $ \ begingroup \ $
如果您想对代码进行审查,则应该发表自己的问题。
\ $ \ endgroup \ $
–RubberDuck
14年7月14日在10:35
\ $ \ begingroup \ $
再次感谢,希望我能发表所有意见。我仍然要假设“ FizzBuzz”是“ Fizz”和“ Buzz”的串联,但我可能错了!
\ $ \ endgroup \ $
– HS_Tri
14年7月14日在11:01
评论
我认为,任何不使用简单的for循环和System.out.println的FizzBuzz解决方案都无法通过测试。@RemcoGerlich FizzBuzz挑战中没有什么要求解决方案是我所必须的。您为什么要提出这一主张?
@Rune FS:这不是挑战,但是使程序复杂到需要的程度并不是很好的编程。
使用for循环的@REmcoGerlich比编写此纯函数要复杂得多。
@RuneFS因为您这么说?这就是Java,它中的任何功能都无法纯粹发挥作用,那么您的评论的重点是什么?