Русский
Русский
English
Статистика
Реклама

Изучение программирования

Перевод Reagent Минималистичный React для ClojureScript

29.07.2020 14:20:10 | Автор: admin
Хабр, привет.

Я PM, которые лезет в то, что его может сожрать. Так что, я перевела часть документации по Reagent, если будет полезно, выложу продолжение.


In the no-holds-barred world of coding, the end justifies the extreme.

Введение в Reagent


Reagent обеспечивает минималистичное взаимодействие между ClojureScript и React. Он позволяет вам создавать эффективные компоненты React, используя только простые функции ClojureScript и данные, которые описывают пользовательский интерфейс, используя Hiccup-подобный синтаксис.

Цель Reagent сделать возможным создание произвольно сложных пользовательских интерфейсов, используя пару основных понятий, и быть по-умолчанию достаточно быстрым, чтобы вам редко приходилось думать о производительности.

Простейший компонент Reagent

может выглядеть примерно так:
(defn simple-component []  [:div   [:p "I am a component!"]   [:p.someclass    "I have " [:strong "bold"]    [:span {:style {:color "red"}} " and red "] "text."]])





Вы можете создавать новые компоненты, вкладывая внутрь другие компоненты.

Как в этом примере:
(defn simple-parent []  [:div   [:p "I include simple-component."]   [simple-component]])




Данные передаются дочерним компонентам с помощью простых типов данных Clojure.

Как в этом примере:
(defn hello-component [name]  [:p "Hello, " name "!"])(defn say-hello []  [hello-component "world"])




Примечание: В приведенном выше примере hello-component можно было бы точно так же вызвать как нормальную функцию Clojure, а не как компонент Reagent, т.е. с круглыми скобками вместо квадратных скобок. Единственным отличием была бы производительность, так как реальные компоненты Reagent перерисовываются только тогда, когда их данные изменились. Однако более продвинутые компоненты (см. ниже) должны вызываться в квадратных скобках.

Вот еще один пример, который рендерит список элементов:
(defn lister [items]  [:ul   (for [item items]     ^{:key item} [:li "Item " item])])(defn lister-user []  [:div   "Here is a list:"   [lister (range 3)]])




Примечание: приведенная выше часть ^{: key item} на самом деле не является необходимой в этом простом примере, но добавление уникального ключа к каждому элементу в динамически генерируемом списке компонентов является хорошей практикой и и увеличивает производительность рендеринга больших списков. Ключ может быть задан либо (как в этом примере) как метаданные, либо как элемент :key в первом аргументе компонента (если это map). Дополнительную информацию смотрите в документации React.

Управление состоянием в React


Самый простой способ управлять состоянием использовать собственную версию atom из Reagent. Он работает точно так же, как и в clojure.core, за исключением того, что при каждом использовании дерефается/разыменовывается (deref). Любой компонент, использующий atom, автоматически ререндерится при изменении его значения.

