Live Coding

17.04.2024https://youtu.be/kKcwi0w5c8A
31.10.2023https://youtu.be/d4iq9-ZxqCw
28.04.2023https://youtu.be/KH0vLN1siI8
04.07.2022https://youtu.be/JaZ_1aXlX5A
15.03.2021https://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)
  1. Какой будет алгоритмическая сложность решения?

    Алгоритмическая сложность решения состоит из двух частей:

    1. Проход по каждому дню соревнований:

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

    1. Поиск максимального значения и фильтрация пользователей:

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

    Итоговая сложность: Поскольку поиск максимума и фильтрация выполняется один раз после основного прохода, итоговая сложность будет.

  1. Какой будет пространственная сложность решения? Сколько памяти выделим в худшем случае?

    Пространственная сложность решения будет зависеть от следующих факторов:

    1. Хранение статистики шагов для всех дней:

    Мы храним статистику всех пользователей за каждый день. Если d — количество дней, а n — среднее количество пользователей за день, то для хранения входных данных потребуется  памяти.

    1. Словарь для сумм шагов:

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

    1. Список пользователей с максимальным количеством шагов:

    Мы храним список 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()
}
  1. Что выведет код?

    Нет гарантий видимости переменных и атомарности операций, поэтому результат может любой(0 0, 1 0, 0 1, 1 1).

  1. Почему результат можем быть 0, 0?

    У потоков могут могут совпасть линии кэшей или может произойти реордеринг.

  1. Как избежать результата 0, 0?

    Нужно использовать пометку @Volatile на переменных x,y и сделать и @Synchronized сеттеры.

  1. Как сделать так, чтобы код всегда выводил 1 1?

    Решение: CountDownLatch.