Задача FizzBuzz в Java 8 написана коротким, понятным и интересным способом

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

Это в отличие от какого-то драгоценного камня, который я нашел в сети: 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, однако мое намерение состоит в не hardcode if (value % 15 == 0) аналогично if (value % 3 == 0 && value % 5 == 0), потому что он создает своего рода нелогичную операцию, поскольку вам абсолютно необходимо написать if (value % 15 == 0) case up front, за которым следуют 3-й случай и 5-й случай (или наоборот).

37 голосов | спросил skiwi 12 J000000Saturday14 2014, 15:28:58

8 ответов


16

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

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

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

private static String fizzBuzzify(final int value) {
   String result = "";

   if (value % 3 == 0) {
       result += "Fizz";
   }
   if (value % 5 == 0) {
       result += "Buzz";
   }
   return result.length() > 0 ? result : Integer.toString(value);
}
ответил Jeroen Vannevel 12 J000000Saturday14 2014, 16:15:32
14

Я думаю, что если вы собираетесь это сделать, лучше выделить условия. Я не знаю Java 8 достаточно хорошо, чтобы использовать его, но в более старой Java я бы подумал о чем-то подобном (обратите внимание, что это не предназначено для компиляции Java, а просто псевдокода, подобного Java):

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));
    }
}

Это отделяет основную концепцию «если выполняется какое-то условие, подставляем строку для числа» из отдельных условий и результатов. Однако, как и у вас, он все еще не полностью избегает в зависимости от порядка в некоторой степени (и я не думаю, что такой зависимости можно избежать). Если вы не поддерживаете порядок замещений, вы можете закончить замену кратным 15 «buzzfizz» вместо требуемого «fizzbuzz».

Чтобы этого избежать, у вас есть несколько вариантов. Один из них состоит в том, чтобы поддерживать упорядоченный список объектов сопоставления и запускать все из них для каждого входа. Если вы используете два или более огня, вы объединяете результаты вместе, и клиентский код должен быть соответствующим образом упорядочен.