Продемонстрируем это на простом примере:
(ns example  (:require [reagent.core :as r]))(def click-count (r/atom 0))(defn counting-component []  [:div   "The atom " [:code "click-count"] " has value: "   @click-count ". "   [:input {:type "button" :value "Click me!"            :on-click #(swap! click-count inc)}]])




Иногда вы можете захотеть использовать локальный стейт в компоненте. Это легко сделать и с атомом.

Мы вызываем setTimeout, и каждый раз компонент перерендеривается с обновленным счетчиком:
Вот пример
(ns example  (:require [reagent.core :as r]))(def click-count (r/atom 0))(defn counting-component []  [:div   "The atom " [:code "click-count"] " has value: "   @click-count ". "   [:input {:type "button" :value "Click me!"            :on-click #(swap! click-count inc)}]])




В предыдущем примере также используется другая особенность Reagent: компонентная функция может возвращать другую функцию, которая используется для фактического рендеринга. Эта функция вызывается с теми же аргументами, что и первая.

Это позволяет выполнить некоторую настройку вновь создаваемых компонентов, не прибегая к событиям жизненного цикла Reagent.

Просто передавая атом, вы можете разделить управление состоянием между компонентами.

Как это делается:
(ns example  (:require [reagent.core :as r]))(defn atom-input [value]  [:input {:type "text"           :value @value           :on-change #(reset! value (-> % .-target .-value))}])(defn shared-state []  (let [val (r/atom "foo")]    (fn []      [:div       [:p "The value is now: " @val]       [:p "Change it here: " [atom-input val]]])))




Замечание: Функции компонентов могут вызываться с любыми аргументами до тех пор, пока они неизменны. Вы могли бы использовать и мутабельные структуры, но тогда было бы необходимо убедиться, что компонент ререндерится при изменении данных. Reagent предполагает по-умолчанию, что два объекта равны, если они являются одним и тем же объектом.

Основное API



Reagent поддерживает большинство API React, но на самом деле существует только одна точка входа, необходимая для большинства приложений: reagent.dom/render.

Требуется два аргумента: компонент и DOM-узел.

Например, код самого первого примера по странице будет выглядеть так:
(ns example  (:require [reagent.core :as r]))(defn simple-component []  [:div   [:p "I am a component!"]   [:p.someclass    "I have " [:strong "bold"]    [:span {:style {:color "red"}} " and red "] "text."]])(defn render-simple []  (rdom/render    [simple-component]    (.-body js/document)))


Складывая все вместе


Вот более жизненный пример: простой калькулятор BMI.

Данные хранятся в одном reagent.core/atom: map со следующими ключами: высота, вес и BMI.
(ns example  (:require [reagent.core :as r]))(defn calc-bmi [{:keys [height weight bmi] :as data}]  (let [h (/ height 100)]    (if (nil? bmi)      (assoc data :bmi (/ weight (* h h)))      (assoc data :weight (* bmi h h)))))(def bmi-data (r/atom (calc-bmi {:height 180 :weight 80})))(defn slider [param value min max invalidates]  [:input {:type "range" :value value :min min :max max           :style {:width "100%"}           :on-change (fn [e]                        (let [new-value (js/parseInt (.. e -target -value))]                          (swap! bmi-data                                 (fn [data]                                   (-> data                                     (assoc param new-value)                                     (dissoc invalidates)                                     calc-bmi)))))}])(defn bmi-component []  (let [{:keys [weight height bmi]} @bmi-data        [color diagnose] (cond                          (< bmi 18.5) ["orange" "underweight"]                          (< bmi 25) ["inherit" "normal"]                          (< bmi 30) ["orange" "overweight"]                          :else ["red" "obese"])]    [:div     [:h3 "BMI calculator"]     [:div      "Height: " (int height) "cm"      [slider :height height 100 220 :bmi]]     [:div      "Weight: " (int weight) "kg"      [slider :weight weight 30 150 :bmi]]     [:div      "BMI: " (int bmi) " "      [:span {:style {:color color}} diagnose]      [slider :bmi bmi 10 50 :weight]]]))



Производительность


React очень быстрый, как и Reagent. На самом деле, Reagent будет даже быстрее, чем обычный React практически всегда, благодаря оптимизациям, сделанным ClojureScript.

Смонтированные компоненты ререндерятся только тогда, когда их параметры изменились. Ререндеринг может вызываться сменой значения атома, аргументы передаются компоненту или состоянию компонента.

Все параметры сравниваются через функцию identical?, которая сравнивает указатели, поэтому не влияет на производительность. Mapы, передаваемые в качестве аргументов компонентам, сравниваются так же: они считаются идентичными, если все их элементы идентичны. Это относится и к встроенным компонентам React, таким как :div, :p и т. д.

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

Есть пара ситуаций, о которых вам, возможно, придется позаботиться. Если вы дадите Reagent большой список компонентов для рендеринга, вам, возможно, придется снабдить их все уникальным атрибутом :key для ускорения рендеринга (см. выше). Также обратите внимание, что анонимные функции в целом не равны друг другу, даже если они представляют один и тот же код и замыкание.

Но опять же, в целом, стоит просто поверить, что React и Reagent будут достаточно быстрыми. Именно эта страница состоит из одного компонента Reagent с тысячами дочерних компонентов (каждая скобка и т.д. в примерах кода это отдельный компонент), и все же страница может обновляться многократно каждую секунду, не нагружая браузер.

Кстати, та страница также использует другой трюк React: вся страница пререндериватся с помощью Nodejs и reagent.dom.server/render-to-string. Когда она загружается в браузер, React автоматически добавляет обработчики событий к уже существующему DOM-дереву.

Полная демонстрация



Reagent поставляется с парой полных примеров, с проектными файлами Leiningen и всем остальным.

Вот один из них в действии:
(ns simpleexample.core  (:require [reagent.core :as r]            [reagent.dom :as rdom]            [clojure.string :as str]))(defonce timer (r/atom (js/Date.)))(defonce time-color (r/atom "#f34"))(defonce time-updater (js/setInterval                       #(reset! timer (js/Date.)) 1000))(defn greeting [message]  [:h1 message])(defn clock []  (let [time-str (-> @timer .toTimeString (str/split " ") first)]    [:div.example-clock     {:style {:color @time-color}}     time-str]))(defn color-input []  [:div.color-input   "Time color: "   [:input {:type "text"            :value @time-color            :on-change #(reset! time-color (-> % .-target .-value))}]])(defn simple-example []  [:div   [greeting "Hello world, it is now"]   [clock]   [color-input]])(defn ^:export run []  (rdom/render [simple-example] (js/document.getElementById "app")))



Todomvc


Список дел (ToDo List) в Reagent (за исключением роутинга и сохранения данных)

выглядит примерно так:
(ns todomvc.core  (:require [reagent.core :as r]            [reagent.dom :as rdom]            [clojure.string :as str]))(defonce todos (r/atom (sorted-map)))(defonce counter (r/atom 0))(defn add-todo [text]  (let [id (swap! counter inc)]    (swap! todos assoc id {:id id :title text :done false})))(defn toggle [id] (swap! todos update-in [id :done] not))(defn save [id title] (swap! todos assoc-in [id :title] title))(defn delete [id] (swap! todos dissoc id))(defn mmap [m f a] (->> m (f a) (into (empty m))))(defn complete-all [v] (swap! todos mmap map #(assoc-in % [1 :done] v)))(defn clear-done [] (swap! todos mmap remove #(get-in % [1 :done])))(defonce init (do                (add-todo "Rename Cloact to Reagent")                (add-todo "Add undo demo")                (add-todo "Make all rendering async")                (add-todo "Allow any arguments to component functions")                (complete-all true)))(defn todo-input [{:keys [title on-save on-stop]}]  (let [val (r/atom title)        stop #(do (reset! val "")                  (if on-stop (on-stop)))        save #(let [v (-> @val str str/trim)]                (if-not (empty? v) (on-save v))                (stop))]    (fn [{:keys [id class placeholder]}]      [:input {:type "text" :value @val               :id id :class class :placeholder placeholder               :on-blur save               :on-change #(reset! val (-> % .-target .-value))               :on-key-down #(case (.-which %)                               13 (save)                               27 (stop)                               nil)}])))(def todo-edit (with-meta todo-input                 {:component-did-mount #(.focus (rdom/dom-node %))}))(defn todo-stats [{:keys [filt active done]}]  (let [props-for (fn [name]                    {:class (if (= name @filt) "selected")                     :on-click #(reset! filt name)})]    [:div     [:span#todo-count      [:strong active] " " (case active 1 "item" "items") " left"]     [:ul#filters      [:li [:a (props-for :all) "All"]]      [:li [:a (props-for :active) "Active"]]      [:li [:a (props-for :done) "Completed"]]]     (when (pos? done)       [:button#clear-completed {:on-click clear-done}        "Clear completed " done])]))(defn todo-item []  (let [editing (r/atom false)]    (fn [{:keys [id done title]}]      [:li {:class (str (if done "completed ")                        (if @editing "editing"))}       [:div.view        [:input.toggle {:type "checkbox" :checked done                        :on-change #(toggle id)}]        [:label {:on-double-click #(reset! editing true)} title]        [:button.destroy {:on-click #(delete id)}]]       (when @editing         [todo-edit {:class "edit" :title title                     :on-save #(save id %)                     :on-stop #(reset! editing false)}])])))(defn todo-app [props]  (let [filt (r/atom :all)]    (fn []      (let [items (vals @todos)            done (->> items (filter :done) count)            active (- (count items) done)]        [:div         [:section#todoapp          [:header#header           [:h1 "todos"]           [todo-input {:id "new-todo"                        :placeholder "What needs to be done?"                        :on-save add-todo}]]          (when (-> items count pos?)            [:div             [:section#main              [:input#toggle-all {:type "checkbox" :checked (zero? active)                                  :on-change #(complete-all (pos? active))}]              [:label {:for "toggle-all"} "Mark all as complete"]              [:ul#todo-list               (for [todo (filter (case @filt                                    :active (complement :done)                                    :done :done                                    :all identity) items)]                 ^{:key (:id todo)} [todo-item todo])]]             [:footer#footer              [todo-stats {:active active :done done :filt filt}]]])]         [:footer#info          [:p "Double-click to edit a todo"]]]))))(defn ^:export run []  (rdom/render [todo-app] (js/document.getElementById "app")))




Благодарность
Благодарю за помощь в переводе zombiQWERTY.
Подробнее..

Recovery mode Scala как первый язык

21.10.2020 18:15:40 | Автор: admin
Лестница в логотипе отсылает к институту EPFL в Лозанне. Там придумали этот языкЛестница в логотипе отсылает к институту EPFL в Лозанне. Там придумали этот язык

Эта статья, как и мой предыдущий пост, не похожа на то, что обычно публикуют на Хабре. Здесь не объясняется никаких новых или старых концепций, я не рассказываю что такое ООП и функциональное программирование, сложного кода почти нет. Я хочу пригласить вас к дискуссии стоит ли начинать программировать с языка Scala.

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

Это другая лестница by JuJu Lyn on Unsplash blog.softwaremill.com/annoying-things-in-scala-2-thatll-be-mostly-gone-in-scala-3-e1479a6d855cЭто другая лестница by JuJu Lyn on Unsplash blog.softwaremill.com/annoying-things-in-scala-2-thatll-be-mostly-gone-in-scala-3-e1479a6d855c

Умный компилятор

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

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

JVM-экосистема

Как известно, Джеймс Гослинг, создатель языка джава, считает самой лучшей ее частью джава-машину. Действительно, много инженеров потратило десятки человеко-лет на разработку виртуальной машины и достигли скоростей, в некоторых случаях превышающих Си++. Вот исследование производительности JVM в мобильных устройствах, в нем чаще побеждает нативный код на Си, но в одном случае победила JVM: Java vs C app performance. Кстати, Джеймсу Гослингу скала тоже нравится.

На Android 6 AArch64 JVM победила Си в математических вычислениях, хотя и проиграла во всех других видахНа Android 6 AArch64 JVM победила Си в математических вычислениях, хотя и проиграла во всех других видах

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

Кроме того, на джаве написаны миллионы строк кода, которые вы можете использовать из скалы или без проблем в нее транслировать.

Но в этом всем есть и минусы. Как мы знаем, верхнеуровневые абстракции протекают, и для того чтобы программировать на скале, важно знать как устроена виртуальная машина джава, и учитывать ее особенности. Например, такие как Type Erasure.

Обратная совместимость

В экосистеме скалы приняты довольно слабые гарантии обратной совместимости по сравнению с джавой. С одной стороны, это приводит к тому, что разработчикам постоянно приходится поддерживать свой код в форме при переходе на новую версию языка или библиотек. Кроме того, разработчикам библиотек приходится прибегать к кросс-компиляции для того чтобы обеспечить несколько версий скомпилированных библиотек, подходящих для нескольких версий языка. Это, безусловно, минус.

Но с другой стороны такой подход обеспечивает быстрое и динамичное развитие языка, которое не может себе позволить джава. Именно поэтому я думаю что будущее за скалой. К тому же, в Scala 3 появилось типизированное абстрактное синтаксическое дерево компилятора TASTY, которое позволит взаимодействовать классам, собранным разными версиями компилятора.

Scala 3

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

Первоначальным посылом к созданию скалы было сделать язык промышленного уровня, на котором будет приятно программировать. Это действительно был большой шаг вперед и многие программисты отдают предпочтение скале как любимому языку.

Но некоторые решения, принятые более 15 лет назад, были неверными и приводили к неочевидному, либо многословному коду. И сейчас создатели языка делают его действительно лучше. После этих изменений шансы скалы в качестве первого языка программирования заметно вырастают.

Я уже пару лет с нетерпением жду его выхода и мечтаю начать писать на нём. Вот сжатый рассказ Мартина Одерски про новую версию языка: Countdown to Scala 3 by Martin Odersky.

DOT

Dependent Object Types это теоретические основы языка Scala 3, разработанные его основателем Мартином Одерски. Мало какой язык может похвастать математически точным исчислением, лежащем в его основе. Согласитесь, приятно учить язык с таким бэкграундом.

Выразительность и лаконичность

То, что я больше всего люблю в языке это его сжатость и лаконичность. Я ненавижу длиннейшие конструкции джавы, в которых приходится сложно и многословно объяснять компилятору простые вещи. Моя любимая шутка про джаву, что это такой язык, в котором уан-лайнер занимает около 30 строк. Так вон в скале уан-лайнер это уан-лайнер.

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

Понятно, что для написания библиотек важно понимать сложные концепции и знать много умных слов. Но для того, чтобы ими пользоваться и писать простой код, всего этого знать не надо. Минимальный уровень скалы, который подойдет начинающим, очень простой.

Вот мой любимый пример для библиотечного кода на скале. Конечно, он не предназначен для начинающих разработчиков:

class Stack[+A] {  def push[B >: A](elem: B): Stack[B] = new Stack[B] {    override def top: B = elem    override def pop: Stack[B] = Stack.this    override def toString = s"$elem ${Stack.this}"  }  def top: A = sys.error("no element on stack")  def pop: Stack[A] = sys.error("no element on stack")  override def toString = ""}object VariancesTest extends App {  println(Stack().push("hello").push(Object()).push(7))}

Думаю, выразительность это та причина, по которой так популярны динамические и скриптовые языки. Уверен, скала способна с ними в этом поспорить. И даже сама послужить отличным скриптовым языком. В новой версии языка даже есть возможность писать намного меньше фигурных скобок, а пользоваться отступами как в Питоне или в yaml-разметке.

Это будет выглядеть примерно так:

enum IndentWidth:  case Run(ch: Char, n: Int)  case Conc(l: IndentWidth, r: Run)  def <= (that: IndentWidth): Boolean = this match    case Run(ch1, n1) =>      that match        case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0)        case Conc(l, r)   => this <= l    case Conc(l1, r1) =>      that match        case Conc(l2, r2) => l1 == l2 && r1 <= r2        case _            => false  def < (that: IndentWidth): Boolean =    this <= that && !(that <= this)  override def toString: String = this match    case Run(ch, n) =>      val kind = ch match        case ' '  => "space"        case '\t' => "tab"        case _    => s"'$ch'-character"      val suffix = if n == 1 then "" else "s"      s"$n $kind$suffix"    case Conc(l, r) =>      s"$l, $r"object IndentWidth:  private inline val MaxCached = 40  private val spaces = IArray.tabulate(MaxCached + 1)(new Run(' ', _))  private val tabs = IArray.tabulate(MaxCached + 1)(new Run('\t', _))  def Run(ch: Char, n: Int): Run =    if n <= MaxCached && ch == ' ' then      spaces(n)    else if n <= MaxCached && ch == '\t' then      tabs(n)    else      new Run(ch, n)  end Run  val Zero = Run(' ', 0)end IndentWidth

Статическая типизация

По-моему это самое важное что должно быть в первом языке программирования. Программисту важно понимать, что не стоит складывать яблоки с апельсинами. Все в порядке с Питоном, Руби, Джаваскрипт, Си и Си++, но на мой взгляд не стоит начинать обучение с них. Динамическая типизация это очевидный способ выстрелить себе в ногу. Другое дело, что статическая типизация неудобна, неочевидна и требует дополнительного бойлер-плейта. Скала вполне успешно позволяет избавиться от него в простых программах.

ООП

Объектно-ориентированное программирование это самая популярная парадигма, без которой не обходится не один современный язык программирования.

В скале эта парадигма изначально присуща языку, в отличии от Питона, Си/Си++ и в каком-то смысле джаваскрипта. Разумеется, сейчас у этих языков с ООП полный порядок.

Функциональное программирование

Функциональное программирование это многообещающая тенденция. Скале она присуща в не меньшей степени чем ООП, и они взаимно обогащают друг друга благодаря этому языку. Рискну предположить, что скала первый язык промышленного уровня с такими свойствами. Сейчас за ним подтягивается Котлин и другие более новые языки.

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

Иммутабельность

Вообще-то это свойство функционального программирования. Мне кажется очень важным прививать умение пользоваться иммутабельными структурами данных с самого начала. Это позволит в будущем создавать безопасный многопоточный код. И не только многопоточный.

Расширяемость

Как известно, название языка изначально преподносилось как аббревиатура Scalable Language. Особенности языка позволяют писать гибкие и красивые DSL, вкладывать абстракции друг в друга, создавать очень удобные библиотеки и простые скрипты.

Это был последнее свойство языка, которое мне хотелось бы обсудить. Некоторые свойства отображены на картинке ниже.

Свойства языка Scala

Источник: https://prwatech.in/blog/apache-spark/introduction-to-scala-programming-language/Источник: https://prwatech.in/blog/apache-spark/introduction-to-scala-programming-language/

Рынок вакансий

Конечно, вакансий для скала-программистов значительно меньше, чем для джава-программистов. Но тем не менее язык демонстрирует стабильный рост и превосходит все остальные java-based языки, не считая самой джавы. Ближе всего из JVM-языков к нему подбирается Котлин благодаря тому, что его используют для программирования под мобильные телефоны. Сама джава по-прежнему вне конкуренции и больше всего JVM-вакансий именно на ней. Вот вам картинка про области применения скалы.

Источник: https://prwatech.in/blog/apache-spark/introduction-to-scala-programming-language/Источник: https://prwatech.in/blog/apache-spark/introduction-to-scala-programming-language/

Мои первые книжки

Самая лучшая книжка про скалу, которую я читал Scala by Example Мартина Одерски, написанная уже 10 лет назад. Прежде чем начинать сейчас читать книгу по скале, очень рекомендую убедиться что она обновлена до версии Scala 3.

А любимый курс это Functional Programming in Scala, я проходил его в самой первой версии, с тех пор он, конечно, успел измениться. Первые уроки этого курса основываются все на той же Scala by Example.

Если вы хотите поиграть скалой, не устанавливая IDE добро пожаловать в Scastie.

Пример выразительного кода

Много примеров короткого кода на скале можно найти вот в этом обсуждении: Samples of Scala and Java code where Scala code looks simpler/has fewer lines? Вот один из них:

Scala

object Main extends App {  val keywords = List("Apple", "Ananas", "Mango", "Banana", "Beer")  val result = keywords.sorted.groupBy(_.head)  println(result)}

Java 8

import java.util.*;class Main {  public static void main(String[] args) {    List&lt;String> keywords = Arrays.asList("Apple", "Ananas", "Mango", "Banana", "Beer");     Map&lt;Character, List&lt;String>> result = keywords.stream().sorted().collect(Collectors.groupingBy(it -> it.charAt(0)));    System.out.println(result);  }}

Java 7

import java.util.*;class Main {  public static void main(String[] args) {    List&lt;String> keywords = Arrays.asList("Apple", "Ananas", "Mango", "Banana", "Beer");     Map&lt;Character, List&lt;String>> result = new HashMap&lt;Character, List&lt;String>>();     for(String k : keywords) {         char firstChar = k.charAt(0);           if(!result.containsKey(firstChar)) {             result.put(firstChar, new  ArrayList&lt;String>());         }           result.get(firstChar).add(k);     }     for(List&lt;String> list : result.values()) {         Collections.sort(list);     }    System.out.println(result);   }}

Поступь прогресса

На прощанье шутка Кея Хорстмана The March of Progress

1980: C

printf("%10.2f", x);

1988: C++

cout << setw(10) << setprecision(2) << fixed << x;

1996: Java

java.text.NumberFormat formatter = java.text.NumberFormat.getNumberInstance(); formatter.setMinimumFractionDigits(2); formatter.setMaximumFractionDigits(2); String s = formatter.format(x); for (int i = s.length(); i < 10; i++) System.out.print(' '); System.out.print(s);

2004: Java

System.out.printf("%10.2f", x);

2008: Scala and Groovy

printf("%10.2f", x)

2012: Scala 2.10

println(f"$x%10.2f")

На этом я рискну опубликовать этот пост, но по-прежнему планирую его дописывать по результатам обсуждения с вами.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru