diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8dd1c2a4 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +SUNBIRD_API_TOKEN=your_sunbird_token_here \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a6344aac --- /dev/null +++ b/.gitattributes @@ -0,0 +1,35 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..d5dee87e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 7860 + +CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"] \ No newline at end of file diff --git a/README.md b/README.md index 87bd5a08..c087c2d3 100644 --- a/README.md +++ b/README.md @@ -1,139 +1,281 @@ -# Sunbird AI Internship Assessment Exercise +--- +title: Sunbird AI App -This assessment consists of 3 parts: -- Programming exercises. -- Build a simple command line app using the Sunbird AI API. +colorFrom: blue +colorTo: yellow +sdk: docker +app_port: 7860 +pinned: false +--- -## Getting started -- Fork this repository to create your own copy. ([More info about forking a repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo)) -- Clone your repository to access it locally: `git clone https://github.com//internship-assessment.git`. (Replace `` with your Github username.) -- Change directory into the `internship-assessment` folder after cloning the repository. -- Create a python virtual environment: `python -m venv venv` -- Activate the virtual environment: - - Linux/Mac: `source venv/bin/activate` - - Windows: `venv\Scripts\activate.bat` -- Install the required python packages: `pip install -r requirements.txt` -- Run the command `pytest`. (The tests should be failing, it's your task to make them pass. See below for instructions) -## Part 1: Programming exercises -There are 2 programming exercises designed to test your competency with the python programming language. -You can find the starter code and task descriptions in the `exercises/basics.py` file in this repo. -Run the following command: `pytest`. You will see that all the tests are failing. -Your goal is to implement the 2 functions `collatz` and `distinct_numbers` to make the above failing tests pass. -You can keep running the `pytest` command to see which tests are still failing and fix your code accordingly. +# Sunbird AI — Transcribe, Summarise & Translate -## Part 2: Build a GenAI Application with Sunbird AI +A Generative AI web application powered by **Sunbird AI's Sunflower LLM** and Speech APIs. The app accepts English text or audio input, summarises it, translates the summary into a Ugandan local language, and generates a spoken audio output of the translation. -Build a small **Generative AI web application** powered by Sunbird AI's [Sunflower LLM](https://sunflower.sunbird.ai/) and the [Sunbird AI API](https://docs.sunbird.ai/introduction). +Built as part of the **Sunbird AI Internship Assessment**. -The application should let a user provide either **text** or an **audio file**, then run the input through this pipeline: +--- -1. **Input** — accept either typed/pasted text **or** an uploaded audio file. -2. **Transcribe (audio only)** — if the input is audio, transcribe it to text using Sunbird's Speech-to-Text API. -3. **Summarise** — summarise the text (typed input or transcribed text) using the Sunflower LLM. -4. **Translate** — translate the summary into a chosen Ugandan local language (Luganda, Runyankole, Ateso, Lugbara, or Acholi) using the Sunflower LLM. -5. **Synthesise speech** — generate an audio clip of the translated summary using Sunbird's Text-to-Speech API. -6. **Output** — display the original text, the summary, the translated summary, and the generated audio (playable in the UI). +## Live Demo -### Tech stack requirements + **[Try the app here](https://kennedymutebi-sunbird-ai-app.hf.space)** -- **Backend:** Python (you may use FastAPI, Flask, or call the Sunbird API directly from your frontend framework — your choice). -- **Frontend:** one of [Gradio](https://www.gradio.app/), [Streamlit](https://streamlit.io/), or [Next.js](https://nextjs.org/docs). -- **APIs:** all AI capabilities **must** come from Sunbird AI. Do not call OpenAI, Anthropic, or any other model provider for the core pipeline. +--- -### Sunbird AI API references +## Project Description -Read these docs carefully before implementing — they show the exact request/response shapes and authentication you'll need: +This application solves a real communication problem in Uganda — making English content accessible to people who speak local languages. -- **Speech-to-Text (STT):** https://docs.sunbird.ai/guides/speech-to-text -- **Text-to-Speech (TTS):** https://docs.sunbird.ai/guides/text-to-speech -- **Summarisation & Translation (Sunflower Simple Inference):** https://docs.sunbird.ai/guides/sunflower-chat -- **Full API reference:** https://docs.sunbird.ai/api-reference/introduction +A user can: +- Type or paste English text, **or** upload a voice recording +- Select a target Ugandan language (Luganda, Runyankole, Acholi, Ateso, or Lugbara) +- Receive a summarised translation **and** a generated audio clip in that language -You will need a Sunbird AI API token. Sign up and obtain one from the [Sunbird AI API portal](https://api.sunbird.ai/), then store it in a `.env` file as `SUNBIRD_API_TOKEN` (or equivalent). **Never commit your token to git.** +All AI capabilities are powered exclusively by **Sunbird AI** — no OpenAI or Anthropic APIs are used. -### Functional requirements +--- -- Input switching: the UI must clearly let the user choose between text input and audio upload. -- Audio constraint: reject audio files longer than **5 minutes** with a clear error message. -- Language picker: allow the user to select the target local language for the translated summary. -- Visible intermediate results: the UI should show the transcript (when audio is used), the summary, the translated summary, and the generated audio player — not just the final audio. -- Sensible error handling: surface API failures to the user instead of silently failing. +## Architecture Overview -### Suggested project layout +The application follows a 4-step pipeline: ``` -. -├── app.py # entry point (Gradio/Streamlit) OR Next.js app/ +Input (Text or Audio) + ↓ +[Step 1] Speech-to-Text — Sunbird STT API + Only runs if audio is uploaded + Endpoint: /tasks/stt + ↓ +[Step 2] Summarisation — Sunflower LLM + Shortens the text to 3 key sentences + Endpoint: /tasks/sunflower_simple + ↓ +[Step 3] Translation — Sunflower LLM + Translates summary to chosen Ugandan language + Endpoint: /tasks/sunflower_simple + ↓ +[Step 4] Text-to-Speech — Sunbird TTS API + Converts translated text to spoken audio + Endpoint: /tasks/tts + ↓ +Output (Transcript + Summary + Translation + Audio Player) +``` + +### File Structure + +``` +internship-assessment/ +│ ├── backend/ -│ ├── sunbird_client.py # thin wrapper around Sunbird API endpoints -│ ├── pipeline.py # orchestrates STT -> summarise -> translate -> TTS -│ └── ... -├── requirements.txt # or package.json if Next.js + Python backend -├── .env.example # document required env vars (no real secrets) -└── README.md # see Part 3 +│ ├── __init__.py # Makes backend a Python module +│ ├── sunbird_client.py # All Sunbird API calls (STT, Sunflower, TTS) +│ └── pipeline.py # Orchestrates the 4-step pipeline +│ +├── exercises/ +│ ├── __init__.py +│ └── basics.py # Part 1: Python programming exercises +│ +├── tests/ +│ └── test_basics.py # Pytest tests for Part 1 +│ +├── app.py # Streamlit frontend — the user interface +├── Dockerfile # Docker configuration for Hugging Face deployment +├── .env.example # Template showing required environment variables +├── requirements.txt # Python dependencies +└── README.md # This file +``` + +--- + +## Local Setup + +Follow these steps exactly to run the app on your machine. + +### Prerequisites +- Python 3.10 or higher +- A Sunbird AI API token (see below) +- Git + +### Step 1 — Clone the Repository + +```bash +git clone https://github.com/kennedymutebi/internship-assessment.git +cd internship-assessment +``` + +### Step 2 — Create a Virtual Environment + +```bash +python -m venv venv +``` + +Activate it: + +- **Windows (PowerShell):** +```bash +.\venv\Scripts\Activate.ps1 +``` + +- **Mac/Linux:** +```bash +source venv/bin/activate +``` + +You will see `(venv)` appear in your terminal when it is active. + +### Step 3 — Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### Step 4 — Set Up Environment Variables + +Create a `.env` file in the root folder: + +```bash +cp .env.example .env +``` + +Open `.env` and add your Sunbird API token: + ``` +SUNBIRD_API_TOKEN=your_actual_token_here +``` + +### Step 5 — Run the App + +```bash +streamlit run app.py +``` + +The app will open automatically at `http://localhost:8501` + +--- + +## 🔑 Environment Variables + +| Variable | Description | Required | +|---|---|---| +| `SUNBIRD_API_TOKEN` | Your Sunbird AI Bearer token for all API calls | ✅ Yes | + +### How to Get Your Token + +1. Go to [https://platform.sunbird.ai](https://platform.sunbird.ai) +2. Create a free account with your email +3. After logging in, copy your access token +4. Paste it into your `.env` file as shown above + +> ⚠️ **Never commit your `.env` file to GitHub.** It is listed in `.gitignore` to protect your token. + +--- + +## 🧪 Running the Tests (Part 1) + +Part 1 of the assessment required implementing two Python functions — `collatz` and `distinct_numbers`. + +To verify they work: + +```bash +pytest +``` + +Expected output: +``` +5 passed in 0.15s +``` + +--- + +## 🖥️ Usage Walkthrough + +### Text Input Mode + +1. Open the app at `http://localhost:8501` +2. Select **Text** as your input type +3. Paste or type English text in the text area + + Example: + > *"Uganda is a landlocked country in East Africa with a population of about 45 million people. The capital city is Kampala. Uganda is known for its mountain gorillas and diverse wildlife found in national parks like Bwindi and Queen Elizabeth."* + +4. Choose a target language from the dropdown (e.g. **Luganda**) +5. Click **Run Pipeline** +6. Wait for processing (10–20 seconds) +7. View the **Summary**, **Translation**, and play the **Generated Audio** + +### Audio Input Mode + +1. Select **Audio File** as your input type +2. Upload a voice recording (MP3, WAV, OGG, M4A — maximum 5 minutes) +3. Choose a target language +4. Click **Run Pipeline** +5. View the **Transcript**, **Summary**, **Translation**, and play the **Generated Audio** + +--- -## Part 3: Documentation & Deployment +## 🌐 Supported Languages -A working app you can't run isn't a working app. For this part, you must (a) document your project so a reviewer can run it locally, and (b) deploy it publicly so we can try it without setting anything up. +| Language | Code | TTS Speaker ID | +|---|---|---| +| Luganda | lug | 248 | +| Acholi | ach | 241 | +| Ateso | teo | 242 | +| Runyankole | nyn | 243 | +| Lugbara | lgg | 245 | -### README requirements +--- -Replace this README (or add a `PROJECT_README.md` next to it) with documentation that includes: +## 🔌 Sunbird AI API Reference -- **Project description** — one paragraph on what the app does. -- **Architecture overview** — a short diagram or bullet list of the pipeline (input → STT → summarise → translate → TTS → output) and which Sunbird endpoints handle each step. -- **Local setup** — exact, copy-pasteable steps to clone, install dependencies, configure environment variables (with a `.env.example` reference), and run the app locally. -- **Environment variables** — list every required variable and what it does. -- **Usage** — a short walkthrough showing the app being used end-to-end (screenshots are encouraged). -- **Deployed link** — a public URL where reviewers can try the app. -- **Known limitations** — anything that doesn't work, or constraints (e.g. 5-minute audio cap, supported languages). +| Step | Endpoint | Method | Purpose | +|---|---|---|---| +| Speech-to-Text | `/tasks/stt` | POST | Transcribe audio to text | +| Summarise | `/tasks/sunflower_simple` | POST | Summarise text with Sunflower LLM | +| Translate | `/tasks/sunflower_simple` | POST | Translate text to local language | +| Text-to-Speech | `/tasks/tts` | POST | Convert text to spoken audio | -### Deployment +Full API documentation: [https://docs.sunbird.ai](https://docs.sunbird.ai) -Deploy your app to a free hosting provider that fits your stack. Pick one: +--- -#### Option A — Hugging Face Spaces (recommended for Gradio/Streamlit) +## ⚠️ Known Limitations -1. Create a free account at https://huggingface.co/join. -2. Create a new Space: https://huggingface.co/new-space — choose **Gradio** or **Streamlit** as the SDK and a public visibility. -3. Add your Sunbird API token as a Space secret: Space settings → **Variables and secrets** → **New secret** → name it `SUNBIRD_API_TOKEN`. -4. Push your code to the Space's git repo: - ```bash - git remote add space https://huggingface.co/spaces// - git push space main - ``` -5. Hugging Face will build and deploy automatically. Confirm your `requirements.txt` lists every Python dependency and that your entry file matches the SDK convention (`app.py` for both Gradio and Streamlit). +| Limitation | Detail | +|---|---| +| Audio must be speech | STT is optimised for human voice. Uploading music will produce inaccurate transcripts | +| 5-minute audio cap | Audio files longer than 5 minutes are rejected with a clear error message | +| Short text inputs | Very short inputs like single words may cause the Sunflower model to respond unexpectedly | +| Temporary audio URLs | The generated TTS audio URL expires after 30 minutes — play it immediately | +| Free tier cold starts | On Hugging Face Spaces free tier, the app may take 30–60 seconds to wake up after inactivity | +| Language accuracy | Translation quality varies by language — Luganda performs best as it is Sunflower's strongest local language | -Reference: https://huggingface.co/docs/hub/spaces-overview +--- -#### Option B — Vercel (recommended for Next.js + Python backend) +## Tech Stack -1. Create a free account at https://vercel.com/signup and install the CLI: `npm i -g vercel@latest`. -2. From your project root, link the project: `vercel link`. -3. Add your Sunbird API token as an environment variable for all environments: - ```bash - vercel env add SUNBIRD_API_TOKEN - ``` - (You'll be prompted to select Development, Preview, and Production — select all that apply.) -4. Pull the env vars locally for development: `vercel env pull .env.local`. -5. Deploy: - - Preview: `vercel` - - Production: `vercel --prod` -6. If you have a Python backend (FastAPI/Flask), put it under an `api/` directory or a separate Python service — Vercel runs Python via Fluid Compute. See https://vercel.com/docs/functions/runtimes/python. +| Layer | Technology | +|---|---| +| Frontend / UI | Streamlit | +| Backend | Python | +| AI APIs | Sunbird AI (STT, Sunflower LLM, TTS) | +| Deployment | Hugging Face Spaces (Docker) | +| Environment | python-dotenv | +| HTTP Requests | requests | +| Testing | pytest | -Reference: https://vercel.com/docs/getting-started-with-vercel +--- -### Submission +## About the Developer -Your final submission must include: +**Kennedy Mutebi** +BSc Computer Science — Makerere University, -- A pull request (or repository link) with all your code. -- An updated README that meets the requirements above. -- **A working deployed link** that we can open and use end-to-end with at least one test input. +Full-stack developer with experience in React.js, Django, Python, and Flutter. Passionate about building technology that solves real African problems and some project on git as you will see. +- GitHub: [github.com/kennedymutebi](https://github.com/kennedymutebi) +- Email: kennedymutebi7@gmail.com \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 00000000..bfc9abd5 --- /dev/null +++ b/app.py @@ -0,0 +1,96 @@ +import streamlit as st +import requests +from backend.pipeline import run_pipeline + +# ── Page config ────────────────────────────────────────────── +st.set_page_config( + page_title="Sunbird AI App", + + layout="centered" +) + +# ── Title ───────────────────────────────────────────────────── +st.title(" Sunbird AI — Transcribe, Summarise & Translate") +st.markdown("Convert audio or text into a summarised, translated audio in your local language.") + +st.divider() + +# ── Input Mode ──────────────────────────────────────────────── +input_mode = st.radio( + "Choose your input type:", + ["Text", "Audio File"], + horizontal=True +) + +text_input = None +audio_input = None + +if input_mode == "Text": + text_input = st.text_area( + "Paste or type your text here:", + height=200, + placeholder="Enter your text here..." + ) + +else: + audio_input = st.file_uploader( + "Upload an audio file (MP3, WAV, OGG, M4A — max 5 minutes):", + type=["mp3", "wav", "ogg", "m4a", "aac"] + ) + +st.divider() + +# ── Language Picker ─────────────────────────────────────────── +language = st.selectbox( + "Choose target language for translation:", + ["Luganda", "Runyankole", "Acholi", "Ateso", "Lugbara"] +) + +st.divider() + +# ── Run Button ──────────────────────────────────────────────── +if st.button("Run Pipeline", type="primary", use_container_width=True): + + # Validate inputs + if input_mode == "Text" and not text_input: + st.error("Please enter some text before running.") + + elif input_mode == "Audio File" and audio_input is None: + st.error("Please upload an audio file before running.") + + else: + with st.spinner("Processing... please wait"): + try: + if input_mode == "Text": + result = run_pipeline(language=language, text=text_input) + else: + # Check audio duration — reject anything over 5 minutes + if audio_input.size > 50_000_000: + st.error("Audio file is too large. Maximum allowed is 5 minutes.") + st.stop() + + result = run_pipeline(language=language, audio_file=audio_input) + + st.success("Done!") + st.divider() + + # ── Show Results ────────────────────────────── + if result["transcript"]: + st.subheader("📝 Transcript") + st.write(result["transcript"]) + st.divider() + + st.subheader(" Summary") + st.write(result["summary"]) + st.divider() + + st.subheader(f" Translation ({language})") + st.write(result["translation"]) + st.divider() + + st.subheader("🔊 Generated Audio") + audio_response = requests.get(result["audio_url"]) + st.audio(audio_response.content, format="audio/wav") + + except Exception as e: + st.error(f"Something went wrong: {str(e)}") \ No newline at end of file diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/pipeline.py b/backend/pipeline.py new file mode 100644 index 00000000..3742041a --- /dev/null +++ b/backend/pipeline.py @@ -0,0 +1,39 @@ +from backend.sunbird_client import ( + transcribe_audio, + summarise_text, + translate_text, + text_to_speech +) + + +def run_pipeline(language, text=None, audio_file=None): + """ + Runs the full pipeline: + 1. Transcribe (if audio) + 2. Summarise + 3. Translate + 4. Text to Speech + """ + + result = { + "transcript": None, + "summary": None, + "translation": None, + "audio_url": None + } + + # Step 1 — Transcribe audio if user uploaded a file + if audio_file is not None: + result["transcript"] = transcribe_audio(audio_file) + text = result["transcript"] + + # Step 2 — Summarise the text + result["summary"] = summarise_text(text) + + # Step 3 — Translate the summary + result["translation"] = translate_text(result["summary"], language) + + # Step 4 — Convert translation to speech + result["audio_url"] = text_to_speech(result["translation"], language) + + return result \ No newline at end of file diff --git a/backend/sunbird_client.py b/backend/sunbird_client.py new file mode 100644 index 00000000..59f9ee0a --- /dev/null +++ b/backend/sunbird_client.py @@ -0,0 +1,95 @@ +import requests +import os +from dotenv import load_dotenv + +load_dotenv() + +SUNBIRD_API_TOKEN = os.getenv("SUNBIRD_API_TOKEN") + +BASE_URL = "https://api.sunbird.ai" + +HEADERS = { + "Authorization": f"Bearer {SUNBIRD_API_TOKEN}", + "Content-Type": "application/json" +} + +LANGUAGE_SPEAKER_IDS = { + "Luganda": 248, + "Acholi": 241, + "Ateso": 242, + "Runyankole": 243, + "Lugbara": 245 +} + + +def transcribe_audio(audio_file): + """Send audio file to Sunbird STT and return transcribed text.""" + url = f"{BASE_URL}/tasks/stt" + headers = {"Authorization": f"Bearer {SUNBIRD_API_TOKEN}"} + files = { + "audio": ( + audio_file.name, + audio_file.read(), + audio_file.type + ) + } + response = requests.post(url, files=files, headers=headers) + response.raise_for_status() + return response.json()["audio_transcription"] # ← fixed key + + +def summarise_text(text): + """Send text to Sunflower and return a summary.""" + url = f"{BASE_URL}/tasks/sunflower_simple" + headers = {"Authorization": f"Bearer {SUNBIRD_API_TOKEN}"} + payload = { + "instruction": f"""You are a text summariser. Your job is to summarise the text below. +Do not introduce yourself. Do not say anything else. +Just return a summary of the text in 3 clear sentences. + +Text to summarise: +{text} + +Summary:""", + "model_type": "qwen", + "temperature": 0.3 + } + response = requests.post(url, data=payload, headers=headers) + response.raise_for_status() + return response.json()["response"] + + +def translate_text(text, language): + """Translate text into the chosen Ugandan language.""" + url = f"{BASE_URL}/tasks/sunflower_simple" + headers = {"Authorization": f"Bearer {SUNBIRD_API_TOKEN}"} + payload = { + "instruction": f"""You are a translator. Translate the text below to {language}. +Do not introduce yourself. Do not explain anything. +Just return the translated text and nothing else. + +Text to translate: +{text} + +Translation:""", + "model_type": "qwen", + "temperature": 0.3 + } + response = requests.post(url, data=payload, headers=headers) + response.raise_for_status() + return response.json()["response"] + + +def text_to_speech(text, language): + """Convert translated text to audio and return the audio URL.""" + url = f"{BASE_URL}/tasks/tts" + speaker_id = LANGUAGE_SPEAKER_IDS[language] + payload = { + "text": text, + "speaker_id": speaker_id + } + response = requests.post(url, json=payload, headers=HEADERS) + print("TTS STATUS:", response.status_code) + print("TTS RESPONSE:", response.text) + response.raise_for_status() + return response.json()["output"]["audio_url"] \ No newline at end of file diff --git a/exercises/basics.py b/exercises/basics.py index 61113aca..d5b4b9a3 100644 --- a/exercises/basics.py +++ b/exercises/basics.py @@ -12,7 +12,17 @@ def collatz(n: int) -> List[int]: For example, if n = 3, the sequence of values is: 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 So, your function would return: [3, 10, 5, 16, 8, 4, 2, 1] """ - pass + sequence = [n] + while n!=1: + if n%2== 0: + n=n//2 + else: + n=n*3+1 + sequence.append(n) + + return sequence + + def distinct_numbers(numbers: List[int]) -> int: @@ -21,4 +31,5 @@ def distinct_numbers(numbers: List[int]) -> int: E.g if numbers = [2, 3, 2, 2, 3], then the answer is 2 since there are only 2 unique numbers: 2 and 3. """ - pass + return len(set(numbers)) + diff --git a/requirements.txt b/requirements.txt index 5571acf5..a8b4a8e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pytest requests python-dotenv +streamlit diff --git a/src/streamlit_app.py b/src/streamlit_app.py new file mode 100644 index 00000000..99d0b846 --- /dev/null +++ b/src/streamlit_app.py @@ -0,0 +1,40 @@ +import altair as alt +import numpy as np +import pandas as pd +import streamlit as st + +""" +# Welcome to Streamlit! + +Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:. +If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community +forums](https://discuss.streamlit.io). + +In the meantime, below is an example of what you can do with just a few lines of code: +""" + +num_points = st.slider("Number of points in spiral", 1, 10000, 1100) +num_turns = st.slider("Number of turns in spiral", 1, 300, 31) + +indices = np.linspace(0, 1, num_points) +theta = 2 * np.pi * num_turns * indices +radius = indices + +x = radius * np.cos(theta) +y = radius * np.sin(theta) + +df = pd.DataFrame({ + "x": x, + "y": y, + "idx": indices, + "rand": np.random.randn(num_points), +}) + +st.altair_chart(alt.Chart(df, height=700, width=700) + .mark_point(filled=True) + .encode( + x=alt.X("x", axis=None), + y=alt.Y("y", axis=None), + color=alt.Color("idx", legend=None, scale=alt.Scale()), + size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])), + )) \ No newline at end of file