Например что использовать, когда нужно создавать колонку в таблице только в том случае, если такой колонки в ней нет?
Я могу, как написать код, который будет проверять, существует ли колонка, и лиш
потом добавлять без ошибок, так могу и без проверки пытаться добавить колонку, обернув метод в try-catch (если есть — перехватится исключение; если нет — колонка добавится).
Результат работы будет одинаковым.
И таких примеров можно привести массу, например можно проверять файлы на существование, и только потом делать копирование, а можно перехватывать исключения.
Какой метод более грамотный, или правильный?
Ответы
Ответ 1
Исключения позволяют сделать код чище и понятнее, поскольку с их помощью можно разделит
выполнение действий и обработку ошибок. В книге Мартина «Чистый код» этот аспект описан самым первым.
При этом, по моему опыту, надо именно разделять код: выносить блок try/catch в отдельны
метод. Программист, который будет разбираться с вашей программой, скажет вам «спасибо». Эта конструкция весьма громоздка, даже в простой форме try/finally, и в середине большого метода озадачит кого угодно.
Второй плюс исключений в том, что они позволяют передавать дополнительную информацию
Функция atoi из C ничего не могла сказать о том, почему именно не удалось конвертировать строку в целое число.
int result;
result = atoi("123"); /* в result 123 */
. . .
result = atoi("foo"); /* что в result? */
В таких языках, как Java и C# вы можете добавить в свой класс исключений необходимы
свойства, сочетая какие-нибудь коды ошибок с контекстами вызова и ещё чем-нибудь. Пример:
. . .
catch (SqlException e)
{
Console.WriteLine("Ошибка '{2}' в строке {0} процедуры {1}", e.LineNumber, e.Procedure, e.Message);
}
. . .
Разработчики, которые давно и прочно перешли на исключения, делают свой код ещё чище, не возвращая никаких кодов ошибок, в частности, пресловутого null:
// Что будет, если в хранилище нет пользователя с указанным userid?
// Вернёт null или сгенерирует исключение?
User user = userRepository.GetById(userId);
В настоящее время считается, что правильнее создавать исключение (за аргументам
снова отсылаю к книге «Чистый код»). Если метод используется для проверки наличия пользователя, его рекомендуют переделать в форму TryX. Она неуклюжа, но уже привычна, по крайней мере, для программистов .NET:
User user;
if (userRepository.TryGetById(userId, out user))
{
. . .
}
Что более ценно, она однозначна: смотря на код, вы не думаете: «а что, если такого пользователя нет?»
Теперь посмотрим на ситуацию с другой стороны — а когда не надо использовать исключения
На мой взгляд, тогда, когда они затрудняют понимание кода. Если ситуация не исключительная, тогда в тексте программы должна быть обычная проверка.
Например, приложение для нового документа генерирует имена Untitled.foo, Untitled1.foo, Untitled2.foo и т.д.
Та ситуация, что файл с таким именем уже существует, является вполне обычной, н
исключительной, поэтому и реализовать код корректнее с помощью обычной проверки:
public string GetNewDocumentName(string prefix)
{
var filename = prefix + ".foo";
if (!File.Exists(filename))
return filename;
int suffix = 0;
do
{
filename = prefix + (++suffix).ToString() + ".foo";
} while (File.Exists(filename));
return filename;
}
Этот код не только быстрее, чем аналогичный с использованием исключений, но, чт
важнее, понятнее другим программистам, потому что неявно передаёт им дополнительную информацию: эта штука будет случаться регулярно, и мы к этому готовы.
А вот, например, невероятная ситуация, что в папке скопилось 2 миллиарда untitled-файлов — несомненное исключение.
public string GetNewDocumentName(string prefix)
{
var filename = prefix + ".foo";
if (!File.Exists(filename))
return filename;
int suffix = 0;
do
{
if (suffix == int.MaxValue)
throw new InvalidOperationException("You're crazy!");
filename = prefix + (++suffix).ToString() + ".foo";
} while (File.Exists(filename));
return filename;
}
Такой код выглядит более запутанным. К счастью, мы можем часть проверок возложить на компилятор C#:
public string GetNewDocumentName(string prefix)
{
var filename = prefix + ".foo";
if (!File.Exists(filename))
return filename;
int suffix = 0;
do checked
{
filename = prefix + (suffix++).ToString() + ".foo";
} while (File.Exists(filename));
return filename;
}
Откуда возникает ещё одно правило: код можно сделать чище, если знать язык, платформу, библиотеку, и опираться на их исключения.
Выше я написал, что исключения выполняются медленнее, чем проверки, и хочу уточнит
свою мысль: не надо опираться на производительность при принятии решения. Правильна
передача смысла другому программисту, чистота кода — то, к чему следует стремится. Разница в производительности, хотя и существует, никогда не была настолько большой, чтобы пользователи её замечали. Ну, если только вы не пишите код для самого вложенного цикла в каком-нибудь графическом движке.
Ответ 2
Предварительная проверка быстрее работает и зачастую является более точной. Но только обработка исключений дает полную гарантию.
Пример: простое создание файла. Ошибок тут возможно - море:
в имени могут быть запрещенные символы (набор разрешенных символов зависит от файловой системы!);
полный путь к файлу может оказаться слишком длинным;
файл мог быть уже создан;
может не быть доступа для создания файлов;
диск может быть защищен от записи или вообще не поддерживать ее (CD-ROM тому пример).
Все эти варианты можно проверить заранее - и выдать пользователю понятное сообщение на русском языке.
Но есть ситуации, которые ни одна предварительная проверка не отловит:
файл может быть создан другой программой сразу после проверки;
файл может быть заблокирован антивирусом;
если файл создается на сетевом ресурсе - может "отвалиться" сеть.
Как настроить angular-gridster, чтобы убрать Z-слои и наложение изображений дру
на друга? Сейчас, если изображения большие, они накладываются друг на друга.
Настройки, видимо, находятся в $scope.gridOpts. И еще не срабатывает resizable: { enabled : false }, все равно можно растягивать изображения.
Есть официальный демо-пример, там ресайз каким-то образом отключается.
Мои текущие настройки:
$scope.gridsterOpts = {
margins: [20, 20],
outerMargin: false,
floating: true,
pushing: true,
draggable: {
enabled: true
},
resizable: {
enabled: false,
handles: ['n', 'e', 's', 'w', 'se', 'sw']
}
}
Необходимо, чтобы изображения не накладывались друг на друга, т.е. учитывался и
реальный размер, и они просто располагались рядом; чтобы не было возможности ресайз
при помощи растягивания изображений за угол. И была возможность перемещения. Сейчас изображения перемещаются нормально, но накладываются друг на друга, и все равно доступен ресайз.
Обновление:
После добавления к html-блоку div, в котором находятся все изображения, опции ресайз выключился. Осталась проблема наложения друг на друга изображений.
styles
.gridster-item:not(.gridster-item-moving) {
z-index: initial; /* drop z-index to make it possible to set higher than 2 z-index for widget elements */
}
Для камеры есть коллбеки задания превью, получения картинки, но не понятно одно
у меня есть изображение в камере — делаю тач по какой-либо области — нужно получить область тача (цвет или хотя бы байты с данными).
Каким образом?
UPD:
Возникла проблема с получением картинки с канваса.
Делаем так:
Canvas canvas = mHolder.lockCanvas(mHolder.getSurfaceFrame());
А что дальше - непонятно.
Про канву можно забыть. Массив с изображением мы получаем из коллбека превьюшки для камеры, но получаем всегда null и нагугливаем этот вопрос.
Использую метод так:
Camera.Size camSize = camera.getParameters().getPictureSize();
int[] outBuff = new int[camSize.width * camSize.height];
decodeYUV(outBuff, data, camSize.width, camSize.height);
Log.d(TAG, "int[]=" + Arrays.toString(outBuff));
В лог ничего не выводится.
Итого:
Нужно по тачу в превью камеры получить область изображения (в байтах). То есть, делаем тач — получаем кусок картинки на которой был сделан тач.
Ответы
Ответ 1
Можно использовать YuvImage:
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Size previewSize = camera.getParameters().getPreviewSize();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, previewSize.width, previewSize.height, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Rect rect = new Rect(0, 0, previewSize.width, previewSize.height);
yuvImage.compressToJpeg(rect, 100, baos);
byte[] bytes = baos.toByteArray();
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
// дальше можно получить область касания
someImageView.setImageBitmap(Bitmap.createBitmap(bitmap,
tapRegionX,
tapRegionY,
tapRegionWidth,
tapRegionHeight));
}
null - это экземпляр чего-то?
К какому типу принадлежит null?
Что такое null?
Как он представлен в памяти?
Ответы
Ответ 1
null - это экземпляр чего-то?
Нет такого типа, которому бы соответствовал instanceof от null.
15.20.2 Type Comparison Operator instanceof
RelationalExpression:
RelationalExpression instanceof ReferenceType
В рантайме результат оператора instanceof будет true, если
значение RelationalExpression не null и ссылка может быть приведена
к ReferenceType без получения исключения ClassCastException.
Иначе результат будет false.
Это означает, что для любого типа E и R, для любого E o, где o == null, o instanceof R будет всегда false.
К какому типу принадлежит null?
JLS 4.1 The Kinds of Types and Values.
Есть также специальный тип - null, тип выражения null,
у которого нет имени. И т.к. тип null не имеет имени, невозможно объявить
переменную с типом null или привести переменную к типу null.
null ссылка - единственное возможное значение выражение типа null
. null всегда можно привести к любому ссылочному типу. В
действительности, можно игнорировать тип null и притвориться, что
null - это просто специальный литерал, который может быть любым ссылочным типом.
Что такое null?
Как сказано в цитате из JLS выше, можно считать, что "null - это просто специальный литерал, который может быть любым ссылочным типом".
В Java null == null (что верно не для всех языков). Из описания java.lang.Object:
public boolean equals(Object obj)
Для любой не null переменной x, x.equals(null) должно возвращать false.
null также является значением по умолчанию для всех ссылочных типов.
JLS 4.12.5 Initial Values of Variables:
Любой экземпляр класса, переменная или элемент массива специализируются значением по умолчанию:
Для всех ссылочных типов (§4.3), дефолтное значение - null.
Вы можете использовать это свойства для отложенной инициализации, когда поле буде
иметь начальное значение null до тех пор, пока оно фактически не будет использовано, где будет заменено "реальным" значением (вычисление которого может быть дорогостоящим).
Есть и другие применения. Если посмотреть на java.lang.System:
public static Console console()
Returns: The system console, if any, otherwise null.
Это очень распространённая практика: null используется для обозначения несуществующего объекта.
Другой пример - java.io.BufferedReader:
public String readLine() throws IOException
Returns: A String containing the contents of the line, not including any line-termination characters, or null if the end of the
stream has been reached.
readLine() будет возвращать instanceof String для каждой строки, пока не получи
null, обозначающий конец. Это позволяет обрабатывать каждую строку следующим образом:
String line;
while ((line = reader.readLine()) != null) {
process(line);
}
Примечание: пустая строка - не проблема, т.к. "" != null.
Давайте рассмотри java.util.Map:
V get(Object key)
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
If this map permits null values, then a return value of null does not necessaril
indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases.
Здесь мы видим, что использование null может усложнить дело. Тут говорится, что
если такого ключа нет, будет возвращён null. Второе утверждение гласит, что даже если элемент по такому ключу есть, всё равно может вернуться null.
К примеру, java.util.Hashtable делает вещи проще путём запрета null в ключах и значениях
так что, если V get(Object key) вернёт null это однозначно говорит о том, что под таким ключом ничего нет.
Операции автоматического боксинга/анбоксинга на null выбросят java.lang.NullPointerException:
Integer i = null;
// при анбоксинге null в integer будет выброшен NullPointerException
int a = i;
Если резюмировать, то null используется как специальное значение для обозначения:
Не инициализированного состояния.
Терминальное условие
Несуществующий объект.
Неизвестное значение.
Как null представлен в памяти?
Из спецификации JVM:
The Java Virtual Machine specification does not mandate a concrete
value encoding null.
Небольшое дополнение
Интересная цитата C.A.R Hoare:
I call it my billion-dollar mistake. It was the invention of the
null reference in 1965. At that time, I was designing the first
comprehensive type system for references in an object oriented
language (ALGOL W). My goal was to ensure that all use of
references should be absolutely safe, with checking performed
automatically by the compiler. But I couldn't resist the temptation to
put in a null reference, simply because it was so easy to implement.
This has led to innumerable errors, vulnerabilities, and system
crashes, which have probably caused a billion dollars of pain and
damage in the last forty years.
Так же можно посмотреть презентацию про ошибку на миллиард долларов.
Читая разные статьи иногда сталкиваюсь с таким выражением :
Boilerplate code
В моей команде внятно никто не смог объяснить это понятие. Что же это такое?
Ответы
Ответ 1
В Википедии же всё написано
Понятие boilerplate code или boilerplate относится к секциям кода, которые должны быть написаны во многих местах с минимальными изменениями.
Часто используется по отношению к языкам, в которых программист должен написать много кода, чтобы выполнить минимальную задачу.
Многие современные IDE автоматически генерируют boilerplate код. Также существуе
такая вещь, как скаффолд (scaffold), который позволяет генерировать boilerplate (например, в Ruby on Rails можно генерировать базовые CRUD методы в контроллерах).
Примеры:
html
Что такое Boilerplate code?
Boilerplate code, который нам необходимо повторять в каждом шаблоне. (Пруф)
JAVA
public class Pet {
private String name;
private Person owner;
public Pet(String name, Person owner) {
this.name = name;
this.owner = owner;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
P.S.: это главная причина, по которой я в своё время отказался от программирования на Java.
Ответ 2
Шаблонный код, который должен быть написан во многих местах практически без изменений
Программисту приходится писать этот код каждый раз, как правило, занимая ненужное место, загромождая программу и отвлекая внимание от бизнес-логики. Это делает программу более сложной, без какой-либо пользы.
Ответ 3
Часто на хитхабе есть проекты в которых много чего сделано из того что часто встречаетс
в среднестатистическом проекте, эти проекты сделали чтобы их копировли и на их основе делали свои проекты, не заморачивались с одними и теми же вещами когда начинаешь делать проект. Такой проект еще называют starter.
Вот примеры: html5-boilerplate, react-boilerplate, electron-react-boilerplate, laravel-5-boilerplate, hackathon-starter.
Плюсы:
Многие пакеты уже поставлены и настроены
Написано много кода за Вас
̶ ̶Н̶е̶ ̶т̶а̶к̶ ̶м̶н̶о̶г̶о̶ ̶к̶о̶с̶т̶ы̶л̶е̶й̶ ̶в̶ ̶к̶о̶д̶е̶ ̶к̶а̶к̶ ̶е̶с̶л̶и̶ ̶б̶ы̶ ̶п̶и̶с̶а̶т̶ь̶ ̶с̶а̶м̶о̶м̶у̶
Минусы:
Бывает в стартер входит инструменты которые не любишь или не хочешь использоват
сейчас, приходиться выпиливать
Порой не понятно благодаря какому коду что-то работает
Бывает тяжело переделать код на свой лад
Нет уверенности в коде потому как писал его не сам
На C# имеется удивительно быстрый словарь (Dictionary), хотелось бы узнать, а имеетс
ли такой же производительный только на C++ ? Пробовал unordered_map, hash_map, map, но производительность в разы ниже чем у Dictionary сишарповского...
P.S: Пара имеет вид
Ответы
Ответ 1
На самом деле, сравнение языков -- штука неблагодарная. Всегда найдутся тесты, н
которых один из языков выиграет по сравнению с другим, и всегда найдутся люди, считающие, что данный тест не релевантен и подобный код никогда не будет встречаться в реальности.
Тем не менее, я бы не сказал, что результаты ТС очень уж неожиданны: в .NET действительн
выделение памяти обычно происходит быстрее, чем в нативных языках без кастомного аллокатора. А небольшие тесты обычно гораздо больше нагружают аллокатор чем, скажем, механизмы вызова функций.
Причиной такой разницы в производительности аллокатора является то, что объекты C+
невозможно перемещать в памяти, а значит, привычный алгоритм выделения памяти (который
как известно, поддерживает список свободных блоков, и ищет подходящий при аллокации
работает довольно медленно, и, хуже того, требует глобальной блокировки памяти (что ещё более ухудшает ситуацию в многопоточном сценарии). Кроме того, объекты в C++ имеют тенденцию освобождаться быстро как только можно, что приводит к дополнительной нагрузке на освобождение памяти, которое тоже требует глобальную блокировку.
В среде .NET же всё происходит по-другому. Объекты всегда выделяются на вершине heap-памяти
а значит, выделение не медленнее, чем InterlockedIncrement. .NET'у не нужно поддерживать список свободных блоков потому, что при сборке мусора происходит компактификация heap-памяти: объекты перемещаются, заполняя "дыры".
Кроме того, известия о том, что код на C++ вполне может быть медленнее аналогичног
кода на C#, давно не новость. Вот, например, замечательная серия статей о простом приложении от мастеров нативного программирования, и резюме Джефа Этвуда:
Чтобы обойти по производительности версию на C#, Реймонду пришлось написать собственны
процедуры ввода-вывода, переписать класс string, воспользоваться кастомным аллокатором, а также собственной процедурой отображения кодовых страниц.
Это подтверждается и бенчмарком, который приведён ниже: нативные контейнеры "из коробки" существенно проигрывают дотнетовским, (некоторые) самописные контейнеры выигрывают.
Теперь самое интересное: измерения.
C#:
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Sharp
{
class Program
{
static void Main(string[] args)
{
var dict = new Dictionary();
int seed = 1;
var timer = new Stopwatch();
timer.Start();
for (int i = 0; i < 10000000; i++)
{
seed = 1664525 * seed + 1013904223;
dict.Add(seed, i);
}
timer.Stop();
Console.WriteLine(
"elapsed time = {0} ms, dictionary entries count = {1}",
timer.ElapsedMilliseconds,
dict.Count);
}
}
}
C++:
#include "stdafx.h"
#include
#include
Этак с начала времён (юниксовых, т.е. 1.01.1970) в начале скрипта рекомендовалос
использовать shebang / hashbang - строку, указывающую на используемый интерпретатор, например:
#!/bin/bash
echo 'hello world'
#!/usr/bin/python
print 'hello world'
#!/usr/bin/python3
print('hello world')
Недавно наткнулся на рекомендацию использовать вместо этого такую форму:
#!/usr/bin/env bash
echo 'hello world'
#!/usr/bin/env python
print 'hello world'
#!/usr/bin/env python3
print('hello world')
Пожалуйста, объясните, как это работает и в чём преимущества такого подхода? Есл
есть ограничения и/или недостатки по сравнению с обычным способом - то и о них хотелось бы услышать.
Ответы
Ответ 1
Основная идея - улучшение переносимости. Не гарантируется, что на различных системах исполняемый файл будет лежать по пути, который указан в shebang.
Использование env позволяет снизить этот риск за счет запуска команды на основе данных из переменной среды PATH
Более того, если по каким-либо причинам вместо стандартного исполняемого файла пользовател
хочет использовать свой, то ему достаточно добавить путь к этому файлу в PATH без необходимости исправления скриптов:
~ $ cp /bin/bash /home/soon/python
~ $ export PATH=/home/soon:$PATH
~ $ env python
[soon@archlinux ~]$ exit
В примере выше я скопировал bash к себе в домашнюю директорию (переименовав при это
файл в python), добавил путь в PATH и запустил python с помощью env, которая усужливо запустила bash, т.к. нашла его раньше.
Еще одним примером является использование виртуальных окружений при разработке н
Python (virtualenv). Поскольку они также перебивают PATH, env позволяет использовать нужную версию исполняемого файла:
~ $ workon black-box-challenge-2016-py2
~ (venv:black-box-challenge-2016-py2) $ env python
Python 2.7.11 (default, Mar 31 2016, 06:18:34)
[GCC 5.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.executable
/home/soon/.virtualenvs/black-box-challenge-2016-py2/bin/python
>>>
Ответ 2
краткое резюме из информации, приведённой в ответах к этому вопросу и к аналогичным вопросам:
Why is it better to use “#!/usr/bin/env NAME” instead of “#!/path/to/NAME” as my shebang?
Why is #!/usr/bin/env bash superior to #!/bin/bash?
преимущества:
будет запущена программа не из конкретно указанного файла, а из того, что встретитс
первым в списке каталогов, заданных переменной окружения PATH пользователя, запустившего скрипт.
полезно для случаев, когда по указанному пути в системе, на которой будет выполнятьс
скрипт, такого файла нет, или когда пользователь модифицирует эту переменную окружения для того, чтобы выполнялась какая-то особая программа (другая реализация, другая версия).
недостатки:
будет запущена программа не из конкретно указанного файла, а из того, что встретитс
первым в списке каталогов, заданных переменной окружения PATH пользователя, запустившего скрипт.
например, разные пользователи (или тот же пользователь, но с модифицированным содержимы
этой переменной окружения) могут получить разные результаты выполнения одного и того же скрипта.
программы /usr/bin/env может не существовать в системе, где будет запускаться скрипт, соответственно, попытка запуска будет неудачной.
нельзя будет указать дополнительной опции для выполняемой программы. так получится:
#!/путь/к/программе опция
а так — нет:
#!/usr/bin/env программа опция
Ответ 3
Это связано с переносимостью скриптов. Дело в том, что путь к python, к примеру
на разных системах может отличаться, а вот путь к env на всех системах неизменный. Поэтому
вызывая env и передавая ей в качестве аргумента нужный интерпретатор, можно быть уверенным, что скрипт будет запущен вне зависимости от того, где на самом деле находится интерпретатор (главное, чтобы он был в PATH).
Ответ 4
Команда env отображает текущие переменые окружения. А если стоит с командой
env bash
то выполняет команду с текущеми перемеными окружения.
В некоторых системах при запуске интерпритаторов используется не текущие перемены
окружения , а считываются из файлов или даже ставятся по умолчанию. И если Вы изменили или добавили переменые окружения , то они не будут учтены в запускаемом скрипте, а при запуске через env все переменые будут учтены.
Я прочитал, что в UTF-16 два различных порядка байтов (endianness) появились потому, что два различных порядка байтов существуют в архитектуре процессоров:
Систему, совместимую с процессорами x86, называют little endian, а с процессорами m68k и SPARC — big endian.
То есть одно и то же число 0x1234ABCD кодируется последовательностью байтов:
little endian: 12 34 56 78
big endian: 78 56 34 12
Соответственно, при раскодировании последовательности байт в последовательность чисе
(или code point'ов юникода) нужно учитывать использованный при кодировании порядок байтов. (Это несколько дилетантское утверждение, но лучше сформулировать я пока не могу).
Например, если мы кодируем "Привет 😃" в UTF-16:
# big endian:
П р и в е т ( ) 😃
04 1F 04 40 04 38 04 32 04 35 04 42 00 20 D8 3D DE 03
# little endian:
П р и в е т ( ) 😃
1F 04 40 04 38 04 32 04 35 04 42 04 20 00 03 DE D3 D8
Вроде бы всё очевидно. Мы сопоставляем code point'y некоторое число согласно алгоритм
кодировки, а потом записываем это число в соответствии с порядком байт, принятым в системе.
Теперь UTF-8:
П р и в е т ( ) 😃
D0 9F D1 80 D0 B8 D0 B2 D0 B5 D1 82 20 F0 9F 98 83
# в двоичной системе счисления:
11010000 10011111
11010001 10000000
11010000 10111000
11010000 10110010
11010000 10110101
11010001 10000010
# по первому биту сразу видно, что этот code point закодирован одним байтом
00100000
# а здесь первый байт начинается с 4 единиц, значит будет 3 trailing byte'а
11110000 10011111 10011000 10000011
Алгоритм кодировки поменялся, но архитектура процессора осталась прежней! Мы по прежнем
получаем число, которое занимает от 1 до 4х байт. Почему с UTF-8 нас не беспокоит, что байты будут записаны вот так?
П р и в е т ( ) 😃
9F D0 80 D1 B8 D0 B2 D0 B5 D0 82 D1 20 83 98 9F F0
Дополнение:
Задавая этот вопрос, я уже знал, что UTF-8 использует однобайтовые code unit'ы, а UTF-16 – двухбайтовые. Попробую уточнить, что мне было непонятно.
Есть символ «😃». При кодировании его в алгоритме UTF-8 получается последовательност
байт F0 9F 98 83. Это тоже число, четырёхбайтовое слово, его можно использовать дл
сравнения или сортировки строк, закодированных в UTF-8 (правда, толку от такой сортировк
немного). В вышеуказанном виде оно имеет порядок big-endian, значит системы с архитектурой big-endian могут получить преимущество в работе с ним. Но что с little-endian? Как там будет происходить сравнение? Для примера, будем сравнивать «😃» (F0 9F 98 83) и «😐» (F0 9F 98 90). У меня есть два предположения:
Big-endian системы работают с закодированными в UTF-8 символами, как с 1, 2, 3, 4-байтным
словами и получают преимущество в скорости операций. То есть, в них достаточно сравнить F09F9883 и F09F9890 как четырехбайтовые слова. Little-endian системы вынуждены сравнивать побайтно или переворачивать слово дважды.
Любая архитектура работает с закодированными в UTF-8 символами строго как с последовательностям
байт, не оперируя словами более 1 байта. То есть, сравниваются пары байт: FO и FO, 9F и 9F, 98 и 98, 83 и 90. При этом теряется потенциальное преимущество от сравнения двух слов, зато для любой архитектуры алгоритм работает одинаково.
Ответы
Ответ 1
Дело в том, что UTF-8 и UTF-16 обычно хранится в памяти нераспакованным, в том ж
виде, как он приходит в потоке (например, в файле). [Ну и если он таки распаковывается, то это рассмотрение играет роль в момент распаковки.]
Само по себе хранение никакой проблемы, понятно, не создаёт. Проблему создаёт обработка, например, сравнение символов.
В UTF-8 вы читаете входной поток по байту, и интерпретируете их последовательно
Соответственно получившееся значение code point получается однозначно и не зависит от порядка байтов машины: результат приведения к code point однозначно определён, и при сравнении используется именно он.
А вот в UTF-16 вы читаете входной поток по два байта, и для сравнения в обычном случа
вовсе не нужно вычислять code point. Если у вас есть двухбайтное слово в нативной кодировке
не соответствующее суррогатной паре (а это основной, самый частый случай), то для сравнение можно просто использовать её значение, она равна своему code point. Но если кодировка не нативная, вам понадобится переставить байты.
Если бы в UTF-16 был задан конкретный порядок байт, составляющих двойной байт (
тем самым задана endianness), то платформы, на которых данный порядок не является нативным
оказались бы в проигрыше: они должны были бы совершать дополнительные действия (перестановку байт) при чтении и записи потока. С двумя вариантами кодировки приложения могут пользоваться тем форматом, который нативен на их платформе, получая тем самым выигрыш в скорости.
Держать байты в памяти в ненативном порядке — плохая идея: их получается намног
затратнее сортировать и сравнивать. С нативным порядком в обычном случае нужна лиш
проверка на суррогатную пару, а с ненативным ещё и перестановка байтов. Например, для сравнения 1C 55 и 1B 77 в big endian-смысле на little endian-системе не обойтись без перестановки байт. Потому что если сравнивать без перестановки, то будут сравниваться 0x551C и 0x771B, и результат будет неверным. То же и для сортировки.
Обновление ответа к обновлению вопроса.
Насколько я понимаю, при обработке UTF-8 мы не знаем наперёд, сколько байт буде
занимать тот или иной символ. Поэтому мы вынуждены работать с потоком байт, а не потоко
нативных слов. Если бы мы знали, что наш символ всегда кодируется четырьмя байтами
мы могли бы или просто сравнить нативным образом,или для неподходящей байтоориентаци
скопировать оба четырёхбайтных слова во временные переменные, развернуть их и сравнить нативным сравнением четырёхбайтных слов. Но этому мешает ещё и то, что наши четыре байта находятся на случайной позиции в потоке, и значит, скорее всего не выровнены на границу 4-ёх байт. На многих архитектурах (кроме, однако, x86) такой доступ не разрешён, и придётся «выковыривать» байты по частям. Таким образом, получается проще и эффективнее просто сравнить байты по одному.
В UTF-16, кстати, возможных случаев меньше: там возможен либо символ из одного codepoint'а
который можно сравнивать нативно или с одним разворотом, если не угадали с порядком байт, либо из двух (где наверное лучше снова-таки сравнить два раза по двухбайтному слову).
Ответ 2
Почему проблема порядка байтов есть в UTF-16, но её нет в UTF-8?
Потому что code unit равен 8 битам (одному байту) в UTF-8 и 16 битам (двум байтам
в UTF-16. В зависимости от порядка байт внутри code unit, есть utf-16le и utf-16be кодировки, которые могут быть использованы на одном и том же компьютере вне зависимости от endianness CPU):
>>> 'я'.encode('utf-16le').hex()
'4f04'
>>> 'я'.encode('utf-16be').hex()
'044f'
Символ я (U+44f) кодируется в UTF-16 в одно и то же 16-битное число: 1103 == 0x44f
что для utf-16 совпадает с номером (Unicode code point) символа в Юникоде (для BMP символов). Само 16-битное число может быть в памяти представлено в виде двух байт: 4f 04 (от младшего к старшему порядок байт) или 04 4f (от старшего к младшему порядок байт).
>>> 'я'.encode('utf-8').hex()
'd18f'
я (U+44f) кодируется в UTF-8, используя два 8-битных числа 209 == 0xd1 и 143 =
0x8f. В общем случае UTF-8 может использовать от 1 до 4 октетов (8-битных чисел) для каждого символа (Unicode code point).
>>> '😂'.encode('utf-16le').hex()
'3dd802de'
>>> '😂'.encode('utf-16be').hex()
'd83dde02'
>>> '😂'.encode('utf-8').hex()
'f09f9882'
Символ 😂 (U+1f602) кодируется в utf-16, используя два 16-битных слова (utf-16 cod
units): 0xd83d и 0xde02 (utf-16 суррогатная пара). Представление слова в виде байт зависит от выбранного порядка байт (le, be), но порядок самих слов не меняется.
😂 (U+1f602) кодируется в utf-8, используя четыре октета (utf-8 code units): 0xf0
0x9f, 0x98, 0x82. Представление октета в виде 8-битового байта, очевидно, не зависит от порядка (один октет—один байт).
Последовательность code units (октеты для utf-8 и 16-битные слова для utf-16), используема
для кодирования выбранного символа, однозначно определена выбранной кодировкой—в частности нельзя порядок code units менять как в utf-16 так и в utf-8 кодировках.
Оба пункта из дополнения к вопросу у вас неверны. Не нужно путать как результат
виде байт представляется при обмене с внешним миром или внутри разных частей в программ
(при записи на диск, отсылке по сети, вызове API) и какие инструкции CPU используе
для работы с данными, выполняя конкретный алгоритм. То что октеты нельзя переставлять в utf-8 результате, ещё не значит, что фактические алгоритмы не могут работать с бо́льшими единицами. К примеру, memcpy() очевидно сохраняет порядок байт, при этом фактическая реализация может работать c целыми словами (например, с 64-битными словами).
Ответ 3
Сама по себе проблема с endianness возникает из-за разной общепринятой визуализации значений в памяти в и регистрах процессора:
Вот как обычно нумеруют байты в регистре процессора:
[ 12 34 AB CD ]
разряд 3 2 1 0
Старшие разряды пишутся слева - как традиционно сложилось в математике.
В то же время в той же математике сложилась традиция рисовать оси и отрезки и прочи
множества слева направо. И все представляют себе память как длинный бесконечный массив байт.
[ ?? ?? ?? ?? ...]
адрес 0 1 2 3
И у разработчиков процессора есть два решения:
сохранять 0-й разряд в 0-й адрес, 1-й в 1-й и т.д, пожертвовав визуализацией, н
получив выигрыш в скорости и простоте операций типа "вычитать из памяти 0-й байт в 0-й разряд (little-endian)"
разворачивать значение при сохранении, ради удобства отладки
В рамках одной системы нет никаких проблем. Главное писать значения в память та
же, как вы их оттуда читаете - и можно не думать об endiannes.
И все идет хорошо, пока вам не надо передать строку на другой компьютер (прямо
по сети, или косвенно - как файл). Создатели сетевых протоколов заранее договорилис
как передавать отдельные байты. Т.е. если вы передадите с x86 по сети байты 1, 2, 3, 4, 5, 6 - то любая моторолла получит их по сети в порядке 1, 2, 3, 4, 5, 6. Это жестко вбито в стандартах, на всех уровнях, от TCP/IP до Ethernet.
А вот насчет передачи пар байт или четверок байт - никаких договоренностей нет.
UTF-8 работает с потоком байт. Предположим, вы хотите записать на диск или переслать "привет" по сети. Это, с точки зрения энкодера, выглядит так:
передать D0
передать 9F
передать D1
передать 80
...
Получающая сторона (стандарт же!) гарантированно прочитает их в том же порядке
D0 9F D1 80...
Опять же, при записи в память по одному байту никаких разворотов не происходит, и в памяти это же значение представляется в виде
[ D0 9F D1 80 ]
Так происходит только потому, что в русском (английском) языке принято писать буквы направо, что случайно совпадает с общепринятой визуализацией памяти.
Поэтому строку UTF-8 достаточно просто записать в память - и она готова к передаче
Это результат договоренностей на уровне сетевых протоколов / протоколов общения с диском - они тоже работают на уровне байт, а не на уровне бит.
Ок, теперь нам захотелось передать тот же привет, но в UTF-16:
0x041F 0x0440 0x0438 0x0432 0x0435 0x0442
Энкодер в UTF-16 не заморачивается и передает эти двухбайтовые слова по сети/в память/н
диск. По слову за раз. И ожидает что первое слово будет передано/записано первым, второе - вторым и т.д.
передать 0x041F
передать 0x0440
передать 0x0438
Как они будут записаны / переданы - зависит от endiansess. Для LE процессор не заморачивается
[ 1F, 04 ] [ 40, 04 ] [ 38, 04]
адрес 0 1 2 3 4 5
Для big endian - он старательно разворачивает каждое слово:
[ 04, 1F ] [ 04, 40 ] [ 04, 38]
адрес 0 1 2 3 4 5
Договоренностей о передаче двухбайтовых слов по сети нет, как и договоренностей о хранении их на диске. Поэтому в UTF-16 требуется BOM.
На самом деле та же проблема существует с битами при использовании UTF-8 (да и вообщ
при передаче любых байт куда угодно). Например, вы хотите передать по сети байт D0. Он же 11010000. Вы будете передавать его как 0, 0, 0, 0, 1, 0, 1, 1? Или как 1, 1, 0, 1, 0, 0, 0, 0?
Вы с этой проблемой не сталкиваетесь по разным причинам:
- Отсутствует необходимость визуализации бит при хранениии.
- Закрыт доступ к реальному формату хранения на физическом уровне (память и дис
не позволяют адресовать и читать отдельные биты).
- Жесткая стандартизация - заранее согласованный порядк передачи бит в рамках байта в конкретном сетевом протоколе позволяет вам работать на уровне байт.
Достаточно взять любой способ передачи, в котором насчет порядка бит виден (например, попробуйте сделать железку, которая через COM-порт) - и проблема себя проявит.
Ответ 4
То есть одно и то же число 0x1234ABCD кодируется последовательностью байтов:
little endian: 12 34 56 78
big endian: 78 56 34 12
Соответственно, при раскодировании ...
Нет никакого раскодирования. Есть два представления числа в памяти.
http://ideone.com/wsOkXK
#include
int main()
{
volatile int x = 0x1234ABCD;
const unsigned char *p = (unsigned char *)&x;
printf("%0*X\n", sizeof(int) << 1, x);
for (unsigned q=0; q
Ответ 5
В обоих случаях порядок выборки из памяти чисел, кодирующих символ, одинаковый.
случае UTF-8 эти числа однобайтовые и проблемы старшего и младшего байтов, по понятно
причине, не возникает. Когда же речь заходит об UTF-16, оказывается, что в разных архитектурах порядок записи байтов в память разный. Например, в архитектуре Intel первым идёт младший байт, а в ARM - старший.
Недавно стало известно об уязвимости Meltdown, которая затрагивает большинство процессоро
Intel. В чём она заключается? Как функционирует атака? Как можно сдетектировать атаку и/или защититься от неё?
Ответы
Ответ 1
Смотрите, в чём дело.
У многих современных процессоров есть так называемое спекулятивное выполнение. Что это такое?
Дело в том, что компьютерная память медленная, а выполнение инструкций быстрое. Поэтом
если условный переход зависит от значения, лежащего в памяти, то процессор во врем
ожидания, пока из памяти «подгонят» нужное значение, может начать выполнять инструкци
той ветки, которую он считает более вероятной. Эти инструкции выполняются «секретно», их результаты не видны до тех пор, пока не произойдёт реальная подгрузка ожидаемого значения из памяти и процессор реально не убедится, что выполнение таки-пойдёт по ожидаемой ветке.
Если выполнение реально пойдёт по нужной ветке, побочные эффекты «секретно» выполненны
операций публикуются процессором и становятся видимыми другим инструкциям. Если же выполнение не пойдёт по нужной ветке, спекулятивно выполненные команды аннулируются, и их результат никому не виден.
Однако, эти отменённые команды могут всё же производить побочные эффекты, наблюдаемы
из нашего кода. Если отменённая команда читала память, которая не была загружена в кэш процессора, то эта память грузилась в кэш процессора, и последующее обращение к ней было быстрее. Таким образом, можно судить о том, что же делала отменённая команда.
Каким образом можно это использовать? Мы можем в спекулятивно выполняемом коде
зависимости от считанных данных подгрузить в кэш различные куски памяти, и измерив время доступа к ним, решить, какие же данные были прочитаны.
Чем это отличается от обыкновенного чтения данных? Вот тут-то и проявляется ошибк
в Intel'овских процессорах. Дело в том, что мы можем спекулятивно читать память ядра, которая нам недоступна. Ошибка заключается в том, что при спекулятивном чтении не проверяются права на доступ к памяти.
Итак, наш код выглядит следующим образом:
Мы выделяем участок памяти X, по длине больший, чем несколько линий кэша.
Мы сбрасываем кэш так, чтобы память X не была в кэше. (Например, так.)
Мы определяем адрес в ядре, который мы хотим прочитать, и записываем его в указатель p. Мы пишем код вида
if (data == 0)
{
byte kernelByte = *p;
byte dummy = X[(kernelByte & 1) * CACHE_LINE_SIZE];
}
В data мы кладём ненулевое значение, и вытесняем data из кэша.
Мы выполняем указанный код, после чего измеряем время доступа к X[0] и X[CACHE_LINE_SIZE].
Что происходит? Весь код внутри if не выполняется в реальности, но выполняется лиш
спекулятивно. Про спекулятивном выполнении чтение по указателю на память ядра завершаетс
успешно. В зависимости от значения младшего бита спекулятивно прочитанного значения мы производим доступ к одной из двух кэш-линий. Таким образом, одна из кэш-линий окажется загруженной в результате спекулятивного выполнения.
Если время доступа к X[0] близко к типичному времени доступа к закэшированной памяти
то в спекулятивном коде было обращение к X[0], и значит, kernelByte & 1 == 0. Если врем
доступа к X[CACHE_LINE_SIZE] близко к типичному времени доступа к закэшированной памяти, то в спекулятивном коде было обращение к X[CACHE_LINE_SIZE], и значит, kernelByte & 1 == 1.
Таким образом, мы смогли прочитать один бит защищённой памяти. Далее, один за одни
можно считывать информацию. На текущий момент скорости считывания составляют около 1-2 килобайт в секунду.
Процессоры AMD не подвержены атаке Meltdown. Если верить информации от инженера AMD,
AMD processors are not subject to the types of attacks that the kernel
page table isolation feature protects against. The AMD microarchitecture
does not allow memory references, including speculative references, that
access higher privileged data when running in a lesser privileged mode
when that access would result in a page fault.
Параллельно с описанной атакой Meltdown есть очень похожая, в некотором смысле более общая атака, известная как Spectre.
В ней «недоступная» память читается так же, но не через границы уровней доступа
То есть, читается не ядро, а память собственного процесса (или, возможно, даже другог
процесса, бегущего с теми же правами!). Этой уязвимости подвержены уже и процессоры AMD, потому что блокировка спекулятивного выполнения на уровне процессора «не видит» границ между процессами, т. к. процесс — понятие уровня операционной системы.
Зачем может понадобиться читать память своего же процесса? Например, для выхода и
«песочницы». Код на JS в браузере обычно бежит в том же процессе, с теми же правами
а контроль над тем, чтобы JS не читал память за границами положенного, возложена на код, который проверяет выход индексов за границы диапазона. Как мы уже видели, выход индекса за границы диапазона возможен даже при контроле значения индекса при спекулятивном исполнении кода.
Обе атаки полностью пассивные, «молчаливые», поэтому на текущий момент не существуе
защиты или метода обнаружения атак, который мы как пользователи смогли бы использовать. Единственный способ защититься от Meltdown — ждать
патча операционной системы.
Патч для Meltdown заключается в том, что из виртуального пространства процесса операционная система убирает память ядра. Это замедляет системные вызовы, но исправляет данную проблему.
Майкрософт выпустила патч против Meltdown для Windows 10 3.01.2018 как KB4056892
Убедитесь, что он у вас поставлен (если у вас, конечно, Windows 10). Это можно сделать, например, при помощи PowerShell:
PS > Install-Module SpeculationControl
PS > Get-SpeculationControlSettings
Apple включила защиту от Meltdown в iOS 11.2, macOS 10.13.2 и tvOS 11.2. Проверьте вашу версию системы, если у вас Мак или i-устройство!
Обновления для Ubuntu практически готовы, и ожидаются 9.01.2018 (или раньше, если получится). Не пропустите их.
Одна из неприятностей данных уязвимостей состоит в том, что атаку при определённых условиях можно проводить даже из Javascript.
Чтобы защититься от атаки Meltdown из javascript'а других сайтов, можно его временн
отключить до выхода патча, но вы же не станете этого делать? Патча на Spectre для операционной системы нет, и он невозможен: уязвимость нужно исправлять в браузере.
Производители браузеров тоже прилагают усилия, чтобы было сложнее воспользоваться уязвимостью из Javascript.
Firefox включает изменения, направленные на борьбу с уязвимостью, начиная с уже вышедшей версии 57, и усиливает защиту в версии 57.0.4 (бета).
Хром выпустит изменения в JS-движке V8 23 января (версия 64), но уже начиная с сегодняшнег
дня будет предупреждать об использовании подозрительного API. На сегодняшний день Гугл рекомендует поставить последнюю версию Хрома и включить Site Isolation.
Edge и Internet Explorer получили изменения, направленные на борьбу с использованием уязвимости, начиная с уже вышедшего обновления KB4056890.
Улучшения браузеров мешают JS-коду получить точное время для измерения времени выполнени
участков кода. Это, к сожалению, не закрывает полностью атаку Spectre, потому что изобретательный взломщик может найти способ измерять время другими методами. Поэтому Spectre, на эмблеме которого изображено привидение, будет ещё долго появляться и пугать нас.
Небольшое обновление: в выпущенной сегодня (26.01.2017) версии Visual Studio компилято
C++ включает новый ключ /Qspectre, который защищает скомпилированные программы от утечки данных через Spectre. Официальное объявление здесь.)
Литература:
Project Zero, Reading privileged memory with a side-channel
Wikipedia: Meltdown
Geektimes: Новогодние подарки, часть первая: Meltdown
Отдельное спасибо участникам @PashaPash и @Arhad, обсуждение с ними очень помогло прояснить общую картину и яснее понять проблематику.
Ответ 2
В чём она заключается
Внимание: текст ниже является донельзя утрированным и однобоким описанием работ
процессора, в котором опущены многие важные детали. Однако это вынужденная мера для упрощения понимания сути атаки.
1. Многопоточность там, где её не ждут
Все относительно современные процессоры выполняют данные не последовательно, команд
за командой, а параллельно, кучей команд за раз. Это называется суперскалярная архитектура.
Однако любой параллелизм ломается ветвлениями (условиями, циклами и заходами в функции)
Процессор не телепат, а потому не знает, куда именно пойдёт выполнение на очередно
развилке. Поэтому он отслеживает, куда выполнение идёт чаще всего, и упреждающе помещает в параллельное исполнение команды именно этой ветки. Это называется спекулятивное исполнение.
Но процессор может и ошибаться, а потому все операции с регистрами и памятью в это
ветке перенаправляются во временное хранилище, так называемые временные регистры, содержимое которых записывается по назначению только при удачном переходе по ожидаемой ветке. В противном случае их содержимое отбрасывается.
Иными словами, весь выполняемый на упреждение код как бы сидит в «песочнице». И во
тут инженеры Intel допустили оплошность с целью оптимизации. Мол, если запись при упреждающе
выполнении невозможна до известного процессору момента, то и все проверки безопасности можно отсрочить до того же момента — всё равно в случае чего данные будут бесследно отброшены.
Если бы инженеры знали, как они жестоко просчитались насчёт понятия «бесследно»
Да, временные регистры абсолютно изолированы. Но у инструкций, выполняемых на опережение, имеется доступ не только к регистрам, но и к такой долгоживущей штуке, как кэш.
2. Сага о кэше
Кэш — это место, куда процессор временно складывает копии фрагментов оперативно
памяти. Зачем он это делает? Всё просто. Оперативная память — штука крайне медленная
требующая сотни тактов на чтение одного байта. Потеря сотни тактов на каждый чих — непозволительна
роскошь, да и из всей оперативной памяти программе, как правило, требуется совсем немного. Поэтому всю память условно разбивают на череду блоков по 64 байта каждый, а процессор снабжают небольшим (32КБ) количеством сверхбыстрой памяти, в которую он копирует те из блоков, что пытается читать программа. Блочность используется в предположении, что программа будет не скакать по оперативной памяти, а располагать данные компактно.
Вроде бы всё понятно и очевидно. Ах, да, кэш можно выборочно (поблочно) очищать дл
возможности получения наисвежайших данных из оперативной памяти. И ещё, если нужного блока в кэше ещё (либо уже) нет, команда-инициатор обращения приостанавливается и ждёт свои сотни тактов, пока блок не будет готов.
3. В гостях у проктолога, или используем стандартные средства нестандартным способом
Вы ещё не заметили ничего необычного? Значит вы не хакер (в исконно-благородном смысл
этого слова). Ведь если принять каждый блок в кэше за один бит, а его пустоту/полнот
за ноль и единицу соответственно, то мы получим прекрасное хранилище данных. Кратковременное, конечно, — до близжайшего массового сброса переключением задач, — да и объём подкачал. Но всё равно оно прекрасно.
Вы спросите: чем? Да тем, что к этому «хранилищу» имеют доступ инструкции из опережающег
выполнения, упомянутого в начале (далее ОП)! То есть получается следующая воистину гениальная картина:
ОП, с одной стороны, имеют неподконтрольный и неограниченный доступ ко всей оперативно
памяти (включая память ядра ОС, лежащую в верхнем диапазоне адресов). Ведь, как был
сказано выше, все проверки будут выполнены позднее, когда настанет черёд проверки взятия условного перехода и записи изменений в регистры и память. А пока за нами никто не следит, мы с ОЗУ наедине — спасибо вам, инженера Intel!
С другой стороны, ОП имеют доступ к кэшу. Записать туда, конечно, ничего нельзя (эт
кэш чтения, а не записи), но можно сделать из него вышеописанное «хранилище» (заране
сбросив его) и производить чтение из определённых блоков памяти, отправляя их в кэш (мы условились, что это запись единичных бит в хранилище). С кэшем мы тоже наедине — ведь выполнение опережающее, а потому нас пока как бы и нет. То, что нас потом, возможно, отбросят, неважно — мы уже успеем поиграть с кэшем.
А на стыке этих двух вещей рождается поистине грандиозная атака по побочным каналам
позволяющая втихаря читать любую память, включая привелегированную, и отправлять её содержимое наружу. Осталось решить только две проблемы:
как впоследствии читать из такого «хранилища»,
как быть с посланным вдогонку прерыванием о нарушении страничной защиты; ведь неизбежн
настанет момент сохранения временных результатов, при котором и вскроется наше переступление границ памяти.
4. Практическое использование
Первая проблема решается достаточно легко. Для этого достаточно пробежаться по все
той памяти, которой соответствуют использованные для «хранилища» записи кэша. Те блоки
которые не были тронуты (то есть как бы обозначают ноль) будут считываться значительн
медленнее (помним про сотни тактов?), чем те, которые были принудительно предзагружены (то есть обозначают единицу). Если время доступа вообще неприличное, значит у нашего процесса истекло время исполнения и его вытеснили другим — повторяем запрос. Ну а точное время выполнения команды можно узнать у самого процессора с точностью до такта, благо есть соответствующая ассемблерная команда.
А вот у второй проблемы есть целых два решения, которые и известны как те самые Meltdown и Spectre:
Можно вообще плюнуть на исключение, благо степень параллелизма у современных процессоро
большой, и мы спокойно (если повезёт) успеем прошерстить блоки памяти и восстановить переданное число. Это Meltdown.
Можно обмануть процессор, пустив выполнение по неожиданной для него условной ветви
Тогда исключения не возникнет в принципе, так как всё исполнение будет ошибочно-излишним. Ну а про побочный эффект где-то в районе кэша никто никогда и не узнает. Это Spectre.
Вот и вся суть обеих нашумевших уязвимостей. Кстати, это не гипотетические изыскания
а практичные вещи, с использованием которых исследователи спокойно копировали ядерную память стабильным потоком в пару килобайт в секунду.
Как защититься
Если вы пользователь — то никак. Уязвимость слишком низкоуровневая.
Если вы разработчик программ или операционных систем — сбрасывайте участки кэш-памяти с чувствительными данными перед передачей управления стороннему коду.
Если вы разработчик микрокода в компании Intel — перенесите все проверки правомерност
доступа с этапа применения команд на этап опережающего исполнения, как уже давно сделано у AMD. И не тяните с выпуском обновления микрокода.
Ответ 3
Найдено на самом деле две разных уязвимости с похожим механизмом но с совершенн
разными последствиями:
Meltdown - Rogue Data Cache Load
Подвержены Intel и некоторые из ARM.
Позволяет читать память ядра из юзермода. Прикрывается только патчем на OS, которы
перестает мапить память ядра, что бьет по производительности (и вызывает промахи кэша при системных вызовах).
Позволяет процессу без ограничений читать память других процессов.
Позволяет коду в одной виртуальной машине читать память хоста и других виртуальных машин.
Последнее - и есть основная проблема. Затронуты все хостеры, включая Amazon и Azure.
Иcправления:
Windows KB4056892
Linux - ядра 4.14.11, 4.9.75 и будущее 4.15.
MacOS - 10.13.2/10.13.3
Никакое "отключение JS" не помогает. Апдейта микрокода не будет. Единственный вариан
- патч на операционку, который достаточно сильно бьет по производительности на IO-intensive workload (сеть и базы данных). В играх - никакой разницы.
Долгосрочные последствия: Интел выпустит новые исправленные процессоры, на 30% быстрее
которые раскупят намного быстрее, чем 8-е поколение. Хостеры, предоставляющие VM на исправленных процессорах, поднимут цены.
Spectre - Bounds Check Bypass + Branch Target Injection
Подвержены Intel, AMD и ARM.
Позволяет обойти проверки вида if (x < array1_size) y = array1[x]; при доступе
массиву в рамках одного процесса, выполняя код в том же процессе.
Основная проблема:
позволяет коду на JavaScript обойти защиту и читать всю память процесса-браузера.
Исправления ожидаются со стороны уязвимых приложений:
Firefox v57.
Хром v64.
Edge и Internet Explorer - KB4056890.
Исправления браузеров никак не отключают сам Branch Prediсtion. Они снижают точност
performance.now и временно отключают SharedArrayBuffer (был использован как таймер авторами атаки для Chrome). И, наверное, переписывают подверженные уязвимости проверки.
Долгосрочные последствия: Пострадают, прежде всего, разработчики браузеров. Всем
кто запускает в своем процессе сторонний код, придется очень аккуратно бороться с возможностью прочитать память всего процесса.
Задача: Написать код минимально возможной длины, выводящий полученную на вход, строк
из цифр большим, символьным шрифтом. Шрифт должен быть в точности такой, как указан тут, в вопросе.
Пример:
На вход получена строка "0123456789", на стандартный вывод программа должна вывести:
### # ##### ##### # ####### ##### ####### ##### #####
# # ## # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # #
# # # ##### ##### # # ###### ###### # ##### ######
# # # # # ####### # # # # # # #
# # # # # # # # # # # # # # # #
### ##### ####### ##### # ##### ##### # ##### #####
Правила и ограничения:
Программа может, но не обязана быть оформленной в виде функции. Если она функци
- синтаксис объявления этой функции (int main() {} для C) не учитывается в размере, важен размер самого рабочего кода.
Входная строка может поступать в программу любым, удобным вам, способом: в виде переменной
указанной в тестовом примере непосредственно перед кодом, в виде параметра функции или со стандартного ввода.
Входная строка может содержать только цифры
Результат должен быть выведен на стандартный вывод в текстовом виде. Если стандартны
вывод направлен на терминал, экран считать достаточной ширины для вывода всего контрольног
примера c запасом. Переход на новую строку обозначайте (явно или не явно) любым символом/комбинацией символов, использующейся для перевода строки на вашей платформе (Например, \n или \r\n).
Шрифт результата должен в точности соответствовать указанному выше. Между цифрам
на выводе должен быть минимум один пробел (цифры не должны сливаться). Шрифт считается моноширинным, т.е. вокруг единицы может быть больше пустого пространства т.к. ее изображение ýже остальных цифр.
В программе запрещено использовать любые встроенные в язык и библиотеки к нему функции сжатия и кодирования данных (Такие как: zip/unzip, base64)
Программа должна содержать шрифт (или код его формирующий) непосредственно в свое
теле. Получать шрифт из внешних источников (ввод, диск, сеть, память видеоадаптера, bios) запрещено.
Размер программы учитывается в байтах. Побеждает программа имеющая минимальный размер
Конкурс окончен
Первое место занимает @RusArt с ответом на 05AB1E, длиной всего 91 "байт".
Втрое место занимает @Anton Petrusevich с ответом на perl, длиной 150 байт.
И третье место достается @retorta с ответом на python, длиной 161 байт.
В ответах рассмотрены самые разные способы сжатия шрифта. При подготовке к конкурс
я рассматривал большинство из них. Самым простым для реализации и в то же время достаточн
эффективным оказалось сжатие до 70 байт в 7 битной, горизонтальной, кодировке (оригинально
моя кодировка представлена в ответе на postgresql. Даже удалось попасть в диапазон допустимы
символов, поменяв 6-7 биты и вычтя 2. Правда не все участники заморачивались и тратил
драгоценные байты на кодирование в диапазон печатных символов с 0x20 до 0x7E. Применяли кодирование как есть, часто с 8 битом или залезая вообще в диапазон управляющих. В принципе такое кодирование имеет право на жизнь, программы на той платформе, где писались работают. Хотя мне и не очень нравится когда программу нельзя напечатать на принтере, ввести по новой с листа и что бы при этом она продолжила работать (Вы не сможете опубликовать свой код в книге ;) ).
Победителем была использована совершенно другая кодировка шрифта. Словарь из 16 возможны
7и символьных элементов шрифта (в битовом кодировании) и кодовая таблица с номерам
частей для каждого символа, которая благодаря нестандартной кодовой странице языка 05AB1
заняла 35 "байт" (технически в этом языке используются 256 графем, которые принято считат
"байтами" потому что их именно 256 и если бы реально существовала такая кодовая таблица, то их все действительно можно было бы закодировать одним байтом). К сожалению подобное кодирование в других языках невозможно в принципе, в связи с тем, что из 256 значений байта в кодировке ASCII 31 используется для управляющих кодов, а 128 старших значений плохо переносятся между платформами.
Подобный подход со словарем в принципе был использован еще в нескольких ответах
но там не применялось бинарное кодирование в результате чего шрифт занимает горазд
больше места. В процессе подготовки конкурса я рассматривал даже несколько варианто
кодирования со словарем. Например, две комбинации ##### и # #, встречающиеся очен
часто, предполагалось кодировать двумя битами со значениями 10 и 11, а остальные варианты 5и битным значением, первый бит которого 0, что бы отличить от первых двух и остальные 4 бита номер варианта. На практике же это давало совсем небольшой выигрыш в кодировании шрифта и при этом код декодирования оказывался слишком сложным и в код-гольфе неприменимым и опять же в обычном ascii коде уложить это очень сложно.
В ответах можно найти и совершенно иные способы кодирования, например, кодирование повторов # и пробелов просто количествами подряд, представленном в ответе @Qwertiy
К сожалению никто из участников не пробовал использовать вертикальное кодирование
т.е. где элементом выступает 7 бит кодируемого символа берущихся из него по вертикали
А при таком способе кодирования получается очень много повторов. Присмотритесь к цифра
5689 у них отличаются только 1 и последняя вертикаль, остальные одинаковы. При кодировании повторов в такой строке удается достичь практически такого же сжатия как и в случае с словарем и битовым кодированием. Но на практике гольфа опять же слабо применимое из за роста объема декодирующего кода.
Альтернативный рейтинг
К сожалению не удалось сформулировать правила так, что бы они действительно отражал
то, чего я хотел от конкурса. Во многих ответах используются символы не работающие
другой кодировке символов или функции близкие по смыслу к base64, т.е. представляющие закодированную строку как одно большое число, в результате чего в коде отсутствует собственноручное ее декодирование. В данном альтернативном рейтинге я отражаю результаты, как бы они выглядели, если бы правила были точно сформулированы.
Anton Petrusevich, ответ на perl, длиной 150 байт
Андрей, ответ на C#, длиной 176 байт
Visman, ответ на PHP, длиной 235 байт
Пожалуйста, указывайте в ответе количество байт, чтобы проще было выявить победителя.
execute("ru.stackoverflow.com", "674415");
.cssload-container,.cssload-cube{width:97px;height:97px;transform-style:preserve-3d}.cssload-container,.cssload-cube,.cssload-half1,.cssload-half2{transform-style:preserve-3d}.cssload-container{position:relative;margin:23p
84px;perspective:292px}.cssload-cube{animation:cube 11.5s forwards infinite;transform-origin:cente
49px}.cssload-half1,.cssload-s1{top:0;transform-origin:50% 100%}.cssload-half1{height:39px;position:absolute;animation:half-fol
11.5s forwards infinite}.cssload-side{width:19px;height:19px;background:#ddd;position:absolute}.cssload-s1{left:39px;animation:s1an
11.5s forwards infinite}.cssload-s2,.cssload-s3,.cssload-s4{left:39px;transform-origin:50
0}.cssload-s2{top:19px;animation:s2ani 11.5s forwards infinite}.cssload-s3{top:39px;animation:s3an
11.5s forwards infinite}.cssload-s4{top:58px;animation:s4ani 11.5s forwards infinite}.cssload-s5{left:19px;top:19px;transform-origin:100
50%;animation:s5ani 11.5s forwards infinite}.cssload-s6{left:58px;top:39px;transform-origin:
50%;animation:s6ani 11.5s forwards infinite}@keyframes cube{0%,30%{transform:rotateX(0)}40%{transform:rotateX(45deg
rotateY(0) rotate(45deg)}60%{transform:rotateX(60deg) rotateY(0) rotate(45deg)}65%,70%{transform:rotateX(60deg
rotate(45deg) rotate(180deg)}75%,80%{transform:rotateX(60deg) rotate(45deg) rotate(1turn)}90%{transform:rotateX(0
rotate(0) rotate(0)}}@keyframes s1ani{0%{opacity:1;transform:translateY(0);background:#ddd}40%{transform:rotateX(0);background:#ddd}50%{transform:rotateX(-90deg);background:#ddd}90%{transform:rotateX(-90deg)}}@keyframes s2ani{0%{opacity:0;transform:rotateX(-179deg)}10%{opacity:1;transform:rotateX(0)}40%{background:#ddd}45%,80%{background:#b4b4b4}65%{opacity:1;background:#b4b4b4}90%{opacity:1}to{opacity:0}}@keyframes s3ani{0%,10%{opacity:0;transform:rotateX(-179deg)}20%,90%{opacity:1;transform:rotateX(0)}40%{background:#ddd}45%{background:#969696}to{opacity:0}}@keyframes s4ani{0%,20%{opacity:0;transform:rotateX(-179deg)}10%,to{opacity:0}30%{opacity:1;transform:rotateX(0)}40%{transform:rotateX(0);background:#ddd}50%{transform:rotateX(90deg);background:#b4b4b4}80%{background:#b4b4b4}90%{opacity:1;transform:rotateX(90deg)}}@keyframes s5ani{0%,10%{opacity:0;transform:rotateY(-179deg)}20%{opacity:1;background:#ddd;transform:rotateY(0)}40%{transform:rotateY(0)}50%{transform:rotateY(90deg)}55%{background:#ddd}60%{background:#c8c8c8}90%{transform:rotateY(90deg);opacity:1}to{opacity:0}}@keyframes s6ani{0%,20%{opacity:0;transform:rotateY(179deg)}30%{opacity:1;transform:rotateY(0)}40%{transform:rotateY(0)}50%{transform:rotateY(-90deg);background:#ddd}60%,80%{background:#c8c8c8}90%{opacity:1;transform:rotateY(-90deg)}to{opacity:0}}@keyframes half-fold{0%,50%{transform:rotateX(0)}60%,90%{transform:rotateX(-90deg)}}
Python3, 161 байт
def f(s):
for i in'0123456':print(*(format(b'>>@>>>"AAB@ABAAA(B@@AAA>>B~~>?A@AA"@AAAAA>>>>>>'[int(i+j)],'7b').translate({48:' ',49:'#'})for j in s))
SO режет некоторые символы, так что код скопированный отсюда не будет работать, но если скопировать отсюда, то всё работает.
Проверка
Ответ 3
Perl, 230 165 155 153 150 символов и байт.
Вся программа в ASCII, поэтому, число байт, занимамых программой, равно числу символов. Символы шрифта кодированы простой формулой, чтобы оставались печатными.
$_="0987654321";
for$l(0..6){say map{sprintf("%08b",127&(80+ord substr'L8nnp/n/nnRHqqrpqrqqqX11rpp4qqq8nnr..8noq8p1/1q@q1R8pq2qq@qqLn/n2nn@nn',$l.$_))=~y/01/ #/r}/./g}
Ideone: https://ideone.com/gbBuzn
Комментарий: код эволюционировал, приведён последний вариант. С подсказками Mike
Можно ужать ещё, наверное, пару символов, если формулу взять подсказанную, но тогда совсем моего авторства не останется :)
Update: минус ещё два символа за счёт того, что ord берёт код первого символа строки. Спасибо Alexander Onokhov.
Update: минус ещё три символа за счёт переупорядочивания данных шрифта и замены умножения на слияние строк. Идея "Someone Unknown".
PHP7.0, 211 189 182 символов
@Mike уточнил в комментарии, что base64_decode() под запретом. Это тот же вариан
на 252 символа, но строка со шрифтом представлена символами ASCII с кодами от 0 до 254, а функция base64_decode() удалена. Код не может быть нормально отображен в кодировке UTF-8
Ответ 7
PHP, 214 символов и байт
function d($s){
$c='>>@>>>"AAB@ABAAA(B@@AAA>>B~~>?A@AA"@AAAAA>>>>>>';
for($l=0;$l<7;$l++){
for($d=0;$d>@>>>"AAB@ABAAA(B@@AAA>>B~~>?A@AA"@AAAAA>>>>>>';for($l=0;$l<7;$l++){for($d=0;$d
Ответ 8
Delphi, 407 символов
Запишу пока что, потом подумаю еще. На C/C++, конечно, короче будет, хотя и над этим еще надо подумать, как ужать.
program numbers;
{$APPTYPE CONSOLE}
uses SysUtils, strUtils;
const a: array[0..6] of string = (
'1C083E3E407F3E7F3E3E',
'22184141424040424141',
'41280101424040044141',
'41083E3E427E7E083E3F',
'410840017F0141104101',
'22084041024141104141',
'1C3E7F3E023E3E103E3E');
const s = '1234567890';
var r,b: byte;
c: char;
x : word;
begin
for r:=0 to 6 do begin
for c in s do begin
x := strToInt('$'+copy(a[r],1+StrToInt(c)*2,2));
for b := 6 downto 0 do
write(ifthen(x and (1 shl b)>=1, '#', ' '));
write(' ');
end;
writeln;
end;
end.
зы: как длину то считаете
Ответ 9
Crystal 0.22, 250 байт
def foo(s)
7.times{|i|s.each_byte{|b|8.times{|j|print (1&[0x1C22414141221C,0x3E0808080A0C08,0x7F01013E40413E,0x3E41403E40413E,0x20207F21212101,0x3E41403F01017F,0x3E41413F01413E,0x404040810217F,0x3E41413E41413E,0x3E41407E41413E][b-48]>>8*i+j)>0?"#": " "}};puts}
end
foo("0123456789");
https://play.crystal-lang.org/#/r/24o0
Основано на коде https://ru.stackoverflow.com/a/674586/253020
Ответ 10
C#, 313 305 304 302 299 281 279 278 277 276 275 261 259 258 252 250 байтов
void f(string s){
for(int i=-1,j;++i<7;WriteLine())foreach(var c in s)for(Write(" "),j=0;j<7;)Write((new[]{-0x88F7EF9FCFA2,-0x1CB86CD9AB6,1056705L<<28,0,-0x790413A7D03D,0x102041,16449<<14,-0xE9EBC6CC0FBF,16385<<14,8193<<14}[c-48]+0xfa0c07d020be>>i*7+j++&1)>0?"#":" ");
}
ЗЫ. using static System.Console; - считаем как подключение стандартной библиотеки
В немного более читабельном виде:
using static System.Console;
class Program
{
static void Main()
{
ShowNumber("0123");
ReadLine();
}
static void ShowNumber(string s)
{
for (int i = -1, j; ++i < 7; WriteLine())
foreach (var c in s)
for (Write(" "), j = 0; j < 7;)
Write((new[] {
-0x88F7EF9FCFA2,
-0x1CB86CD9AB6,
1056705L<<28,
0,
-0x790413A7D03D,
0x102041,
16449<<14,
-0xE9EBC6CC0FBF,
16385<<14,
8193<<14
}[c - 48] + 0xfa0c07d020be >> i * 7 + j++ & 1) > 0 ? "#" : " ");
}
}
Перенос объявления массива внутрь Write позволил выиграть 7 байт
Перевод магических чисел в шестнадцатеричную систему позволил выиграть 8 байт
Перевод '0' в 48 позволил выиграть еще один байт
Чтение этого позволило избавиться от пары скобок и выиграть 2 байта
Перенос вывода разделителей между цифрами в Write позволил уменьшить магические числа и выиграть 3 байта
Убрал из подсчета заголовок функции
Перенос WriteLine() внутрь заголовка for() позволил выкинуть пару скобок {} и выиграть 2 байта (подсмотрено в этом ответе)
Замена операции сравнения == на < дает еще байт
Вынос объявления переменных перед циклами позволил выиграть еще байт
Перенос инкремента j++ в тело цикла позволил выиграть еще байт
Перенос инкремента ++i в предусловие позволил выиграть еще байт
Вынос постоянного слагаемого позволяет выиграть невероятные 14 байт (возможно выбор другого слагаемого может улучшить результат еще на 1-2 байта, но лень выбирать)
Вынос вывода разделителя между цифрами в заголовок цикла позволил выиграть еще 2 байта
Рекомендация @Qwertiy даёт еще один байт
Формирование магических чисел с помощью сдвигов дает еще 6 байт
Удалил лишнюю пару скобок (не понятно откуда она взялась) - 2 байта
Perl, -132 байта (вне конкурса, использует функцию кодирования)
укороченный вариант решения от Anton Petrusevich
$_="0987654321";
for$l(0..6){say map{(unpack'(B8)*','>>@>>>"AAB@ABAAA(B@@AAA>>B~~>?A@AA"@AAAAA>>>>>>')[$l.$_]=~y/01/ #/r}/./g}
Использованы непечатные символы ASCII, поэтому копипаст отсюда работать не будет. На ideone можно увидеть рабочий пример. Пятая строка длиной 132 байта.
Ответ 16
Javascript ES6, 382 309 276 271 259 258 символов
Входные данные в переменной s.
eval("[[56b=16c=124cd=128g=254cgcc],[68 48e=130ef=132defee],[e80a=2afdd8ee],[ebccfh=252hbc126],[ebdagae32ea],[68bde4ee32ee],[56cgc4cc32cc]]"[R='replace'](/\d+|\w(?!=)/g,'$&,')).map(x=>s[R](/./g,m=>(256+x[m]).toString(2)[R](/./g,b=>" #"[b]).slice(1))).join`
`
Проверка
t = s => eval("[[56b=16c=124cd=128g=254cgcc],[68 48e=130ef=132defee],[e80a=2afdd8ee],[ebccfh=252hbc126],[ebdagae32ea],[68bde4ee32ee],[56cgc4cc32cc]]"[R='replace'](/\d+|\w(?!=)/g,'$&,')).map(x=>s[R](/./g,m=>(256+x[m]).toString(2)[R](/./g,b=>" #"[b]).slice(1))).join`
`
document.querySelector("input").addEventListener("input", function (e) {
document.querySelector("pre").textContent = t(e.target.value.replace(/\D/g, ''));
})
body { display:inline-block; }
input { position:sticky; left:8px; width:calc(100vw - 16px); box-sizing:border-box; }
Ответ 17
C#, 248 194 193 188 187 177 176 байтов
Другой подход с упаковкой цифр не в long-константы, а в строку с символами, т. е., по сути, в число в 128-ричной системе:
static void f(string s)
{
for(int i=-5,j;++i<3;WriteLine())foreach(var c in s)for(j=0;j<8;)Write(" #"[("~r``k!`!``Ln++Kk+K+++t**Kkkz+++r``Kaar` +rk*!*+f+*Lrk+J++f++~`!`J``f``"[i*10+c-8]-6^100)>>j++&1]);
}
Использует все наработки из моего предыдущего ответа + задействована хотелка из комментария.
В более читабельной форме:
using static System.Console;
class Program
{
static void Main()
{
ShowNumber2("012345678");
ReadLine();
}
static void ShowNumber2(string s)
{
for (int i = -5, j; ++i < 3; WriteLine())
foreach (var c in s)
for (j = 0; j < 8;)
Write(" #"[
(
"~r``k!`!``Ln++Kk+K+++t**Kkkz+++r``Kaar` +rk*!*+f+*Lrk+J++f++~`!`J``f``"
[i * 10 + c - 8] - 6 ^ 100
) >> j++ & 1
]);
}
Проверка: http://ideone.com/xhiOgv
Невероятно, но факт - один маленький XOR позволяет выкинуть из строки кучу управляющих последовательностей и выиграть ~50 байт!
Рекомендация @Qwertiy даёт еще один байт
Замена x^0b111...111 на ~x приносит 5 байтов и третье место в рейтинге
Рекомендация @Mike дает еще один байт
Благодаря помощи @Mike по подбору значения для XOR удалось выиграть еще 10 байт
Еще одна хитрость приносит байт
Ответ 18
Ruby and Crystal -161b
Вне конкурса вариант Антона (+Майка), просто чтобы показать синтаксис Ruby и Crystal, тут у них совпало.
v="0987654321"
7.times{|l|puts v.chars.map{|c|("%08b"% (127&(80+"LRqqqRL8HX888nnq1npp/nq1n1qnprrr/22/pp.1qnnqp.qqn/r48@@@nqqnqqnnqqo1qn"[c.to_i*7+l].ord))).tr("01"," #")}.join}
http://ideone.com/XvoUPZ
Ответ 19
PostrgeSQL (8.4+), 284 байта
select string_agg(translate((ascii(substr(s,c::int+l::int*10+1,1))-2#96)::bit(8)::text,'01',' #'),' ')
from (values('~j``"!`!``Dz##$"#$###Jcc$""f###j``$ j`a#j"c!c#r#cDj"#d##r##~`!`d``r``')) B(s),
regexp_split_to_table('0123456','') l,
regexp_split_to_table('9876543210','') c
group by l
order by l
Число которое надо вывести задается в 4й строке. В примере на sqlfiddle.com пришлос
заменить пробелы на подчеркивания, потому что там вывод на экран происходит в HTML (соседние пробелы подавляются), но разглядеть можно. На экране в pgAdmin (утилита из комплекта поставки postrgesql) выглядит замечательно.
Ответ 20
F#, 496 байт
let p (s:string)=
let f o v = String([|for i=(56-(o*8)) downto (56-(o*8)-7) do yield if (if i>32 then((v>>>30)&&&(1L<<<(i-31)))<>0L else(v&&&(1L <<< i-1))<>0L)then '#' else ' '|])
printfn "%s"(List.fold(fun a e-> a+(Array.fold(fun b v->b+(f e ([0x38448282824438L;0x1030501010107CL;0x7C82027C8080FEL;0x7C82027C02827CL;0x80848484FE0404L;0xFE8080FC02827CL;0x7C8280FC82827CL;0xFE840810202020L;0x7C82827C82827CL;0x7C82827E02827CL].[(int v - int '0')])))""(s.ToCharArray()))+"\n")"" [0..6])
Тест: https://ideone.com/i0hTW0
Аналог кода на Delphi из предыдущего ответа переписанный на php.
PHP, 285 символов
читабельный вариант:
$s = "1234567890";
$a=['1C083E3E407F3E7F3E3E','22184141424040424141','41280101424040044141','41083E3E427E7E083E3F','410840017F0141104101','22084041024141104141','1C3E7F3E023E3E103E3E'];
foreach($a as $l){
for($i=0; $i=0; $b--) echo hexdec(substr($l,$i*2,2))&1<<$b?'#':' ';
?> =0;$b--)echo hexdec(substr($l,$i*2,2))&1<<$b?'#':' ';?>
Ответ 25
Python 3, 244 байта (без учёта первых двух строк)
s=input()
from re import sub
for r in range(7):print(*(eval(sub('(.)'*2,r'+" "*0x\1+"#"*\2',hex(int('3BKFT57KVYH9NKP5RWAHYETF85SH2KMQ1BKLL2CVX0HPWX1W2WBGPNN1SHAZPXK7JS0JP0V493SO5PSD1JDSIOB1VMCY67RQ2N7X94FSBVDX9MV0UCUVVSK5GR0DHCG',36)))[15:])[int(d)*7+r*70:][:7]for d in s))
Ответ 26
C#, 252 байта
Весь код:
http://ideone.com/SzOJCG
Только функция:
int i=0,j;for(;i<70;i+=10){foreach(var c in s)for(j=0;j<8;j++)Write(new BitArray(new[]{(int)"\x1\x4\x8\x10\x14\x18\x1
!8>?@AD|~\x7f\uc282"["JDKKARKRKKOFNNIANINNSEMMIAADNNSDKKILLCKQSDAMRMNBNMODANHNNBNNJPRKHKKBKK"[i+c-48]-65]})[j]?'#':' ');WriteLine();}
Ответ 27
Kotlin, 233 234 243 268, зато без непечатных символов :)
(0..6).map{c->s.map{(BigInteger("cq7ntkqmgle8bf3logj0a8x7uwoe4u3x2dpfv5x5hvwz7z25fob8oanyd2cs7xt99qeaoj1bnj3jw1guzhbzknwgyi7cdz5",36).toString(2).replace('0','4').drop(c*70+it.toInt()*7-336).take(7)+"1").map{print(it-17)}};println()}
Развернуто
import java.math.BigInteger
fun main(args: Array) {
val s = "110123456789"
(0..6).map{c->
s.map{(BigInteger("cq7ntkqmgle8bf3logj0a8x7uwoe4u3x2dpfv5x5hvwz7z25fob8oanyd2cs7xt99qeaoj1bnj3jw1guzhbzknwgyi7cdz5",36)
.toString(2)
.replace('0','4')
.drop(c*70+it.toInt()*7-336)
.take(7)+"1"
)
.map{print(it-17)}
}
println()
}
}
Попробовать можно тут, просто скопировать туда целиком развернутый код