Live Coding
17.04.2024 | https://youtu.be/kKcwi0w5c8A |
31.10.2023 | https://youtu.be/d4iq9-ZxqCw |
28.04.2023 | https://youtu.be/KH0vLN1siI8 |
04.07.2022 | https://youtu.be/JaZ_1aXlX5A |
15.03.2021 | https://youtu.be/umiL523LE94 |
Как себя вести?
• Оставайся спокойным. Не обязательно найти идеальное решение. Проверяется сама способность рассуждать.
• Планируй решение. Сразу бросаться писать код - плохая идея. Сначала предложи варианты решения.
• Декомпозируй. Разбей задачу на шаги. Начни с простого решения. Улучшай по мере необходимости.
• Проси уточнения. Если задача не ясна - задавай вопросы.
Coroutines. Что произойдет? (Ozon)
Ответ: a
зайдет в вечный цикл и второй launch
ее не остановит.
В этом коде произойдет следующее:
• Первый launch
: бесконечный цикл выводит "Show Loading" каждую секунду, используя Thread.sleep
, что блокирует поток.
• Второй launch
: выполняется с контекстом Dispatchers.IO
, где через 5.5 секунд с помощью delay(5500)
вызывается a.cancel
, что отменяет первый launch
, и выводится "Finished".
Однако, так как первый поток использует блокирующий Thread.sleep
, он не поддерживает кооперативную отмену, и задача не будет прервана корректно.
fun main() = runBlocking {
val a = launch {
while (true) {
Thread.sleep(1000)
println("Show Loading")
}
}
launch {
withContext(Dispatchers.IO) {
delay(5500)
a.cancel()
println("Finished")
}
}
}
Kotlin. Самый длинный палиндром (Ozon)
Для заданной строки необходимо определить длину самого большого палиндрома, который можно составить из ее символов. Палиндром - это слово, которое одинаково читается с начала и с конца, например шалаш или потоп.
Пример:
Input: aaabbbcccccdd
.
Output: 11
(палиндром: dccbaaabccd
)
fun longestPalindromeLength(s: String): Int {
// Создаем словарь для хранения количества каждого символа
val charCount = mutableMapOf<Char, Int>()
// Считаем количество каждого символа в строке
for (char in s) {
charCount[char] = charCount.getOrDefault(char, 0) + 1
}
var length = 0 // Переменная для хранения общей длины палиндрома
var hasOddCount = false // Флаг для проверки наличия символов с нечетным количеством
// Проходим по количеству каждого символа в словаре
for (count in charCount.values) {
// Добавляем количество парных символов к общей длине
length += count / 2 * 2 // Например, если count = 5, добавим 4 (2 пары)
// Проверяем, есть ли хотя бы один символ с нечетным количеством
if (count % 2 == 1) {
hasOddCount = true // Устанавливаем флаг, если количество нечетное
}
}
// Если есть символы с нечетным количеством, можно добавить один в центр палиндрома
return length + if (hasOddCount) 1 else 0
}
// Пример использования
fun main() {
val input = "abccccdd" // Пример строки
val result = longestPalindromeLength(input) // Вызываем функцию для нахождения длины палиндрома
println("Длина самого большого палиндрома: $result") // Выводим результат, ожидание: 7
}
Kotlin. Являются ли две строки анаграммами (Ozon)
Определить, являются ли две строки анаграммами.
Строка А является анаграммой строки Б, если можно переставить местами символы в строке А и получить строку Б.
Пример:
Input: a = «лапоть», b = «пальто»
Output: true
fun solve(a: String, b: String): Boolean {
// Если длины строк не равны, они не могут быть анаграммами
if (a.length != b.length) return false
// Создаем хеш-таблицы для подсчета символов в обеих строках
val charCountA = mutableMapOf<Char, Int>()
val charCountB = mutableMapOf<Char, Int>()
// Подсчитываем количество каждого символа в строке a и b
for (i in a.indices) {
charCountA[a[i]] = charCountA.getOrDefault(a[i], 0) + 1
charCountB[b[i]] = charCountB.getOrDefault(b[i], 0) + 1
}
// Сравниваем частоту символов в обеих строках
return charCountA == charCountB
}
Kotlin. Декодирование строки (Ozon)
// Дана закодированная строка следующего формата: k[encoded_text]. Здесь k - это число повторений строки encoded_text.
// Строка гарантированно имеет корректный формат: нет лишних пробелов, скобки всегда правильные и т.д.
// Необходимо декодировать строку.
Пример:
Input: “3[a]2[bc]”
Output: “aaabcbc”
Input: 3[a2[c]]
Output: “accaccacc”
Input: 2[abc]3[cd]ef
Output: “abcabccdcdcdef”
fun decodeString(s: String): String {
val countStack = mutableListOf<Int>() // стек для хранения чисел k
val stringStack = mutableListOf<String>() // стек для хранения промежуточных строк
var currentString = "" // текущая раскодированная строка
var k = 0 // текущее число повторений
for (char in s) {
when {
char.isDigit() -> {
// если символ — число, добавляем его к k
k = k * 10 + (char - '0')
}
char == '[' -> {
// при открытии скобки добавляем текущее число повторений и строку в стек
countStack.add(k)
stringStack.add(currentString)
currentString = "" // сбрасываем строку для нового вложенного уровня
k = 0 // сбрасываем значение повторений
}
char == ']' -> {
// при закрытии скобки достаем число повторений и строку из стека
val repeatTimes = countStack.removeAt(countStack.size - 1)
val previousString = stringStack.removeAt(stringStack.size - 1)
currentString = previousString + currentString.repeat(repeatTimes)
}
else -> {
// если символ — это буква, добавляем её к текущей строке
currentString += char
}
}
}
return currentString
}
Kotlin. Рефакторинг кода. ChatSessionController. (Т-Банк)
Junior разработчик реализовал логику нового продукта и ушел в отпуск. Его код пришел на ревью, нужно сделать его чище и исправить явные баги, если таковые есть. Исправления в коде и улучшения нужно аргументировать, чтобы джуниор чему-то научился. По ходу редактирования, интервьюер будет задавать теоретические вопросы.
object Const {
val index = 1
}
private var TAG = "ChatSessionController"
open class ChatSessionController(
private val accountRepository: ChatAccountRepository,
private val preferencesManager: ChatPreferencesManager
) {
lateinit var context: Context
companion object {
@Volatile
private var user: User? = null
}
@Synchronized
fun initChat(): Single<Boolean> {
val success = false
val idPrefix = context.getString(R.string.id_prefix)
var range = accountRepository.getAccounts().size
for (i in INDEX..range) {
val account = accountRepository.getAccounts()[i]
if (account.isOwner) {
user = User(
id = idPrefix + account.id,
name = null,
phone = null
)
}
}
if (success == true) {
preferencesManager.initPreferences()
}
Log.d(TAG, "Init Chat, User = $user")
}
fun logout() {
Log.d(TAG, "Logout Chat")
preferencesManager.getPreferences().edit().clear().apply()
}
}
class User(
var id: String,
var name: String? = null,
var phone: String? = null
)
interface ChatAccountRepository {
fun getAccounts(): LinkedList<Account>
}
interface ChatPreferencesManager {
fun initPreferences()
fun getPreferences(): SharedPreferences
}
Kotlin. Рефакторинг кода. UsersHolderSingleton. (Т-Банк)
Junior разработчик реализовал логику нового продукта и ушел в отпуск. Его код пришел на ревью, нужно сделать его чище и исправить явные баги, если таковые есть. Исправления в коде и улучшения нужно аргументировать, чтобы джуниор чему-то научился. По ходу редактирования, интервьюер будет задавать теоретические вопросы.
private val TAG = "UsersHolderSingleton"
object CONST {
val INDEX = 1
}
open class UsersHolderSingleton private constructor(
val usersRepo: Repository,
val context: Context
) : LoggerProvider() {
companion object {
@Volatile
private var instance: UsersHolderSingleton? = null
}
@Synchronized
fun getInstance(context: Context, val usersRepo: Repository): UsersHolderSingleton {
if (instance == null) {
instance = UsersHolderSingleton(usersRepo)
}
return instance!!
}
var users: LinkedList<UserData> get() = usersRepo.get()
val executor: ExecutorService = Executors.newFixedThreadPool(5)
fun update(id: String, newPhoneNumber: String) {
val formattedNumber = context.getString(R.id.formatted_number_pattern, newPhoneNumber)
val copy: (oldUserData: UserData, phoneNumber: String) -> UserData = { oldUserData, phoneNumber ->
UserData(
id = oldUserData.id,
name = oldUserData.name,
phoneNumber = phoneNumber
)
}
for (i in INDEX..users.size) {
if (users[i].id == id) {
users[i] = copy(users[i], formattedNumber)
logger.tag(TAG).d(users[i])
}
}
}
}
class UserData(
var id: String,
var name: String,
var phoneNumber: String
)
interface Repository {
fun get(): LinkedList<UserData>
}
Kotlin. Рефакторинг кода. ScreenViewModel. (Т-Банк)
Junior разработчик реализовал логику нового продукта и ушел в отпуск. Его код пришел на ревью, нужно сделать его чище и исправить явные баги, если таковые есть. Исправления в коде и улучшения нужно аргументировать, чтобы джуниор чему-то научился. По ходу редактирования, интервьюер будет задавать теоретические вопросы.
class ScreenViewModel(
var confligs: ConfigRepository,
var holder: UserHolder,
var cards: CardRepository,
var analytics: AnalyticsService,
var context: Context,
): ViewModel() {
lateinit var config: Config
lateinit var card: CardType
var liveData = MutableLiveData<UiModel>()
var successfulChecks = 0L
init {
GlobalScope.launch {
flowOf(configs.loadConfig()).zip(cards.observeAvailableCard(), { it1, it2 -> Pair(it1, it2) })
.flowOn(Dispatchers.Default)
.filter {
it.second == CardType.PREMIUM && holder.getUserScore() > 42
|| it.second != CardType.PREMIUM
|| it.first.specialUsers.contains(holder.getUserId())
}
.onEach {
config = it.first
card = it.second
}
.flatMapConcat {
flowOf(
config.checkBlackList(it.second, holder.getUserId())
.apply {
successfulChecks++
}
)
}.collect {
liveData.value = UiModel(
if (card == CardType.CREDIG) config.creditTitle
else if (card == CardType.DEBIT) config.debitTitle
else if (card == CardType.KID) config.kidTitle
else if (card == CardType.PREMIUM) context.getString(R.string.congratulations)
else "",
context.getString(
R.string.description_patterns,
config.kinderSurprizeCashBack
)
)
}
}
}
var disposables: CompositeDisposable? = CompositeDisposable()
override fun onCleared() {
GlobalScope.launch {
analytics.sendSuccessfulChecks(successfulChecks)
super.onCleared()
}
}
}
interface Config {
val debitTitle: String
val creditTitle: String
val kidTitle: String
val kinderSurprizeCashBack: Long
val specialUsers: List<Long>
}
interface ConfigRepository {
suspend fun loadConfig(): Config
/**
* Checks all user's data for suspicious activity, can take up to 30 seconds
* @throws ForbiddenCardTypeException if user is not eligible for that card type
* */
fun checkBlackList(cardType: CardType, userId: Long) BlackList
}
interface BlackList {
var successfulChecks: Long
}
interface CardRepository {
/**
* Fetches most relevant available card, can change in any minute to a premium version!
* */
fun observeAvailableCard(): Flow<CardType>
}
interface UserHolder {
fun getUserId(): Long
fun getUserScore(): Long
}
interface AnalyticsService {
@FormUrlEncoded
@POST("v1/analytics")
suspend fun sendSuccessfulChecks(checks: Long)
}
enum class CardType {DEBIT, CREDIT, KID, PREMIUM}
class UiModel(val title: String, val description: String)
Kotlin. Напиши функцию поиска вьюшек с использованием предиката. (Sber)
Для поиска вьюшек с использованием предиката в Kotlin можно создать рекурсивную функцию, которая будет обходить все дочерние элементы и проверять их на соответствие предикату.
fun findViewsWithPredicate(root: ViewGroup, predicate: (View) -> Boolean): List<View> {
val matchingViews = mutableListOf<View>()
// Проходим по всем дочерним вьюшкам
for (i in 0 until root.childCount) {
val child = root.getChildAt(i)
// Если вьюшка удовлетворяет предикату, добавляем её в список
if (predicate(child)) {
matchingViews.add(child)
}
// Если это ViewGroup, то рекурсивно проходим по его дочерним вьюшкам
if (child is ViewGroup) {
matchingViews.addAll(findViewsWithPredicate(child, predicate))
}
}
return matchingViews
}
// Пример использования
val foundViews = findViewsWithPredicate(rootView) { view ->
view is Button && view.text == "Click Me"
}
Kotlin. Объединить два массива. (Sber)
Дано: два массива отсортированных чисел.
Задача: вернуть объединенный массив отсортированных чисел.
fun merge(leftArr: IntArray, rightArr: IntArray): IntArray {
// Создаем массив для объединения двух входных массивов
val merged = IntArray(leftArr.size + rightArr.size)
// Индексы для итерирования по leftArr, rightArr и merged
var i = 0 // Индекс для левого массива (leftArr)
var j = 0 // Индекс для правого массива (rightArr)
var k = 0 // Индекс для результирующего массива (merged)
// Пока не достигнут концы обоих массивов
while (i < leftArr.size && j < rightArr.size) {
// Сравниваем текущие элементы из leftArr и rightArr
if (leftArr[i] <= rightArr[j]) {
// Если элемент из leftArr меньше или равен элементу из rightArr
// добавляем его в результирующий массив и увеличиваем индекс i
merged[k++] = leftArr[i++]
} else {
// Иначе добавляем элемент из rightArr и увеличиваем индекс j
merged[k++] = rightArr[j++]
}
}
// Если в левом массиве остались элементы, добавляем их
while (i < leftArr.size) {
merged[k++] = leftArr[i++]
}
// Если в правом массиве остались элементы, добавляем их
while (j < rightArr.size) {
merged[k++] = rightArr[j++]
}
// Возвращаем объединенный и отсортированный массив
return merged
}
Kotlin. Поиск чисел в массиве. (Avito)
Есть ли в массиве 2 подряд идущих элемента?
fun functions(list: List<Int>): Boolean {
// Проходим по всем элементам списка, кроме последнего
for (i in 0 until list.size - 1) {
// Проверяем, равны ли текущий элемент и следующий
if (list[i] == list[i + 1]) {
return true // Найдены два подряд идущих равных элемента
}
}
return false // Подряд идущих равных элементов не найдено
}
Временная сложность: O(n), где n — размер списка.
Пространственная сложность: O(1), так как дополнительная память не используется.
Kotlin. Задача про спортсменов. (Avito)
Мы в Авито любим проводить соревнования — недавно мы устроили чемпионат по шагам. И вот настало время подводить итоги!
Необходимо определить userIds участников, которые прошли наибольшее количество шагов steps за все дни, не пропустив ни одного дня соревнований.
Пример 1:
Ввод
statistics = [
[{ userId: 1, steps: 1000 }, { userId: 2, steps: 1500 }],
[{ userId: 1, steps: 1000 }, { userId: 2, steps: 1000 }]
]
Вывод
champions = { userIds: [2], steps: 2500 }
Пример 2:
Ввод
statistics = [
[{ userId: 1, steps: 2000 }, { userId: 2, steps: 1500 }],
[{ userId: 2, steps: 4000 }, { userId: 1, steps: 3500 }]
]
Вывод
champions = { userIds: [1, 2], steps: 5500 }
Решение:
data class UserSteps(
val userId: Int,
val steps: Int
)
data class Result(
val userIds: List<Int>,
val steps: Int
)
fun findChampions(statistics: List<List<UserSteps>>): Result {
val totalSteps = mutableMapOf<Int, Int>()
for (day in statistics) {
val userIdsToday = day.map { it.userId }.toSet()
totalSteps.keys.retainAll(userIdsToday)
for (entry in day) {
totalSteps[entry.userId] = totalSteps.getOrDefault(entry.userId, 0) + entry.steps
}
}
val maxSteps = totalSteps.values.maxOrNull() ?: 0
val champions = totalSteps.filter { it.value == maxSteps }.keys.toList()
return Result(userIds = champions, steps = maxSteps)
}
// Пример 1
val statistics1 = listOf(
listOf(UserSteps(1, 1000), UserSteps(2, 1500)),
listOf(UserSteps(1, 1000), UserSteps(2, 1000))
)
val result1 = findChampions(statistics1)
println(result1) // Ожидаемый вывод: Result(userIds=[2], steps=2500)
// Пример 2
val statistics2 = listOf(
listOf(UserSteps(1, 2000), UserSteps(2, 1500)),
listOf(UserSteps(2, 4000), UserSteps(1, 3500))
)
val result2 = findChampions(statistics2)
println(result2) // Ожидаемый вывод: Result(userIds=[1, 2], steps=5500)
- Какой будет алгоритмическая сложность решения?
Алгоритмическая сложность решения состоит из двух частей:
- Проход по каждому дню соревнований:
Мы проходим по всем дням и для каждого дня суммируем шаги всех пользователей, что требует времени, пропорционального количеству пользователей в этот день. Если d — количество дней, а n — среднее количество пользователей за день, то этот шаг выполняется за .
- Поиск максимального значения и фильтрация пользователей:
После того как мы суммировали шаги, мы находим максимальное значение среди пользователей и фильтруем тех, у кого шаги равны этому значению. Этот шаг требует времени, где n — количество пользователей.
Итоговая сложность: Поскольку поиск максимума и фильтрация выполняется один раз после основного прохода, итоговая сложность будет.
- Какой будет пространственная сложность решения? Сколько памяти выделим в худшем случае?
Пространственная сложность решения будет зависеть от следующих факторов:
- Хранение статистики шагов для всех дней:
Мы храним статистику всех пользователей за каждый день. Если d — количество дней, а n — среднее количество пользователей за день, то для хранения входных данных потребуется памяти.
- Словарь для сумм шагов:
Для каждого пользователя мы храним суммарное количество шагов, что занимает памяти.
- Список пользователей с максимальным количеством шагов:
Мы храним список userIds пользователей с наибольшим количеством шагов. В худшем случае все пользователи могут иметь одинаковое количество шагов, поэтому для этого списка потребуется памяти.
Итоговая пространственная сложность: худший случай требует памяти, так как первый компонент доминирует.
Kotlin. Определение палиндрома. (Avito)
// Реализуйте функцию isPalindrome(), которая возвращает true или false в зависимости от того, является ли переданная ей строка палиндромом.
// Строка состоит из букв в нижнем регистре, и не содержит пробелов.
fun isPalindrome(string: String): Boolean {
// Твое решение здесь
}
Kotlin. Найти упавший кубик. (Купибилет)
По конвееру едут кубики, на гранях которых нарисована одна буква. В конце конвеера кубики падают в корзину. Когда выгрузили кубики из корзины, то обнаружили что одного не хватает. нужно реализовать метод который, возвращает букву с пропавшего кубика.
// Пример:
val input = arrayOf('d', 'a', 'a', 'c', 'd', 'b')
val output = arrayOf('b', 'a', 'd', 'a', 'd')
val result = 'c'
fun checkResults(input: Array<Char>, output: Array<Char>): Char {
// Создаем карту для подсчета букв на входе
val charCount = mutableMapOf<Char, Int>()
// Заполняем карту количеством каждого кубика в input
for (cube in input) {
charCount[cube] = charCount.getOrDefault(cube, 0) + 1
}
// Уменьшаем счетчик для каждого кубика в output
for (cube in output) {
charCount[cube] = charCount.getOrDefault(cube, 0) - 1
}
// Ищем кубик с отрицательным значением, это и будет пропавший
for ((cube, count) in charCount) {
if (count > 0) {
return cube // Возвращаем пропавшую букву
}
}
throw IllegalArgumentException("Все кубики на месте") // Исключение, если все кубики найдены
}
fun main() {
val input = arrayOf('A', 'B', 'C', 'D', 'E', 'A') // Все возможные кубики
val output = arrayOf('A', 'B', 'C', 'D', 'A') // Загруженные кубики
val missingCube = checkResults(input, output)
println("Пропавший кубик: $missingCube") // Вывод: Пропавший кубик: E
}
Kotlin. Рефакторинг кода. ProductFragment. (Купибилет)
// Fragment
class ProductFragment: Fragment() {
companion object {
private const val ARG_PRODUCT_ID = "arg_product_id"
private const val CART_ANIMATION_DELAY_MS = 1000L
fun newInstance(id: Int): ProductFragment {
val args = Bundle().apply { putInt(ARG_PRODUCT_ID, id) }
return ProductFragment().apply { arguments = args }
}
}
private val productModel: ProductViewModel by lazy {
ProductViewModel(
id = requireArguments).getInt(ARG_PRODUCT_ID),
context = requireContext)
)
}
private var _binding: FragmentProductBinding? = null
private val binding: FragmentProductBinding
get() = _binding ?: throw IllegalStateException(
"Illegal fragment state (${viewLifecycleOwner.lifecycle.currentState}) for view binding"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
productModel.inCartCount
observe(this) { count ->
if (count == 0) {
binding.addToCartButton.text = requireContext).getString(R.string.add_to_cart)
} else {
val text = requireContext).getString(R.string.in_cart) + " (" + count + ")"
binding.addToCartButton.text = text
}
}
productModel.favouriteButtonText
observe(this) {
binding.favouriteButton.text = it
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentProductBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
productModel.product
.observe(viewLifecycleOwner) { product ->
with(binding) {
productImage.loadImage(product.imageUrls[0])
title.text = product.title
product.description?.image?.let {
it.url?.let {
descriptionImage.loadImage(it)
}
}
description.text = product.description!!.text
}
}
binding.root.postDelayed(CART_ANIMATION_DELAY_MS) {
showCartAnimation()
}
}
private fun showCartAnimation() {
binding.cartLogo.showAnimation()
}
}
//View model
class ProductViewModel(
private val id: Int,
private val context: Context
): ViewModel() {
val product: MutableLiveData<Product> = MutableLiveData<Product>()
val inCartCount: LiveData<Int>.liveData
val favouriteButtonText: LiveData<String>
private val productRepository: ProductRepository = Container.productRepository
private val cartRepository: CartRepository = Container.cartRepository
private val favouriteRepository: FavouriteRepository = Container.favouriteRepository
init {
viewModelScope.launch {
product.value = productRepository.getProduct(id)
}
inCartCount = cartRepository.getInCartCount(id).asLiveData()
favouriteButtonText = favouriteRepository.isFavourite(id)
.map { isFavourite ->
val resId = if (isFavourite) {
R.string.remove_from_favourite_button_text
} else {
R.string.add_to_favourite_button_text
}
context.getString(resId)
}
.asLiveData()
}
}
// Other (не требует ревью)
interface CartRepository {
fun getInCartCount(id: Int): Flow<Int>
}
interface ProductRepository {
suspend fun getProduct(id: Int): Product
}
interface FavouriteRepository {
fun isFavourite(id: Int): Flow<Boolean>
}
data class Product(
val id: Int,
val title: String,
val description: Description,
val imageUrls: List<String>
)
data class Description(
val text: String?,
val image: Image?
)
data class Image(
val url: String?,
val name: String?
)
Kotlin. Рефакторинг кода. (Yandex)
class Task(var id: Long, val name: String)
val list = listOf(1, 3, 5)
val tasks = HashSet<Task>()
fun main() {
val task1 = Task(1, "Задача")
val task2 = Task(1, "Задача")
tasks.add(task1)
tasks.add(task2)
list.add(7)
list.forEvery { it ->
if (it == 3) {
return
}
println("$it")
}
println("tasks contains ${tasks.size} elements")
println("Done!")
}
synchronized fun <reified T> List<T>.forEvery(itemAction: (T) -> Unit) {
list.reversed().forEach { itemAction(it) }
}
// Компилируется ли код? Если нет, то поправить проблемные места.
// Какой будет вывод после выполнения данного кода?
// Как вывести все числа пропустив 3?
Kotlin. Рефакторинг кода. (Yandex)
/**
* Дана функция main(), в которой выполняется некоторый код.
* Расскажите, что конкретно в нем происходит и что будет выведено
* в консоли после выполнения данного кода
*/
class Counter(var count: Long) {
fun increment() {
count++
}
fun decrement() {
count--
}
}
const val PIECES_OF_WORK = 1000
var counterOfCompletedPiecesOfWork = Counter(0)
fun main() {
thread {
Handler(Looper.getMainLooper()).post {
MainScope().launch {
coroutineScope {
launch {
withContext(Dispatchers.Default) {
(1..PIECES_OF_WORK).map {
async {
delay(2.nanoseconds) // simulate work time
counterOfCompletedPiecesOfWork.increment()
}
}.awaitAll()
}
}
}
try {
async {
delay(10.nanoseconds)
when ((0..100).random()) {
else -> throw Exception("Failed to do simple work")
}
}
} catch (e: Exception) {
println(e)
}
runBlocking(Dispatchers.Main) {
for (i in 1..PIECES_OF_WORK) {
delay(2.nanoseconds) // simulate work time
counterOfCompletedPiecesOfWork.decrement()
}
println(counterOfCompletedPiecesOfWork)
}
}
}
}
}
Kotlin. Реализовать слои UI + Бизнес логика. (Yandex)
Использовать View или Compose.
/**
* Вам доступно некоторое API:
* https://api.yandex.net/offer/{idy/phones.json
*
* Формат ответа:
* {
* "phones": ["+7 (999)-999-00-01", "+7 (999)-999-00-02"]
* }
*
* Есть репозиторий PhoneRepository, который ходит в эту ручку, и есть метод getPhones(),
* который возвращает список номеров List<String>
* interface PhoneRepository {
* getPhones(): List<string> // suspend fun getPhones(): List<String>, если решите использовать корутины
* }
*
* Задание:
* - Необходимо, чтобы по нажатию на некоторую кнопку на экране дёргалось эта ручка
* и совершался переход в звонилку с полученным номером.
* - Требуется написать весь UI и необходимую бизнес-логику для совершения этого действия.
* - Иных элементов интерфейса (кроме кнопки) на экране нет.
*
* Примечание:
* - Технологический стек - на ваш выбор, но важно обосновать принятое решение.
* - Реализовывать репозиторий не нужно.
*/
// Как защититься от того что юзер нажал кнопку 20 раз подряд?
Kotlin. Рефакторинг кода. (Yandex)
Перед тобой код, который находит изображение в папке «Downloads» и отображает его в ImageView
. Как бы ты улучшил этот код?
class MyActivity: Activity() {
private val context = applicationContext
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootView = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
}
setContentView(rootView)
val button = Button(context).apply {
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
200
)
}
rootView.addView(button)
val imageView = ImageView(context).apply {
layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
rootView.addView(imageView)
button.setOnClickListener {
val downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val files = downloads!!.listFiles()
files.forEach {
if ((it as File).name.endsWith(".jpg")) {
Thread {
val image = BitmapFactory.decodeFile(it.path)
imageView.setImageBitmap(image)
}.start()
return@forEach
}
}
throw RuntimeException("There are no images in your downloads!!!!")
}
}
}
Kotlin. Реализовать логику отображения уведомления. (Иннотех)
// При разрывах соединения пользователям необходимо дать возможность
// альтернативного входа в конференцию, для этого мы будем показывать уведомление
// о возможности входа в конференцию по телефонному звонку.
// Необходимо показывать уведомление внутри приложения (не в центре уведомлений)
// пользователю через 10 секунд после отключения соединения.
// При восстановлении соединения уведомление должно скрываться.
// Если реконнект произошел быстрее 10 секунд - уведомление не должно показываться.
// К показу уведомления привязана аналитика по которой мы судим о том,
// что пользователю показалось уведомление на экране.
interface ConnectionManager {
val isSocketConnected: StateFlow<Boolean>
}
interface Analytics {
fun notificationHasBeenShown()
}
Kotlin. Написать эффективную модель для работы с разными событиями
Есть стрим эвентов. Некоторые экраны на него подписаны и слушают нужные им события.
Решение:
• Реализация не должна следить сразу за всеми событиями
• Скрывать реализацию фильтрации
• Буферизация?
interface ServiceConnection {
val event(): Flow<String> // Emit events in JSON format
}
interface EventFilter<E: Input> {
fun(json: String) -> E?
}
class OptimizedServiceConnection(
private val serviceConnection: ServiceConnection
) {
private val eventFilterRegister = mutableMapOf<String, EventFilter<*>>()
fun registerFilter(type: String, eventFilter: EventFilter<E: Input>) {
...
}
fun <E: Input> ServiceConnection.subscribe(type: String): Flow<E> {
return serviceConnection.events().filter().mapNotNull { eventFilterRegister.getValue(type).filter(it) }
}
}
sealed class Input(
val type: String
) {
data class LogoutInput(
@SerialName("type") override val type: String
)
}
Kotlin. Рефакторинг кода
@Component
interface FeatureComponent {
fun inject(viewModel: FeatureViewModel)
}
class FeatureFragment @Inject constructor(
private val viewModel: FeatureViewModel
): Fragment() {
override fun onResume() {
super.onResume()
viewModel.bindView(this)
viewModel.loadData()
}
}
class FeatureViewModel @Inject constructor(
private val id: String
): ViewModel() {
@Inject
private val repository: FeatureRepository? = null
lateinit var view: FeatureFragment
fun loadData() {
repository?.loadData(id)
}
fun bindView(fragment: Fragment) {
view = fragment as FeatureFragment
}
}
interface FeatureRepository {
fun loadData(id: String): ArrayList<String>
}
class FeatureRepositoryRetrofit @Inject constructor(
private val service: RetrofitService
): FeatureRepository {
override fun loadData(id: String): ArrayList<String> {
// load data
return arrayListOf()
}
}
class FeatureRepositoryKtor @Inject constructor(): FeatureRepository {
override fun loadData(id: String): ArrayList<String> {
// load data
return arrayListOf()
}
}
interface RetrofitService {
suspend fun loadData(
@Query("id") id: String
): ArrayList<String>
}
Kotlin. Рефакторинг кода
class ProductsViewModelToRefactor(): BaseViewModel() {
@Inject
private lateinit var router: ProductsNavigationApi
lateinit var interactor: ProductsInteractorImpl
lateinit var loadInteractor: LoadWithWorkersUseCase
val _productRecyclerLD: MutableLiveData<UiState<List<ProductsListItem.ProductInListVO>>> = MutableLiveData(UiState.Init())
private val fiveMinutes = 1000L * 60 * 5
fun getProducts() {
viewModelScope.launch(Dispatchers.Main) {
_productRecyclerLD.value = UiState.Loading()
val responseFlow = loadInteractor.loadProducts()
responseFlow.collect {
if (it.state == WorkInfo.State.SUCCEEDED) {
updateUiState()
}
}
}
}
private fun updateUiState() {
viewModelScope.launch(Dispatchers.Main) {
val products = interactor.getProducts()
if (products is DomainWrapper.Success) {
_productRecyclerLD.value = UiState.Success(products.value)
}
}
}
fun updateProductCartState(guid: String, inCart: Boolean) {
viewModelScope.launch(Dispatchers.Main) {
var asyncCoroutine: Deferred<DomainWrapper<ProductsListItem.ProductInListVO>> = async {
return@async DomainWrapper.Error(RuntimeException())
}
try {
asyncCoroutine = async { interactor.updateProductCartState(guid, inCart) }
} catch (e: Exception) {
Log.e(e.message, "error via download")
this.cancel()
} catch (e: CancellationException) {
Log.e(e.message, "cancellation exception")
this.cancel()
}
val result = asyncCoroutine.await()
launch {
val products = interactor.getProducts()
if (products is DomainWrapper.Success) {
_productRecyclerLD.value = UiState.Success(products.value)
}
}
}
}
fun addViewCount(guid: String) {
viewModelScope.launch(Dispatchers.Main) {
when (interactor.addViewToProductInList(guid)) {
is DomainWrapper.Success -> {
updateUiState()
}
else -> {}
}
}
}
fun autoUpdateProducts() {
viewModelScope.safeLaunch(Dispatchers.IO) {
delay(fiveMinutes)
getProducts()
autoUpdateProducts()
}
}
}
interface ProductsNavigationApi {
fun isClosed(fragment: Fragment): Boolean
fun navigateToPDP(fragment: Fragment, guid: String)
fun navigateToAddProduct(fragment: Fragment)
}
interface ProductListUseCase {
suspend fun getProducts(): DomainWrapper<List<ProductsListItem.ProductInListVO>>
suspend fun addViewToProductInList(guid: String): DomainWrapper<ProductsListItem.ProductInListVO>
suspend fun updateProductCartState(
guid: String,
inCart: Boolean
): DomainWrapper<ProductsListItem.ProductInListVO>
fun createProductsList(
list: List<ProductsListItem.ProductInListVO>,
context: Context,
estimatedPrice: Int = 100,
): List<ProductsListItem>?
}
abstract class BaseViewModel: ViewModel() {
protected val _action = MutableSharedFlow<Action>()
val action: SharedFlow<Action> = _action
}
Kotlin. Найти кратчайшее расстояние между X и Y в строке. (Yandex)
Дана строка s, состоящая только из символов ‘X’, ‘Y’ и ‘O’. Необходимо найти кратчайшее расстояние между любыми символами ‘X’ и ‘Y’ в строке.
Расстоянием между символами ‘X’ и ‘Y’ считается разница в их индексах (по модулю), между которыми могут находиться любые символы, в том числе ‘O’.
Если в строке нет пар ‘X’ и ‘Y’, вернуть -1.
Пример
Input: s = “XXOOYXYXO”
Output: 1
Explanation: Минимальное расстояние между ‘X’ и ‘Y’ равно 1 (между индексами 5 и 6).
Input: s = “XOXOXO”
Output: 1
Explanation: Минимальное расстояние между ‘X’ и ‘Y’ равно 1 (между индексами 0 и 1, а также 2 и 3).
Input: s = “XXXXXOOO”
Output: -1
Explanation: В строке нет символа ‘Y’, поэтому возвращаем -1.
fun shortestXYDistance(s: String): Int {
var minDistance = Int.MAX_VALUE
var lastXIndex = -1
var lastYIndex = -1
// Проходим по строке, сохраняя последние индексы X и Y
for (i in s.indices) {
when (s[i]) {
'X' -> {
lastXIndex = i
// Если ранее встретили Y, обновляем минимальное расстояние
if (lastYIndex != -1) {
minDistance = minOf(minDistance, lastXIndex - lastYIndex)
}
}
'Y' -> {
lastYIndex = i
// Если ранее встретили X, обновляем минимальное расстояние
if (lastXIndex != -1) {
minDistance = minOf(minDistance, lastYIndex - lastXIndex)
}
}
}
}
return if (minDistance == Int.MAX_VALUE) -1 else minDistance
}
Kotlin. Рефакторинг кода. (Yandex)
Проанализируй код, выяви потенциальные ошибки при работе с коллекцией actionSet и предложи улучшения для безопасного удаления элементов и упрощения логики.
fun main() {
val actionSet = HashSet<Runnable>()
actionSet.add(this::runAction)
actionSet.add(this::runAction) // равны по ссылке
actionSet.remove(this::runAction as Runnable)
for (action in s) {
if (filter(action)) {
actionSet.remove(action)
}
s.run()
}
println("Done!")
}
fun runAction(): Boolean {
println("Action!")
return true
}
fun filter(o: Any): Boolean {
return o.toString().lowercase().contains("lambda")
}
Kotlin. Определить размеры всех View. (Yandex)
// Дано:
// parentWidth — ширина родительского контейнера.
// childSpecs — спецификации ширин дочерних View контейнера, целые числа, причем:
// - если childSpec[i] >= 0, то дочерняя View имеет фиксированную ширину, равную childSpec[i].
// - если childSpec[i] < 0, то это доля, которую займет View от неиспользованной
// ширины parentWidth после расположения в нём всех View с фиксированной шириной.
// Требуется определить финальные размеры всех View.
// Пример: measureWidths(100, listOf(50, -3, -2)) == listOf(50, 30, 20)
fun measureWidths(parentWidth: Int, childSpecs: List<Int>): List<Int> {
// your code
}
Kotlin. Написать функцию поиска самого длинного слова в строке
/**
* Написать функцию getLongestWord, которая определяет
* самое длинное слово в строке и возвращает его
*/
fun main() {
println(getLongestWord("Большой цветок")) // -> Большой
println(getLongestWord("Я люблю печеньки")) // -> печеньки
println(getLongestWord("На улице сегодня солнечно, дождя нет")) // -> солнечно
}
// Решение
fun getLongestWord(str: String): String {
return str
.split(" ")
.map { it.filter { char -> char.isLetter() } }
.maxByOrNull { it.length }.orEmpty()
}
Compose. Проектирование виджета. (Rutube)
Спроектировать виджет, позволяющий скачать описание к видео, показать его, а также сохранить в локальное хранилище.
Compose. Рефакторинг кода. (Yandex)
@Composable
fun CachbackSelection(
items: List<CashbackItem>,
modifier: Modifier,
onButtonClick: () -> Unit
) {
var buttonEnabled by mutableStateOf(false)
val messageAlpha by animateFloatAsState(targetValue = if (buttonEnabled) 1F else 0F)
Column(modifier) {
items.forEach {
Row(modifier) {
Text(text = it.category)
Checkbox(
checked = it.selected,
onCheckedChange = { selected ->
it.selected = selected
buttonEnabled = selected
}
)
}
}
Text(
modifier = modifier.alpha(messageAlpha),
text = "Нажимая кнопку, вы соглашаетесь на передачу сведений о совершенных покупках"
)
Button(
onClick = { onButtonClick() },
enabled = buttonEnabled,
) {
Text(text = "Сохранить")
}
}
}
Compose. Рефакторинг кода. (ДОМ.РФ)
// Найти проблемы перфоманса и предложить варианты их решения:
@Composable
fun Message(
message: Message,
onMessageClick: (id: String) -> Unit
) {
Text(
modifier = Modifier.outline(),
text = message.text
)
// any
}
class Message(
val id: String,
val text: String,
val date: LocalDateTime,
// any payload
)
@Composable
fun MessagesList(
messages: List<Message>,
onMessageClick: (id: String) -> Unit,
) {
val scope = remember {
CoroutineScope(Dispatchers.Main)
}
val sortedMessage = remember(messages) {
messages.sorted()
}
val lazyListState = rememberLazyListState()
LazyColumn(
state = lazyListState,
modifier = Modifier.fillMaxSize()
) {
sortedMessage.forEach {
item {
Message(
it,
onMessageClick
)
}
}
item {
Spacer(modifier = Modifier.height(16.dp))
}
}
if (lazyListState.firstVisibleItemIndex > 3) {
Button(onClick = { scope.launch { lazyListState.scrollToItem(0) } }) {
// any
}
}
}
fun Modifier.outline() = composed {
// any
}
RxJava. Какую бизнес-задачу решает этот код? Как его отрефакторить?
• Переименовать UseCases.
• Передавать в метод listOf<Item>
а не result.
• Избавиться от return if
чтобы уменьшить вложенность кода.
• Mapper вынести в отдельный класс.
class MainUseCase @Inject constructor(
private val firstUseCase: FirstUseCase,
private val secondUseCase: SecondUseCase
) {
@CheckResult
fun execute(result: Result): Completable {
val orderedItems = result.items
return if (orderedItems.isEmpty()) {
Completable.complete()
} else {
firstUseCase.getCartItems()
.map { cartItems ->
orderedItems.mapNotNull { orderItem ->
cartItems.firstOrNull i it.id == orderItem.id }
}
.map { cartItem ->
CartItemId(
cartItemId = cartItem.id,
skuld = cartItem.skuld,
bundleId = cartItem.bundleId,
isPrimaryBundleItem = cartItem.isPrimaryBundleItem
)
}
}
.flatMapCompletable {
if (it.isEmpty)) {
Completable.complete()
}else{
secondUseCase.deleteItems(it)
}
}
}
}
}
Java Thread. Что увидим в консоли?
В приведённом коде возникает проблема с многопоточностью, так как два потока одновременно меняют и читают переменные x и y без синхронизации. Это приводит к непредсказуемому поведению — результат выполнения программы зависит от того, какой поток первым изменит или прочитает данные.
Возможные выводы в консоль:
• Поток t1
может напечатать 0, а поток t2
— 1.
• Или наоборот: поток t1
— 1, а t2
— 0.
• В некоторых случаях оба потока могут вывести 0.
Результат непредсказуем из-за условий гонки.
class State(
var x = 0
var y = 0
)
fun main() {
val state = State()
val t1 = Thread({
state.x = 1
println(state.y)
})
val t2 = Thread({
state.y = 1
println(state.x)
})
t1.start()
t2.start()
t1.join()
t2.join()
}
Java Thread. Что увидим в консоли? (Avito)
class State(
var x: Int = 0,
var y: Int = 0
)
fun main() {
val state = State()
val t1 = Thread {
state.x = 1
println(state.y)
}
val t2 = Thread {
state.y = 1
println(state.x)
}
t1.start()
t2.start()
t1.join()
t2.join()
}
// Решение: CountDownLatch
class State {
@Volatile
var x: Int = 0
@Synchronized set
@Volatile
var y: Int = 0
@Synchronized set
}
fun main() {
val state = State()
val latch1 = CountDownLatch(1)
val latch2 = CountDownLatch(1)
val t1 = Thread {
state.x = 1
latch1.countDown()
latch2.await()
println(state.y)
}
val t2 = Thread {
state.y = 1
latch2.countDown()
latch1.await()
println(state.x)
}
t1.start()
t2.start()
t1.join()
t2.join()
}
- Что выведет код?
Нет гарантий видимости переменных и атомарности операций, поэтому результат может любой(0 0, 1 0, 0 1, 1 1).
- Почему результат можем быть 0, 0?
У потоков могут могут совпасть линии кэшей или может произойти реордеринг.
- Как избежать результата 0, 0?
Нужно использовать пометку
@Volatile
на переменных x,y и сделать и@Synchronized
сеттеры.
- Как сделать так, чтобы код всегда выводил 1 1?
Решение:
CountDownLatch
.