В качестве альтернативы вы можете остановиться на первом, который «срабатывает» (т. е. первый, для которого «условие» возвращает 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 достаточно хорошо, чтобы на самом деле написать код, чтобы сделать это, я уверен, что это позволит замещениям быть написаны как лямбда-выражения, что должно существенно уменьшить многословие без потери общности (и учитывая мое невежество Java 8, я был бы почти удивлен, если бы он не позволял и других улучшений).

ответил Jerry Coffin 12 J000000Saturday14 2014, 16:27:06
7

Для тривиально простого варианта использования StringBuilder излишне. Вместо этого используйте простую конкатенацию:

String result = "";
if (value % 3 == 0) {
    result = "Fizz";
}
if (value % 5 == 0) {
    result += "Buzz";
}

Скобки вокруг toDefault здесь не нужны:

return (toDefault) ? String.valueOf(value) : stringBuilder.toString();

Как насчет генератора удобства с аргументом max, используя min=1 по умолчанию:

static Stream<String> fizzBuzz(final int max) {
    return fizzBuzz(1, max);
}

Как добавить некоторые модульные тесты:

public class FizzBuzzTest {
    private String resultToString(int from, int to) {
        return FizzBuzz.fizzBuzz(from, to).collect(Collectors.joining(" "));
    }

    @Test
    public void testShortRanges() {
        assertEquals("1 2 Fizz 4 Buzz Fizz", resultToString(1, 6));
        assertEquals("7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz", resultToString(7, 15));
    }

    @Test
    public void testConsistentConcat() {
        String a = resultToString(1, 20);
        String b = resultToString(21, 100);
        assertEquals(a + " " + b, resultToString(1, 100));
    }
}
ответил janos 12 J000000Saturday14 2014, 16:42:15
6

Я приведу этот ответ , поскольку вы сделали ту же ошибку при интерпретации требований. Хотя, я не уверен, сколько интервьюеров поймают разницу. Вы должны быть готовы объяснить why , что лучше использовать value % 3 == 0 && value % 5 ==0 над решением, которое вы внедрили, или с жестким кодированием значения 15.

  
    

Напишите программу, которая печатает целые числа от 1 до 100. Но для кратных трех напечатать «Fizz» вместо числа и для кратных пяти печатать «Buzz». Для чисел, кратных как трех, так и пяти печатным «FizzBuzz»

  
     

Однако вы внедрили это:

     
    

Напишите программу, которая печатает целые числа от 1 до 100. Но для кратных трех напечатать «Fizz» вместо числа и для кратных пяти печатать «Buzz». Для чисел, кратных как трех, так и пяти печатным , объединяются оба.

  
     

Почему это имеет значение? Если вы рассматриваете проблему как бизнес-логику, предоставленную клиентом, то прибл. Через 5 секунд после развертывания вашего решения клиент вернется и скажет: «Ах, да, я забыл, если он делится на 3 и 5, вам нужно распечатать FixBugz, потому что некоторые из наших устаревших приложений, которые мы не можем изменить, имеют опечатку в их синтаксический код. " Теперь вместо простого изменения массива (3 => 'Fizz', 5 => 'Buzz', 15 => 'FizzBuzz') в массив (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.

ответил RubberDuck 12 J000000Saturday14 2014, 16:22:08
5

За исключением StringBuilder, я согласен с вашим решением.

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

/**
 * @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. Но код тогда менее многоразовый, если вообще. Кроме того, составление трех разных условий перекрытия не будет таким простым.

ответил toto2 12 J000000Saturday14 2014, 19:13:36
3

Я лично считаю, что лучше проверить делимость на второй член в первом 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);
}

Таким образом, в методе нет дополнительных временных рядов.

ответил Elliott Frisch 13 J000000Sunday14 2014, 11:39:42
2

Я бы пошел с 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 в правильный String в Scala, мы бы просто использовали flatMap как Option также является сборником. В Java нет такой удачи. Я придумал это, это немного уродливо, но я думаю, что это понятно:

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();
}

По существу, мы берем каждый transformer, а apply, который дает нам Stream<Optional<String>>

Альтернативой, не уверен, что это лучше:

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));
}

Здесь мы делаем map, чтобы получить Stream<Optional<String>>, затем filter out absent и map в Stream<String>. Затем мы используем (часто пропускаемый) collectingAndThen Collector для первого join полученный Stream<String>, а затем return i, если объединенный String пуст.

Основная работа теперь становится такой же простой, как:

public static void main(String[] args) throws Exception {
    IntStream.rangeClosed(1, 100).mapToObj(App::transform).forEach(System.out::println);
}
ответил Boris the Spider 13 J000000Sunday14 2014, 13:59:00
0

Он говорит «print», поэтому я буду использовать sysout. Оба решения IMHO также относительно короткие и удобочитаемы.

Я предполагаю, что «FizzBuzz» подразумевается как конкатенация «Fizz» и «Buzz». Это может быть неверно, но, по-моему, здесь все лучше.

Первый использует использует потребительский интерфейс, который был добавлен в Java в Версии 8.

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();
    }
}

FizzBuzzConsumer принимает значение int и решает, что печатать. Не имеет значения, является ли int-значение кратным как 3 , так и 5, поскольку это условие может выполняться каждым отдельно.

public class FizzBuzz {

    public static void main(String[] args) {
        FizzBuzzConsumer fizzBuzzConsumer = new FizzBuzzConsumer();

        IntStream.rangeClosed(1, 100)
            .forEach(fizzBuzzConsumer);
    }
}

Это создало поток целых чисел, каждый из которых загружается в FizzBuzzConsumer.

Другое решение, которое просто смотрит на каждый int в потоке (peek), чтобы решить, следует ли печатать Fizz и /или Buzz или само значение:

public class FizzBuzz2 {

    public static void main(String[] args) {
        IntStream.rangeClosed(1, 100)
            .peek(possibleMultipleOfThree -> {
                if (possibleMultipleOfThree % 3 == 0) {
                    System.out.print("Fizz");
                }
            })
            .peek(possibleMultipleOfFive -> {
                if (possibleMultipleOfFive % 5 == 0) {
                    System.out.print("Buzz");
                }
            })
            .peek(possibleMultipleOfNeither -> {
                if (possibleMultipleOfNeither % 3 != 0 && possibleMultipleOfNeither % 5 != 0) {
                    System.out.print(value3);
                }
            })
            .forEach(intValue -> System.out.println());
    }
}

Снова это создает поток целых чисел. Он смотрит («заглядывает») в каждый из них, чтобы решить, что печатать, за которым следует линия. Это более или менее то же, что и выше, но гораздо более подробный.

Мне даже кажется особенно интересным. В обоих случаях я чувствовал, что повторение сравнения текущей int-стоимости для противоположного условия (if (value % 3 != 0 && value % 5 != 0)) будет делать для наивысшая читаемость.
ответил HS_Tri 14 J000000Monday14 2014, 13:29:55

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132