From a8801cc39825ccd07f9303e659e418a901ab9f64 Mon Sep 17 00:00:00 2001 From: Abhay Date: Sun, 7 Jan 2024 17:00:00 +0530 Subject: [PATCH] added linear search --- .idea/.name | 1 + .idea/deploymentTargetDropDown.xml | 17 ++ .idea/discord.xml | 7 + .../waleska404/algorithms/di/DataModule.kt | 6 + .../domain/linearsearch/LinearSearch.kt | 5 + .../linearsearch/LinearSearchDomainModel.kt | 3 + .../domain/linearsearch/LinearSearchImpl.kt | 17 ++ .../algorithms/ui/core/Navigator.kt | 14 ++ .../algorithms/ui/home/HomeScreen.kt | 11 +- .../ui/linearsearch/LinearSearchList.kt | 17 ++ .../ui/linearsearch/LinearSearchScreen.kt | 233 ++++++++++++++++++ .../ui/linearsearch/LinearSearchViewModel.kt | 60 +++++ .../baseline_keyboard_arrow_right_24.xml | 5 + app/src/main/res/values/strings.xml | 4 + build.gradle.kts | 2 +- 15 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/deploymentTargetDropDown.xml create mode 100644 .idea/discord.xml create mode 100644 app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearch.kt create mode 100644 app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchDomainModel.kt create mode 100644 app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchImpl.kt create mode 100644 app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchList.kt create mode 100644 app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchScreen.kt create mode 100644 app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchViewModel.kt create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..8de8da2 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Algorithms \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..43894fc --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/waleska404/algorithms/di/DataModule.kt b/app/src/main/java/com/waleska404/algorithms/di/DataModule.kt index 8ae91be..fd8a730 100644 --- a/app/src/main/java/com/waleska404/algorithms/di/DataModule.kt +++ b/app/src/main/java/com/waleska404/algorithms/di/DataModule.kt @@ -4,6 +4,8 @@ import com.waleska404.algorithms.domain.bubblesort.BubbleSort import com.waleska404.algorithms.domain.bubblesort.BubbleSortImpl import com.waleska404.algorithms.domain.dijkstra.Dijkstra import com.waleska404.algorithms.domain.dijkstra.DijkstraImpl +import com.waleska404.algorithms.domain.linearsearch.LinearSearch +import com.waleska404.algorithms.domain.linearsearch.LinearSearchImpl import com.waleska404.algorithms.domain.quicksort.QuickSort import com.waleska404.algorithms.domain.quicksort.QuickSortImpl import dagger.Module @@ -28,4 +30,8 @@ class DataModule { @Provides fun providesDijkstra(): Dijkstra = DijkstraImpl() + @Singleton + @Provides + fun providesLinearSearch(): LinearSearch = LinearSearchImpl() + } \ No newline at end of file diff --git a/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearch.kt b/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearch.kt new file mode 100644 index 0000000..346e3ee --- /dev/null +++ b/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearch.kt @@ -0,0 +1,5 @@ +package com.waleska404.algorithms.domain.linearsearch +import kotlinx.coroutines.flow.Flow +interface LinearSearch { + suspend fun compare(list: MutableList, searchItem: Int, position: Int): Flow +} \ No newline at end of file diff --git a/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchDomainModel.kt b/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchDomainModel.kt new file mode 100644 index 0000000..f76e9c2 --- /dev/null +++ b/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchDomainModel.kt @@ -0,0 +1,3 @@ +package com.waleska404.algorithms.domain.linearsearch + +data class LinearSearchDomainModel(val position: Int, val elementFound: Boolean) diff --git a/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchImpl.kt b/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchImpl.kt new file mode 100644 index 0000000..16b7c0c --- /dev/null +++ b/app/src/main/java/com/waleska404/algorithms/domain/linearsearch/LinearSearchImpl.kt @@ -0,0 +1,17 @@ +package com.waleska404.algorithms.domain.linearsearch + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +class LinearSearchImpl @Inject constructor() : LinearSearch { + override suspend fun compare( + list: MutableList, + searchItem: Int, + position: Int + ): Flow { + return flow { + emit(LinearSearchDomainModel(position, list[position] == searchItem)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/waleska404/algorithms/ui/core/Navigator.kt b/app/src/main/java/com/waleska404/algorithms/ui/core/Navigator.kt index c551d72..6f059d8 100644 --- a/app/src/main/java/com/waleska404/algorithms/ui/core/Navigator.kt +++ b/app/src/main/java/com/waleska404/algorithms/ui/core/Navigator.kt @@ -9,6 +9,7 @@ import androidx.navigation.compose.composable import com.waleska404.algorithms.ui.bubblesort.BubbleSortScreen import com.waleska404.algorithms.ui.dijkstra.DijkstraScreen import com.waleska404.algorithms.ui.home.HomeScreen +import com.waleska404.algorithms.ui.linearsearch.LinearSearchScreen import com.waleska404.algorithms.ui.quicksort.QuickSortScreen @RequiresApi(Build.VERSION_CODES.O) @@ -34,6 +35,11 @@ fun ContentWrapper(navigationController: NavHostController) { navigationController.navigate( Routes.Dijkstra.route ) + }, + navigateToLinearSearch = { + navigationController.navigate( + Routes.LinearSearch.route + ) } ) @@ -59,6 +65,13 @@ fun ContentWrapper(navigationController: NavHostController) { } ) } + composable(route = Routes.LinearSearch.route) { + LinearSearchScreen( + navigateToHome = { + navigationController.popBackStack() + } + ) + } } } @@ -67,4 +80,5 @@ sealed class Routes(val route: String) { object BubbleSort : Routes("bubble_sort") object QuickSort : Routes("quick_sort") object Dijkstra : Routes("dijkstras_algorithm") + object LinearSearch : Routes("linear_search") } \ No newline at end of file diff --git a/app/src/main/java/com/waleska404/algorithms/ui/home/HomeScreen.kt b/app/src/main/java/com/waleska404/algorithms/ui/home/HomeScreen.kt index 193e41c..4121974 100644 --- a/app/src/main/java/com/waleska404/algorithms/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/waleska404/algorithms/ui/home/HomeScreen.kt @@ -23,7 +23,8 @@ import com.waleska404.algorithms.ui.core.components.CustomCard fun HomeScreen( navigateToBubbleSort: () -> Unit, navigateToQuickSort: () -> Unit, - navigateToDijkstra: () -> Unit + navigateToDijkstra: () -> Unit, + navigateToLinearSearch: () -> Unit, ) { Column( modifier = Modifier @@ -69,6 +70,14 @@ fun HomeScreen( iconDescription = R.string.sort_descending_icon, navigateToAlgorithm = navigateToDijkstra ) + + // linear search algorithm + AlgorithmListItem( + title = R.string.linear_search, + icon = R.drawable.route, + iconDescription = R.string.sort_descending_icon, + navigateToAlgorithm = navigateToLinearSearch + ) } } diff --git a/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchList.kt b/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchList.kt new file mode 100644 index 0000000..6ac337e --- /dev/null +++ b/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchList.kt @@ -0,0 +1,17 @@ +package com.waleska404.algorithms.ui.linearsearch + +data class LinearSearchList( + val list: List +) { + fun toDataList(): MutableList { + return list.map { + it.value + }.toMutableList() + } +} + +data class LinearSearchItem( + val value: Int, + var position: Int, + var itemFound: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchScreen.kt b/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchScreen.kt new file mode 100644 index 0000000..45d1c82 --- /dev/null +++ b/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchScreen.kt @@ -0,0 +1,233 @@ +package com.waleska404.algorithms.ui.linearsearch + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.waleska404.algorithms.R +import com.waleska404.algorithms.ui.core.components.CustomIconButton +import com.waleska404.algorithms.ui.core.components.CustomSlider +import com.waleska404.algorithms.ui.core.components.CustomTopAppBar +import java.util.UUID +import kotlin.random.Random + +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun LinearSearchScreen( + linearSearchViewModel: LinearSearchViewModel = hiltViewModel(), + navigateToHome: () -> Boolean, +) { + val list: LinearSearchList by linearSearchViewModel.list.collectAsState() + var searchItemVal by remember { + mutableIntStateOf(Random.nextInt(1, 99)) + } + val currentIndex = linearSearchViewModel.currentIndex.collectAsState() + val itemFound = linearSearchViewModel.searchItemFound.collectAsState() + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.primary), + ) { + CustomTopAppBar(navigateToHome = navigateToHome, title = R.string.linear_search) + Spacer(modifier = Modifier.height(10.dp)) + + LazyRow( + modifier = Modifier + .padding(horizontal = 15.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.Bottom + ) { + items(list.list, key = { + UUID.randomUUID().toString() + }) { + LinearSearchItem(item = it) + } + } + + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = "i = ${currentIndex.value}, current element = ${if (currentIndex.value == -1) -1 else list.list[currentIndex.value].value}, search key = $searchItemVal\n ${if (currentIndex.value > list.list.size - 1) "Element not found, press reset to start again." else if (!itemFound.value) "No match and continue to search for the next match." else "Item found at index ${currentIndex.value}"}", + fontWeight = FontWeight.Medium, + fontSize = 18.sp, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 15.dp) + ) + + Spacer(modifier = Modifier.height(10.dp)) + + BottomButtons( + stepOver = { linearSearchViewModel.stepOver(searchItemVal) }, + sliderChange = linearSearchViewModel::randomizeCurrentList, + searchItemChange = { + searchItemVal = it + }, + resetClick = linearSearchViewModel::randomizeCurrentList, + listSizeInit = 10, + isEnabled = !itemFound.value && currentIndex.value < list.list.size - 1, + searchItem = searchItemVal + ) + } +} + +@Composable +fun LinearSearchItem(item: LinearSearchItem) { + Column(modifier = Modifier.padding(horizontal = 5.dp)) { + Box( + modifier = Modifier.size(50.dp).clip( + RoundedCornerShape(15.dp) + ).background(MaterialTheme.colorScheme.onSecondary), + contentAlignment = Alignment.Center, + ) { + Text( + text = item.value.toString(), fontWeight = FontWeight.Bold, + fontSize = 22.sp, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Center + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@RequiresApi(Build.VERSION_CODES.O) +@Composable +private fun BottomButtons( + stepOver: () -> Unit, + sliderChange: (Int) -> Unit, + searchItemChange: (Int) -> Unit, + resetClick: () -> Unit, + modifier: Modifier = Modifier, + listSizeInit: Int, + isEnabled: Boolean, + searchItem: Int +) { + var sliderValue by remember { mutableFloatStateOf(listSizeInit.toFloat()) } + Column( + modifier = modifier.padding(15.dp) + ) { + // range slider + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(id = R.string.list_size), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.secondary + ) + CustomSlider( + modifier = Modifier.weight(1f), + value = sliderValue, + valueRange = 1f..10f, + enabled = isEnabled, + onValueChange = { + val newValue = it.toInt() + if (newValue != sliderValue.toInt()) { + sliderValue = newValue.toFloat() + sliderChange(sliderValue.toInt()) + } + }, + color = MaterialTheme.colorScheme.secondary, + disabledColor = MaterialTheme.colorScheme.surface, + textThumbColor = MaterialTheme.colorScheme.primary + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + + Text( + text = stringResource(id = R.string.search_key), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.secondary + ) + + OutlinedTextField( + value = searchItem.toString(), + onValueChange = { + if (it.length <= 2) searchItemChange.invoke(if (it.isNotEmpty()) it.toInt() else 0) + }, + enabled = isEnabled, + modifier = Modifier + .size(50.dp), + textStyle = TextStyle( + color = MaterialTheme.colorScheme.secondary, + fontSize = 14.sp, + fontWeight = FontWeight.W500 + ), + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Done, + keyboardType = KeyboardType.Number + ), + colors = TextFieldDefaults.outlinedTextFieldColors( + containerColor = MaterialTheme.colorScheme.onSecondary, + focusedBorderColor = MaterialTheme.colorScheme.onSecondary, + unfocusedBorderColor = MaterialTheme.colorScheme.onSecondary + ), + ) + } + + Spacer(modifier = Modifier.height(10.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + CustomIconButton( + modifier = Modifier + .weight(1f), + text = stringResource(id = R.string.step), + onClick = { stepOver() }, + iconResource = R.drawable.baseline_keyboard_arrow_right_24, + iconDescriptionResource = R.string.step, + enabled = isEnabled + ) + + CustomIconButton( + modifier = Modifier + .weight(1f), + text = stringResource(id = R.string.reset), + onClick = resetClick, + iconResource = R.drawable.shines, + iconDescriptionResource = R.string.reset, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchViewModel.kt b/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchViewModel.kt new file mode 100644 index 0000000..c35c04e --- /dev/null +++ b/app/src/main/java/com/waleska404/algorithms/ui/linearsearch/LinearSearchViewModel.kt @@ -0,0 +1,60 @@ +package com.waleska404.algorithms.ui.linearsearch + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.waleska404.algorithms.domain.linearsearch.LinearSearch +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LinearSearchViewModel @Inject constructor(private val linearSearch: LinearSearch) : + ViewModel() { + private var _searchItemFound = MutableStateFlow(false) + val searchItemFound: StateFlow = _searchItemFound + private var _currentIndex = MutableStateFlow(-1) + val currentIndex: StateFlow = _currentIndex + private var _list = MutableStateFlow(getRandomList()) + val list: StateFlow = _list + + private fun getRandomList(size: Int = 9): LinearSearchList { + _currentIndex.value = -1 + _searchItemFound.value = false + val list = (0 until size).map { + LinearSearchItem( + value = (30..100).random(), + position = _currentIndex.value, + itemFound = false + ) + } + return LinearSearchList(list = list) + } + + fun randomizeCurrentList(size: Int = list.value.list.size) { + _list.value = getRandomList(size) + } + + fun stepOver(searchItem: Int) { + val dataList = _list.value.toDataList() + _currentIndex.value++ + viewModelScope.launch { + linearSearch.compare(dataList, searchItem, _currentIndex.value).collect { + _searchItemFound.value = it.elementFound + val updatedList = _list.value.list.toMutableList().also { mutableList -> + mutableList[_currentIndex.value] = LinearSearchItem( + value = _list.value.list[_currentIndex.value].value, + position = _currentIndex.value, + itemFound = it.elementFound + ) + } + + _list.value = _list.value.copy( + list = updatedList + ) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml new file mode 100644 index 0000000..5d47b4d --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cfa4185..a092bc3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,7 @@ Quick Sort Algorithms Visualization Dijkstra\'s Algorithm + Linear Search Start Finish Visited @@ -18,8 +19,11 @@ broom icon shine icon Clear + Reset Run target icon down arrow icon left arrow icon + Step + Search Key: \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b547f79..515c1f8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.1.1" apply false + id("com.android.application") version "8.0.2" apply false id("org.jetbrains.kotlin.android") version "1.8.10" apply false id("com.google.dagger.hilt.android") version "2.44" apply false } \ No newline at end of file