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