Tag: Algorithm

  • Turned 40 and got layoff on April fools day 2024, Here is my senior iOS interview experience with Google, Meta and Amazon

    Turned 40 and got layoff on April fools day 2024, Here is my senior iOS interview experience with Google, Meta and Amazon

    I turned 40 and got layoff on April fools day.

    It’s not a success story but I want to share my iOS interview experience with Google, Meta and Amazon.

    Note

    • I signed NDA. This post does not contains any questions they asked.
    • I’m actively looking for a full-time iOS position. If anyone is interested in my experience, feel free to contact me.

    Preparation

    After I got layoff, I had a lot of time for preparation.

    I practice data structures, algorithms, design patterns, and iOS domain.

    Data Structures and Algorithms

    There are many useful resources. I choose leetcode and Udemy course.

    I paid

    Udemy

    I like Loony Corn’s course. I learned how to implement common data structures and algorithms.

    screenshot 2024 08 10 at 1.24.02e280afam

    I posted about basic data structure and algorithms

    Leetcode

    Is it worth to subscribe it? Yes. It provides questions filter by company. It was very useful.

    screenshot 2024 08 10 at 1.25.28e280afam

    After 50 days, I got top 6.9% of leetcoders solving problems badge

    screenshot 2024 08 10 at 1.32.26e280afam

    iOS domain

    I visit the following sites every day and post articles

    • Apple Official Document
    • HackingWithSwift

    Focused topics are

    • Swift Concurrency (DispatchQueue, Serial/Concurrent, Semaphore, Threading problems and more)
    • Swift fundamentals
    • UIView
    • Core Animation
    • Storage (User Defaults, Core Data, KeyChain and more)
    • Architecture

    Mobile System Design

    Behavior Interview

    I’ve summarized my work experience and data. I practice the STAR (Situation, Task, Action, Result) method to tell my story

    img 1277

    CV

    My CV pass the resume screening from Google, Meta and Amazon.

    Interview with Meta (London, L5 iOS Engineer)

    I applied to iOS engineer position without refer. After 1 month, I received email from HR.

    screenshot 2024 08 10 at 2.05.27e280afam

    After 1 day of screening interview with engineer, I was invited to final stage.

    facebook

    I was expecting offer at that time…

    L5 level Interview process was

    • HR conversation
    • Screening Interview
      • 1 session: 45min, 2 coding problem
    • Virtual Onsite
      • 1 session: 45min, behavior interview
      • 1 session: 45min, mobile system design
      • 3 session: 45min x 3, coding interview (each session 2 coding problems)

    In my experience with Meta, I learned a lot from interview. HR and Interviewers are friendly. HR guided me how to prepare iOS interviews. It helped me a lot.

    I was lucky because they didn’t ask leetcode hard problems. It was medium level and all the questions I solved before. So I solved all the coding problems but some questions I didn’t optimized well.

    I think my behavioral interview didn’t go well because it ended 10 minutes early.

    Interview with Amazon (Senior iOS Engineer)

    I applied to senior iOS position without refer. After 1.5 month, HR reached me.

    screenshot 2024 08 10 at 2.18.35e280afam

    After HR conversation, I invited phone screening interview.

    screenshot 2024 08 10 at 2.22.22e280afam

    After 4 day of phone screening interview, I was invited to final round.

    Interview process was

    • HR Conversation
    • Phone Screening Interview
      • 1 session: 60min Leadership Principle + Coding
    • Final Interview (60min x 5)
      • 1 session: 60min Leadership Principle
      • 3 session: 60min x 3, Leadership Principle + Coding
      • 1 session: 60+min, Mobile System Design

    The interview process is very similar to Meta’s. Only difference is all the interview sessions contains Leadership Principle around 15-25 mins.

    My experience with Amazon was good. They provided detailed preparation materials for the iOS position.

    After 5 days of final interview, HR called me to share feedbacks from interviewers. (I think If you pass the final rounds, they might send an email not a call)

    Interview with Google (iOS Engineer)

    Unlike the interviews with Meta and Amazon, to be honest, I had an interview with Google a few years ago, not in 2024.

    Interview process was

    • HR Hangout Chat
    • Phone Screening: 60 min, 2 coding problems
    • On Site

    In 2020, I was failed at phone screening interview.

    screenshot 2024 08 10 at 2.33.50e280afam
    screenshot 2024 08 10 at 2.34.48e280afam

    My interview experience with Google was good. They used Google Doc for coding interview.

    I remember difficulty of coding problem was leetcode medium level.

    Conclusion

    Initially, I was concerned about my English communication skills. However, I successfully passed the HR screening and technical interviews, and even received positive feedback from behavioral interviews. If you’re worried about your English proficiency, I encourage you not to dwell on it too much. Instead, focus on effectively conveying your experiences, clarifying questions, and demonstrating your passion.

    The coding rounds were neither excessively difficult nor too easy – they were of medium difficulty. I recommend practicing basic data structures and algorithms such as Heaps, Trees, Linked Lists, LRU caches, and Graphs. Most FAANG companies don’t require writing compilable code; they’re more interested in your communication skills, problem clarification abilities, approach to breaking down problems, and how you optimize time and space complexity.

    For iOS-specific questions, unlike other companies, these tech giants are more interested in mobile system design rather than specific knowledge of the UIKit framework.

    For the system design round, I strongly advise practicing extensively. Focus on drawing and explaining high-level components, their interactions, and server communication. Emphasize mobile-side system design.

    In behavioral interviews, structure your responses using the STAR format, drawing from your experiences. Share your best stories if possible, and avoid repeating anecdotes in the final stages.

    Although I didn’t receive an offer, I hope my experience proves helpful to those considering or preparing for similar interviews.

    I’ve also created a GitHub repository to share information about companies that are friendly to native mobile development. If you know of such companies, please feel free to contribute to this resource.

    Best of luck to all mobile engineers in your future endeavors!

  • Core Animation Basics

    Core Animation Basics

    ✍️ Note

    What is Core Animation

    Core Animation use bitmaps.

    • UIView -> Layer -> Bitmap -> Hardware manipulation

    Core Animation provides a general purpose system for animating views and other visual elements of your app. Core Animation is not a replacement for your app’s views. Instead, it is a technology that integrates with views to provide better performance and support for animating their content. It achieves this behavior by caching the contents of views into bitmaps that can be manipulated directly by the graphics hardware. In some cases, this caching behavior might require you to rethink how you present and manage your app’s content, but most of the time you use Core Animation without ever knowing it is there. In addition to caching view content, Core Animation also defines a way to specify arbitrary visual content, integrate that content with your views, and animate it along with everything else.

    You use Core Animation to animate changes to your app’s views and visual objects. Most changes relate to modifying the properties of your visual objects. For example, you might use Core Animation to animate changes to a view’s position, size, or opacity. When you make such a change, Core Animation animates between the current value of the property and the new value you specify. You would typically not use Core Animation to replace the content of a view 60 times a second, such as in a cartoon. Instead, you use Core Animation to move a view’s content around the screen, fade that content in or out, apply arbitrary graphics transformations to the view, or change the view’s other visual attributes.

    Apple Document

    Layers Provide the Basis for Drawing and Animations

    Layer objects are 2D surfaces organized in a 3D space and are at the heart of everything you do with Core Animation. Like views, layers manage information about the geometry, content, and visual attributes of their surfaces. Unlike views, layers do not define their own appearance. A layer merely manages the state information surrounding a bitmap. The bitmap itself can be the result of a view drawing itself or a fixed image that you specify. For this reason, the main layers you use in your app are considered to be model objects because they primarily manage data. This notion is important to remember because it affects the behavior of animations.

    Apple Document

    screenshot 2024 07 03 at 7.24.07e280afpm

    Most layers do not do any actual drawing in your app. Instead, a layer captures the content your app provides and caches it in a bitmap, which is sometimes referred to as the backing store. When you subsequently change a property of the layer, all you are doing is changing the state information associated with the layer object. When a change triggers an animation, Core Animation passes the layer’s bitmap and state information to the graphics hardware, which does the work of rendering the bitmap using the new information, as shown in Figure 1-1. Manipulating the bitmap in hardware yields much faster animations than could be done in software.

    Because it manipulates a static bitmap, layer-based drawing differs significantly from more traditional view-based drawing techniques. With view-based drawing, changes to the view itself often result in a call to the view’s drawRect: method to redraw content using the new parameters. But drawing in this way is expensive because it is done using the CPU on the main thread. Core Animation avoids this expense by whenever possible by manipulating the cached bitmap in hardware to achieve the same or similar effects.

    Although Core Animation uses cached content as much as possible, your app must still provide the initial content and update it from time to time. There are several ways for your app to provide a layer object with content, which are described in detail in Providing a Layer’s Contents.

    Apple Document

    Rendering Process

    screenshot 2024 07 04 at 4.19.49e280afpm

    Layers Can Be Manipulated in Three Dimensions

    Every layer has two transform matrices that you can use to manipulate the layer and its contents. The transform property of CALayer specifies the transforms that you want to apply both to the layer and its embedded sublayers. Normally you use this property when you want to modify the layer itself. For example, you might use that property to scale or rotate the layer or change its position temporarily. The sublayerTransform property defines additional transformations that apply only to the sublayers and is used most commonly to add a perspective visual effect to the contents of a scene.

    Transforms work by multiplying coordinate values through a matrix of numbers to get new coordinates that represent the transformed versions of the original points. Because Core Animation values can be specified in three dimensions, each coordinate point has four values that must be multiplied through a four-by-four matrix, as shown in Figure 1-7. In Core Animation, the transform in the figure is represented by the CATransform3D type. Fortunately, you do not have to modify the fields of this structure directly to perform standard transformations. Core Animation provides a comprehensive set of functions for creating scale, translation, and rotation matrices and for doing matrix comparisons. In addition to manipulating transforms using functions, Core Animation extends key-value coding support to allow you to modify a transform using key paths. For a list of key paths you can modify, see CATransform3D Key Paths.

    screenshot 2024 07 04 at 4.21.24e280afpm
    screenshot 2024 07 04 at 4.21.35e280afpm

    Figure 1-8 shows the matrix configurations for some of the more common transformations you can make. Multiplying any coordinate by the identity transform returns the exact same coordinate. For other transformations, how the coordinate is modified depends entirely on which matrix components you change. For example, to translate along the x-axis only, you would supply a nonzero value for the tx component of the translation matrix and leave the ty and tz values to 0. For rotations, you would provide the appropriate sine and cosine values of the target rotation angle.

    Apple

    Layer Tree

    An app using Core Animation has three sets of layer objects. Each set of layer objects has a different role in making the content of your app appear onscreen:

    • Objects in the model layer tree (or simply “layer tree”) are the ones your app interacts with the most. The objects in this tree are the model objects that store the target values for any animations. Whenever you change the property of a layer, you use one of these objects.
    • Objects in the presentation tree contain the in-flight values for any running animations. Whereas the layer tree objects contain the target values for an animation, the objects in the presentation tree reflect the current values as they appear onscreen. You should never modify the objects in this tree. Instead, you use these objects to read current animation values, perhaps to create a new animation starting at those values.
    • Objects in the render tree perform the actual animations and are private to Core Animation.

    Each set of layer objects is organized into a hierarchical structure like the views in your app. In fact, for an app that enables layers for all of its views, the initial structure of each tree matches the structure of the view hierarchy exactly. However, an app can add additional layer objects—that is, layers not associated with a view—into the layer hierarchy as needed. You might do this in situations to optimize your app’s performance for content that does not require all the overhead of a view. Figure 1-9 shows the breakdown of layers found in a simple iOS app. The window in the example contains a content view, which itself contains a button view and two standalone layer objects. Each view has a corresponding layer object that forms part of the layer hierarchy.

    Apple

    screenshot 2024 07 04 at 4.23.02e280afpm
    screenshot 2024 07 04 at 4.23.37e280afpm

    For every object in the layer tree, there is a matching object in the presentation and render trees, as shown in Figure 1-10. As was previously mentioned, apps primarily work with objects in the layer tree but may at times access objects in the presentation tree. Specifically, accessing the presentationLayer property of an object in the layer tree returns the corresponding object in the presentation tree. You might want to access that object to read the current value of a property that is in the middle of an animation.

    Important: You should access objects in the presentation tree only while an animation is in flight. While an animation is in progress, the presentation tree contains the layer values as they appear onscreen at that instant. This behavior differs from the layer tree, which always reflects the last value set by your code and is equivalent to the final state of the animation.

    Apple

    The Relationship Between Layers and Views

    Layers are not a replacement for your app’s views—that is, you cannot create a visual interface based solely on layer objects. Layers provide infrastructure for your views. Specifically, layers make it easier and more efficient to draw and animate the contents of views and maintain high frame rates while doing so. However, there are many things that layers do not do. Layers do not handle events, draw content, participate in the responder chain, or do many other things. For this reason, every app must still have one or more views to handle those kinds of interactions.

    In iOS, every view is backed by a corresponding layer object but in OS X you must decide which views should have layers. In OS X v10.8 and later, it probably makes sense to add layers to all of your views. However, you are not required to do so and can still disable layers in cases where the overhead is unwarranted and unneeded. Layers do increase your app’s memory overhead somewhat but their benefits often outweigh the disadvantage, so it is always best to test the performance of your app before disabling layer support.

    When you enable layer support for a view, you create what is referred to as a layer-backed view. In a layer-backed view, the system is responsible for creating the underlying layer object and for keeping that layer in sync with the view. All iOS views are layer-backed and most views in OS X are as well. However, in OS X, you can also create a layer-hosting view, which is a view where you supply the layer object yourself. For a layer-hosting view, AppKit takes a hands off approach with managing the layer and does not modify it in response to view changes.

    Note: For layer-backed views, it is recommended that you manipulate the view, rather than its layer, whenever possible. In iOS, views are just a thin wrapper around layer objects, so any manipulations you make to the layer usually work just fine. But there are cases in both iOS and OS X where manipulating the layer instead of the view might not yield the desired results. Wherever possible, this document points out those pitfalls and tries to provide ways to help you work around them.

    In addition to the layers associated with your views, you can also create layer objects that do not have a corresponding view. You can embed these standalone layer objects inside of any other layer object in your app, including those that are associated with a view. You typically use standalone layer objects as part of a specific optimization path. For example, if you wanted to use the same image in multiple places, you could load the image once and associate it with multiple standalone layer objects and add those objects to the layer tree. Each layer then refers to the source image rather than trying to create its own copy of that image in memory.

    For information about how to enable layer support for your app’s views, see Enabling Core Animation Support in Your App. For information on how to create a layer object hierarchy, and for tips on when you might do so, see Building a Layer Hierarchy.

    Apple

    If you learn more about Core Animation? Visit official document

  • iOS, Useful Bit Manipulation Techniques

    iOS, Useful Bit Manipulation Techniques

    ✍️ Note

    Some codes and contents are sourced from Udemy. This post is for personal notes where I summarize the original contents to grasp the key concepts (🎨 some images I draw it)

    Bit Manipulations

    There are 6 types of bit manipulations

    • OR
      • 1 | 0 -> 1
      • 1 | 1 -> 1
      • 0 | 0 -> 0
    • And
      • 1 & 0 -> 0
      • 1 & 1 -> 1
      • 0 & 0 -> 0
    • XOR (if there is only 1 then It’s result will be 1)
      • 1 ^ 0 -> 1
      • 1 ^ 1 -> 0
      • 0 ^ 0 -> 0
    • Not
      • ~1 -> 0
      • ~0 -> 1
    • Left Shift
      • 1 << 1 -> 0000 0010
    • Right Shift (See Apple’s document)
      • 1 >> 1 -> 000 0000

    If you want to learn more details, visit Apple official documents

    Example 1. Find if the N-th bit of a byte is 1

    screenshot 2024 06 09 at 5.02.29e280afpm

    To check N-th bit, use checkBit.

    func is1Bit(number: Int, at: Int) -> Bool {
        var checkBit = 1
        checkBit <<= at
        var result = number & checkBit
        return result == checkBit
        
    }
    let number = 789
    String(number, radix: 2)
    
    //index 0, 2, 4, 8, 9 bit is 1
    is1Bit(number: number, at: 0)
    is1Bit(number: number, at: 2)
    is1Bit(number: number, at: 4)
    is1Bit(number: number, at: 8)
    is1Bit(number: number, at: 9)
    
    //index 1, bit is 0
    is1Bit(number: number, at: 1)

    Example 2. Set N-th bit as 1

    screenshot 2024 06 09 at 8.54.14e280afpm
    var number = 789
    func set1Bit(number: inout Int, at: Int) {
        var checkBit = 1
        checkBit <<= at
        String(number, radix: 2)
        String(checkBit, radix: 2)
        number |= checkBit
        String(number, radix: 2)
    }
    print("Before: \(number)")
    set1Bit(number: &number, at: 1)
    print("After: \(number)")

    Set N-th bit as 1 is very easy.

    • Create checkBit
    • Apply OR bit operation to the number

    Input number is 789

    When you set bit 1 at index 1, the result will be +2

    Example 3. Print and count 1’s bits

    screenshot 2024 06 09 at 9.04.29e280afpm

    In Swift, There is convenient API to print binary from Int

    • String(789, radix: 2)

    Alternative way, we can print all the bit information from right to left using checkBit

    func printBitsAndReturn1sBits(_ number: UInt) -> Int {
        var input = number
        //Use unsinged Int, because signed int hold right most bit as signed information
        var checkBit: UInt = 1
        //MemoryLayout returns byte size of Int. It depends on architecture. 4 byte(32 bit) or 8 byte (64 bit)
        //To get bits we need to multiply 8 and -1 (because index starts from 0)
        let bits = MemoryLayout<UInt>.size * 8 - 1
        checkBit <<= bits
        
        var count = 0
        while checkBit != 0 {
            let rightBit = number & checkBit
            if rightBit == checkBit {
                print("1", terminator: " ")
                count += 1
            }
            else {
                print("0", terminator: " ")
            }
            //Right shift
            checkBit >>= 1
        }
        return count
    }
    printBitsAndReturn1sBits(789)
    

    Above approach, The time complexity is O(Number of Bit)

    We can optimize it by using subtract by 1. It’s time complexity will be O(number of 1’s) -> Assume we ignore print all the bit information. Just focusing on get 1’s count.

    screenshot 2024 06 09 at 9.15.44e280afpm
    screenshot 2024 06 09 at 9.28.45e280afpm
    func get1sBits(_ number: UInt) -> Int {
        var input = number
        var count = 0
        while input != 0 {
            input &= (input - 1)
            count += 1
        }
        return count
    }
    get1sBits(789)

    Example 4. Reverse the bits an Integer

    screenshot 2024 06 09 at 9.51.18e280afpm
    func reversedBit(_ number: UInt) -> UInt {
        var number = number
        print("Input: \(number), Bits: \(String(number, radix: 2))")
        var reversedNumber: UInt = 0
        //Count: Get count of bit of the number, 789 has 10 bit
        var count = String(number, radix: 2).count - 1
        while number != 0 {
            let leftMostBit = number & 1
            reversedNumber = reversedNumber | leftMostBit
            reversedNumber <<= 1
            number >>= 1
            count -= 1
        }
        reversedNumber <<= count
        return reversedNumber
    }
    
    let result = reversedBit(789)
    print("Ourput: \(result), Bits: \(String(result, radix: 2))")
    
    screenshot 2024 06 09 at 10.06.27e280afpm
    screenshot 2024 06 09 at 9.52.56e280afpm
  • UIKit, Learn Mobile System Design from Apple’s Sample Project

    UIKit, Learn Mobile System Design from Apple’s Sample Project

    Did you know Apple provides Sample Code? I learned App Architecture from Apple’s Sample Projects.

    In this post, I drew the high level diagram of the Apple’s Sample Code.

    Check Apple’s Sample Code

    screenshot 2024 05 27 at 4.47.34e280afpm

    Visit Apple Developer’s site and filter by Sample Code

    https://developer.apple.com/documentation/uikit

    Project 1. Restoring Your App’s State

    screenshot 2024 05 27 at 4.54.55e280afpm

    https://developer.apple.com/documentation/uikit/uiscenedelegate/restoring_your_app_s_state

    This sample project demonstrates how to preserve your appʼs state information and restore the app to that previous state on subsequent launches. During a subsequent launch, restoring your interface to the previous interaction point provides continuity for the user, and lets them finish active tasks quickly.

    When using your app, the user performs actions that affect the user interface. For example, the user might view a specific page of information, and after the user leaves the app, the operating system might terminate it to free up the resources it holds. The user should be able to return to where they left off — and UI state restoration is a core part of making that experience seamless.

    This sample app demonstrates the use of state preservation and restoration for scenarios where the system interrupts the app. The sample project manages a set of products. Each product has a title, an image, and other metadata you can view and edit. The project shows how to preserve and restore a product in its DetailParentViewController.

    The sample supports two state preservation approaches. In iOS 13 and later, apps save the state for each window scene using NSUserActivity objects. In iOS 12 and earlier, apps preserve the state of their user interfaces by saving and restoring the configuration of view controllers.

    For scene-based apps, UIKit asks each scene to save its state information using an NSUserActivity object. NSUserActivity is a core part of modern state restoration with UIScene and UISceneDelegate. In your own apps, you use the activity object to store information needed to recreate your scene’s interface and restore the content of that interface. If your app doesn’t support scenes, use the view-controller-based state restoration process to preserve the state of your interface instead.

    By Apple

    High Level System Design

    screenshot 2024 05 27 at 6.40.12e280afpm 1

    It uses Singleton DataModelManager.

    All the ViewControllers access singleton DataModelManager directly to get data.

    The logics are focused on how to save and restore the states using UIStateRestoring.

    🌟 Interesting points

    • It has two scenes.
      • Main Scene
      • Image Scene
    • AppDelegate handles where user navigate to the scene by checking NSUserActivity

    Project Folder structures

    screenshot 2024 05 27 at 6.27.52e280afpm 1

    Project 2. Supporting Multiple Windows on iPad

    screenshot 2024 06 04 at 12.01.13e280afpm

    https://developer.apple.com/documentation/uikit/uiscenedelegate/supporting_multiple_windows_on_ipad

    screenshot 2024 06 04 at 11.52.30e280afam

    This project is very simple. Most of the functions are belong to ViewController.

    Learned from this project

    • UICollectionViewCell’s reuseIdentifier is declared in PhotoCell.
      • static reuseIdentifier
    • Function names
      • initialize the UI codes -> func configure()
      • initialize the storyboard -> loadFromStoryboard()
    • Class / Struct name
      • I found Apple’s sample codes usually naming the noun + manager for class / struct.
        • PhotoManager
    • Conveniently use Static / Singletons
      • UserActivity has multiple static properties
    screenshot 2024 06 04 at 11.54.19e280afam

    Project 3. Supporting desktop-class features in your iPad app

    screenshot 2024 06 04 at 1.51.25e280afpm

    https://developer.apple.com/documentation/uikit/app_and_environment/supporting_desktop-class_features_in_your_ipad_app

    screenshot 2024 06 04 at 3.02.41e280afpm
    screenshot 2024 06 04 at 2.02.54e280afpm
    screenshot 2024 06 04 at 2.04.01e280afpm
  • iOS, Graph(unweighted and weighted) – Shortest Path Algorithms (Dijkstra, Bellman-Ford)

    iOS, Graph(unweighted and weighted) – Shortest Path Algorithms (Dijkstra, Bellman-Ford)

    ✍️ Note

    Some codes are sourced from Loony Corn’s Udemy Course (https://www.udemy.com/user/janani-ravi-2/). This post is for personal notes where I summarize the original contents to grasp the key concepts

    screenshot 2024 05 21 at 1.37.03e280afpm

    Graph – Shortest Path Algorithms

    Given a graph G with vertices V and edges E

    Choose any vertex S – the source

    What is the shortest path from S to a specific destination vertex D?

    It is the path with the fewest hops to get from S to D

    https://www.udemy.com/course/from-0-to-1-data-structures/learn/lecture/8506692#content

    screenshot 2024 05 20 at 4.08.34e280afam
    • There can be multiple paths to the same vertex with different distances
    • Getting the shortest path is very similar to BFS
    • We need to set up something called a Distance Table
    • A Table of all vertices in a Graph

    Type 1. Unweighted Graph, Find Shortest Path using Distance Table

    screenshot 2024 05 20 at 5.59.52e280afpm
    screenshot 2024 05 20 at 6.00.04e280afpm
    screenshot 2024 05 20 at 6.00.15e280afpm
    screenshot 2024 05 20 at 6.00.29e280afpm
    screenshot 2024 05 20 at 6.00.44e280afpm
    screenshot 2024 05 20 at 6.00.54e280afpm
    screenshot 2024 05 20 at 6.01.07e280afpm

    Type 2. Weighted Graph, Dijkstra Algorithm: Find Shortest Path

    Unlike unweighted graph, each edges having a weight or number. Its value can be negative or positive.

    Dijkstra Algorithm is used for positive weights.

    Bellman-Ford is used for negative weights too.

    The algorithm here is quite similar to what we have discussed before in finding the shortest path in an Unweighted Graph (see Type 1 example)

    There are 3 major differences

    • We still use the Distance Table to store information
      • The distance from a Node now has to account for the weight of the edges traversed
      • distance[neighbors] = distance[vertex] + weight_of_edge[vertex, neighbor]
    • Each vertex has neighbors
      • In a weighted graph – visit the neighbour which is connected by an edge with the Lowest Weight
      • Use a priority queue to implement this
        • To get the next vertex in the path, pop the element with the Lowest Weight -> It is called Greedy Algorithm
        • What is a Greedy Algorithm?
          • Greedy algorithms often fail to find the best solution
          • A greedy algorithm builds up a solution step by step
          • A every step it only optimizes for that particular step – It does not look at the overall problem
          • They do not operate on all the data so they may not see the Big Picture
          • Greedy algorithms are used for optimization problems
          • Greedy solutions are especially useful to find approximate solutions to very hard problems which are close to impossible to solve (Technical term NP Hard) E.g, The Traveling Salesman Problem
      • It’s possible to visit a vertex more than once
        • We check whether new distance (via the alternative route) is smaller than old distance
        • new distance = distance[vertex] + weight of edge[vertex, neighbour]
          • If new distance < original distance [neighbour] then Update the distance table. Put the vertex in Queue (Once Again)
          • Relaxation

    Finding the shortest path in a weighted graph is called Dijkstra’s Algorithm

    screenshot 2024 05 21 at 2.32.30e280afpm
    screenshot 2024 05 21 at 2.32.42e280afpm
    screenshot 2024 05 21 at 2.32.54e280afpm
    screenshot 2024 05 21 at 2.33.06e280afpm
    screenshot 2024 05 21 at 2.34.33e280afpm
    screenshot 2024 05 21 at 2.34.43e280afpm

    Shortest Path in Weighted Graph

    The algorithm’s efficiency depends on how priority queue is implemented. It’s two operations – Updating the queue and Popping out from the queue determines the running time

    Running Time is : O(ELogV) <- If Binary Heaps are used for priority queue

    Running time is : O(E + V*V) <- If array is used for priority queue

  • What sorting algorithm does Swift use in iOS?

    What sorting algorithm does Swift use in iOS?

    Sort algorithm in Swift Foundation is TimSort (Insertion Sort + Merge Sort)

    I wrote posts about Insertion Sort and Merge Sort.

    You can check How to implement sorting algorithm in Swift

    screenshot 2024 05 14 at 1.47.55e280afpm
    screenshot 2024 05 15 at 9.22.59e280afpm

    You can check the full source code at here

    https://github.com/apple/swift/blob/main/stdlib/public/core/Sort.swift

    TimSort

    Sorts the elements of this buffer according to areInIncreasingOrder,
    using a stable, adaptive merge sort.
    The adaptive algorithm used is Timsort, modified to perform a straight
    merge of the elements using a temporary buffer.

    https://github.com/apple/swift/blob/main/stdlib/public/core/Sort.swift

    Timsort is a hybridstable sorting algorithm, derived from merge sort and insertion sort, designed to perform well on many kinds of real-world data. It was implemented by Tim Peters in 2002 for use in the Python programming language. The algorithm finds subsequences of the data that are already ordered (runs) and uses them to sort the remainder more efficiently. This is done by merging runs until certain criteria are fulfilled. Timsort was Python’s standard sorting algorithm from version 2.3 to version 3.10,[5] and is used to sort arrays of non-primitive type in Java SE 7,[6] on the Android platform,[7] in GNU Octave,[8] on V8,[9] Swift,[10] and inspired the sorting algorithm used in Rust.[11]

    It uses techniques from Peter McIlroy’s 1993 paper “Optimistic Sorting and Information Theoretic Complexity”.

    https://en.wikipedia.org/wiki/Timsort

  • iOS, Find least common ancestor from 2 UIViews

    iOS, Find least common ancestor from 2 UIViews

    Find the least common ancestor.

    UIView is a tree data structure. Each node (UIView) can have multiple subviews.

    To find the least common ancestor, You can use either recursive calling or using a superview property.

    screenshot 2024 05 08 at 12.52.27e280afam

    UIView provide a useful functions and properties

    • subviews: [UIView]
    • superview: UIView?
    • func isDescendant(of: UIView) -> Bool

    Let’s create a View Trees.

     @discardableResult
        func insertView(_ at: UIView, tag: Int) -> UIView {
            let node = UIView()
            node.tag = tag
            at.addSubview(node)
            return node
        }
    
    func setupViewTree() {
            //insert level 1
            let tag0view = insertView(view, tag: 11)
            insertView(view, tag: 1)
            let tag2view = insertView(view, tag: 2)
            
            //insert level 2
            insertView(tag0view, tag: 3)
            let tag4view = insertView(tag0view, tag: 4)
            
            insertView(tag2view, tag: 5)
            let tag6view = insertView(tag2view, tag: 6)
            insertView(tag2view, tag: 7)
            
            //insert level 3
            insertView(tag4view, tag: 8)
            insertView(tag6view, tag: 9)
        }
    
    

    Helper function – Find a view with Tag

    func findView(_ at: UIView, tag: Int) -> UIView? {
            //Base case
            if at.tag == tag {
                return at
            }
            guard !at.subviews.isEmpty else {
                return nil
            }
            let subviews = at.subviews
            var targetView: UIView?
            for subview in subviews {
                if let view = findView(subview, tag: tag) {
                    targetView = view
                    break
                }
            }
            return targetView
        }

    Approach 1. Use Recursive Call

    If you want to see this logic step by step then visit my previous post.

    This approach is start from rootView. So we have to know what’s the root view.

    func findLeastCommonParentFromRootView(rootView: UIView, targetView1: UIView?, targetView2: UIView?) -> UIView? {
            guard let targetView1, let targetView2 else {
                return nil
            }
            return recursiveFindLeastCommonParentUsing(
                rootView: rootView,
                targetView1: targetView1,
                targetView2: targetView2
            )
        }
    
    
    private func recursiveFindLeastCommonParentUsing(
            rootView: UIView?,
            targetView1: UIView,
            targetView2: UIView
        ) -> UIView? {
            guard let rootView else {
                return nil
            }
            //Base case, prevent search targetView's childs
            if rootView == targetView1 || rootView == targetView2 {
                return rootView
            }
            let subviews = rootView.subviews
            var ancestors = [UIView]()
            for view in subviews {
                if let ancestorView = recursiveFindLeastCommonParentUsing(rootView: view, targetView1: targetView1, targetView2: targetView2) {
                    ancestors.append(ancestorView)
                }
            }
            if ancestors.contains(targetView1), ancestors.contains(targetView2) {
                return rootView
            }
            if ancestors.isEmpty {
                return nil
            }
            return ancestors.last
        }
    

    Approach 2. Use superview

    This approach is much simpler than approach 1. It uses a superview. Not calling a function recursively.

    private func findLeastCommonParentWithoutRootView(targetView1: UIView?, targetView2: UIView?) -> UIView? {
            guard targetView1 != nil, targetView2 != nil else {
                return nil
            }
            var target1 = targetView1
            var target2 = targetView2
            
            while target1?.superview != nil, target2?.superview != nil {
                let targetView1SuperView = target1?.superview
                var targetView2SuperView = target2?.superview
                while targetView2SuperView?.superview != nil {
                    if targetView1SuperView == targetView2SuperView {
                        return targetView2SuperView
                    }
                    targetView2SuperView = targetView2SuperView?.superview
                }
                target1 = target1?.superview
                target2 = target2?.superview
            }
            
            if target1?.subviews != nil {
                while target1?.superview != nil {
                    target1 = target1?.superview
                }
            }
            if target2?.subviews != nil {
                while target2?.superview != nil {
                    target2 = target2?.superview
                }
            }
            if target1 == target2 {
                return target1
            }
            else {
                return nil
            }
        }

    If each target view’s rootView is different then It will returns nil

    Approach 3. Use isDescendant

    func findLeastCommonParentUsingIsDescendantView(rootView: UIView, targetView1: UIView?, targetView2: UIView?) -> UIView? {
            guard let targetView1, let targetView2 else {
                return nil
            }
            var root: UIView = rootView
            var stack = [UIView]()
            //Base case
            guard targetView1.isDescendant(of: root), targetView2.isDescendant(of: root) else {
                return nil
            }
            while !root.subviews.isEmpty {
                for subview in root.subviews {
                    if targetView1 != subview, targetView1.isDescendant(of: subview), targetView2 != subview, targetView2.isDescendant(of: subview) {
                        stack.append(subview)
                    }
                }
                if let last = stack.last, root != last {
                    root = last
                }
                else {
                    break
                }
            }
            return stack.last ?? root
        }

    Test all 3 approaches

     override func viewDidLoad() {
            super.viewDidLoad()
            setupViewTree()
            let result = findLeastCommonParentFromRootView(
                rootView: view,
                targetView1: findView(view, tag: 5),
                targetView2: findView(view, tag: 9)
            )
            print("🟢 Approach 1 Least common parent: \(result?.tag)")
            
            let result2 = findLeastCommonParentWithoutRootView(
                targetView1: findView(view, tag: 5),
                targetView2: findView(view, tag: 9)
            )
            print("🟢 Approach 2  Least common parent: \(result2?.tag)")
            
            let result3 = findLeastCommonParentUsingIsDescendantView(
                rootView: view,
                targetView1: findView(view, tag: 5),
                targetView2: findView(view, tag: 9)
            )
            print("🟢 Approach 3  Least common parent: \(result3?.tag)")
        }
    screenshot 2024 05 08 at 1.28.38e280afam

  • iOS, Implement Least Recently Used Cache

    iOS, Implement Least Recently Used Cache

    ✍️ Note

    Some codes are sourced from Holczer Balazs’s Udemy Course (https://www.udemy.com/course/algorithms-and-data-structures/learn/lecture/9779302#content). This post is for personal notes where I summarize the original contents to grasp the key concepts

    Least Recently Used Cache

    We want to access the recently used items

    • For example URLs very fast so in O(1) time complexity and discard least recently used ones
    • Naive Approach: Use a single hash table and we can achieve put() and get() operations in O(1) but memory complexity is not favorable
    • Splay trees are working fine but again we store all the items in a tree-like structure
    • LRU caches use (usually) doubly linked lists to achieve this goal + we have to use hash tables as well to boost linked list!!

    https://www.udemy.com/course/algorithms-and-data-structures/learn/lecture/9779306#content

    screenshot 2024 05 01 at 1.15.55e280afpm
    public class Node {
        public var prev: Node?
        public var next: Node?
        public var id: String
        public var data: String
        
        public init(prev: Node? = nil, next: Node? = nil, id: String, data: String) {
            self.prev = prev
            self.next = next
            self.id = id
            self.data = data
        }
    }
    public class LRUCacheDoublyLinkedList {
        private var size: Int = 0
        let capacity: Int
        private var head: Node?
        private var tail: Node?
        
        
        private var dict: [String: Node] = [:]
        
        public init(capacity: Int) {
            self.capacity = capacity
        }
        
        //Update the node to be the head
        private func update(_ node: Node) {
            let prev = node.prev
            let next = node.next
            
            //It is a middle node in the list
            if prev != nil {
                prev?.next = next
            }
            else {
                //This case It is first node(head)
                head = next
            }
            //If it is not last node
            if next != nil {
                next?.prev = prev
            }
            else {
                //It is the last node (tail)
                tail = prev
            }
            add(node)
        }
        
        //get item with ID and move to the head because It used
        public func get(_ id: String) -> Node? {
            guard let node = dict[id] else {
                return nil
            }
            update(node)
            return node
        }
        
        //Always add node at Head
        private func add(_ node: Node) {
            node.next = head
            if head != nil {
                head?.prev = node
            }
            //Set new head node
            head = node
            
            //If there is 1 node in the list, it is the head as well as the tail
            if tail == nil {
                tail = node
            }
            dict[node.id] = node
        }
        
        private func removeTail() {
            let tailId = tail?.id
            var cachedTail: Node?
            if let tailId = tail?.id, let lastNode = dict[tailId] {
                cachedTail = lastNode
            }
            tail = tail?.prev
            if tail != nil {
                tail?.next = nil
            }
            
            if let tailId {
                dict.removeValue(forKey: tailId)
            }
            cachedTail = nil
        }
        
        public func put(id: String, data: String) {
            if let node = dict[id] {
                node.data = data
                update(node)
                return
            }
            
            //the data is not present in the cache so insert
            let newNode = Node(id: id, data: data)
            
            //Check cache size
            if size < capacity {
                size += 1
                //insert into the cache and set it to be the head node
                add(newNode)
            }
            else {
                //cache is full, remove the last item and insert new one
                removeTail()
                add(newNode)
            }
        }
        
        public func printAll() {
            var pointNode = head
            while pointNode != nil {
                if let data = pointNode?.data {
                    print("\(data)", terminator: pointNode?.next != nil ? " -> " : "")
                }
                pointNode = pointNode?.next
            }
            print("\n")
        }
    }
    
    screenshot 2024 04 26 at 2.35.33e280afpm
  • iOS, General Programming Problems

    iOS, General Programming Problems

    ✍️ Note

    Some codes are sourced from Loony Corn’s Udemy Course (https://www.udemy.com/user/janani-ravi-2/). This post is for personal notes where I summarize the original contents to grasp the key concepts

    General Programming Problems

    Often coding interviews involve problems which do not have complicated algorithms or data structures – These are straight programming problems

    They test your ability to work through details and get the edge cases right

    The bad thing about them is that they involve lots of cases which you need to work through – They tend to be frustrating

    The great thing about them is that if you are organised and systematic in your thought process it is reasonably straightforward to get them right

    These are problems you should nail. All they need is practice

    None of them require any detailed knowledge of standard algorithms

    We’ll solve 8 general programming problems here – They all use arrays or simple data structures which you create

    https://www.udemy.com/course/break-away-coding-interviews-1/learn/lecture/8462914#overview

    Example 1. Check whether a given string is a palindrome

    Palindromes are strings which read the same when read forwards or backwards

    You can reverse all the letters in a palindrome and get the original string

    Examples of palindromes are

    • MADAM, REFER, LONELY TYLENOL

    Note

    • the string can have spaces, ignore spaces in the palindrome check, the spaces can all be collapsed
    • The check is Case-Insenstive
    screenshot 2024 04 23 at 2.29.30e280afpm
    public func isPalindrome(_ input: String) -> Bool {
        var firstIndex = 0
        var lastIndex = input.count - 1
        let lowerCasedInput = Array(input.lowercased())
        print(lowerCasedInput)
        while firstIndex < lastIndex {
            var first: Character? = lowerCasedInput[firstIndex]
            var last: Character? = lowerCasedInput[lastIndex]
            
            while lowerCasedInput[firstIndex] == " " {
                firstIndex += 1
                first = lowerCasedInput[firstIndex]
            }
            
            while lowerCasedInput[lastIndex] == " " {
                lastIndex -= 1
                last = lowerCasedInput[lastIndex]
            }
            
            print("First char: \(first) Last char: \(last)")
            if first != last {
                return false
            }
            firstIndex += 1
            lastIndex -= 1
            
            print("First index: \(firstIndex)")
            print("Last index: \(lastIndex)")
        }
        return true
    }
    
    isPalindrome("MaIay a Ia m") //True
    isPalindrome("MADAM") //True
    isPalindrome("REFER") //True
    isPalindrome("LONELY TYLENOL") //True
    isPalindrome("LONELY TYLENOLF") //False
    
    screenshot 2024 04 23 at 3.20.50e280afpm

    Example 2. Find all points within a certain distance of another point

    Find points (Given X, Y coordinates) which are within a certain distance of another point. The distance and the central point is specified as an argument to the function which computes the points in range

    Example. All points within a distance 10 of (0,0) will include the point at (3, 4) but not include the point at (12, 13)

    Hint: If you are using an OO Programming language set up an entity which represents a point and contains methods within it to find the distance from another point.

    screenshot 2024 04 24 at 12.47.52e280afpm
    screenshot 2024 04 24 at 1.01.53e280afpm
    public struct Point {
        public let x: Double
        public let y: Double
        
        public init(x: Double, y: Double) {
            self.x = x
            self.y = y
        }
    }
    
    extension Point {
        public func getDistance(_ otherPoint: Point) -> Double {
            //Distance: sqrt(x^2 + y^2)
            return sqrt(pow(otherPoint.x - x, 2) + pow(otherPoint.y - y, 2))
        }
        
        public func isWithinDistance(_ otherPoint: Point, distance: Double) -> Bool {
            let distanceX = abs(x - otherPoint.x)
            let distanceY = abs(y - otherPoint.y)
            if distanceX > distance || distanceY > distance {
                return false
            }
            return getDistance(otherPoint) <= distance
        }
    }
    
    public func getPointsWithinDistance(points: [Point], center: Point, distance: Double) -> [Point] {
        var withinPoints = [Point]()
        for point in points {
            if point.isWithinDistance(center, distance: distance) {
                withinPoints.append(point)
            }
        }
        print("Points within \(distance) of point x: \(center.x) y: \(center.y)")
        for p in withinPoints {
            print("Point: x: \(p.x) y: \(p.y)")
        }
        return withinPoints
    }
    
    screenshot 2024 04 24 at 1.11.19e280afpm

    Example 3. Game of Life: Get the next generation of cell states

    A cell is a block in a matrix surrounded by neighbours. A cell can be in two states, alive or dead. A cell can change its state from one generation to another under certain circumstances

    Rule

    • A live cell with fewer than 2 live neighbours dies of loneliness
    • A dead cell with exactly 2 live neighbours comes alive
    • A live cell with greater than 2 live neighbours dies due to overcrowding

    Given a current generation of cells in a matrix, what does the next generation look like? Which cells are alive and which are dead? Write code to get the next generation of cells given the current generation

    Hint

    • Represent each generation as rows and columns in a 2D matrix. Live and Dead states can be represented by integers or boolean states
    screenshot 2024 04 24 at 1.33.22e280afpm
    func printMatrix(_ input: [[Int]]) {
        for row in 0..<input.count {
            print("|", terminator: "")
            for col in 0..<input[0].count {
                print("\(input[row][col])", terminator: "|")
            }
            print("\n")
        }
    }
    public func getNextGeneration(currentGeneration: [[Int]]) -> [[Int]] {
        var nextGeneration = currentGeneration
        let rowCount = currentGeneration.count
        let colCount = currentGeneration[0].count
        for row in 0..<rowCount {
            for col in 0..<colCount {
                let nextState = getNextState(
                    currentGeneration: currentGeneration,
                    row: row,
                    col: col
                )!
                nextGeneration[row][col] = nextState
            }
        }
        return nextGeneration
    }
    
    //0: Dead, 1: Alive
    public func getNextState(currentGeneration: [[Int]], row: Int, col: Int) -> Int? {
        //Edge case
        if currentGeneration.isEmpty {
            return nil
        }
        if row > currentGeneration.count {
            return nil
        }
        if col > currentGeneration[0].count {
            return nil
        }
        var states = [Int: Int]()
        let currentState = currentGeneration[row][col]
        let rowCount = currentGeneration.count
        let colCount = currentGeneration[0].count
        //Check 8 directions
        if col - 1 >= 0 {
            if let value = states[currentGeneration[row][col - 1]]{
                states[currentGeneration[row][col - 1]] = value + 1
            }
            else {
                states[currentGeneration[row][col - 1]] = 1
            }
        }
        if col - 1 >= 0, row - 1 >= 0 {
            if let value = states[currentGeneration[row - 1][col - 1]]{
                states[currentGeneration[row - 1][col - 1]] = value + 1
            }
            else {
                states[currentGeneration[row - 1][col - 1]] = 1
            }
        }
        if col - 1 >= 0, row + 1 < rowCount {
            if let value = states[currentGeneration[row + 1][col - 1]]{
                states[currentGeneration[row + 1][col - 1]] = value + 1
            }
            else {
                states[currentGeneration[row + 1][col - 1]] = 1
            }
        }
        if col + 1 < colCount {
            if let value = states[currentGeneration[row][col + 1]]{
                states[currentGeneration[row][col + 1]] = value + 1
            }
            else {
                states[currentGeneration[row][col + 1]] = 1
            }
        }
        if col + 1 < colCount, row - 1 >= 0 {
            if let value = states[currentGeneration[row - 1][col + 1]]{
                states[currentGeneration[row - 1][col + 1]] = value + 1
            }
            else {
                states[currentGeneration[row - 1][col + 1]] = 1
            }
        }
        if col + 1 < colCount, row + 1 < rowCount {
            if let value = states[currentGeneration[row + 1][col + 1]]{
                states[currentGeneration[row + 1][col + 1]] = value + 1
            }
            else {
                states[currentGeneration[row + 1][col + 1]] = 1
            }
        }
        if row + 1 < rowCount {
            if let value = states[currentGeneration[row + 1][col]]{
                states[currentGeneration[row + 1][col]] = value + 1
            }
            else {
                states[currentGeneration[row + 1][col]] = 1
            }
        }
        if row - 1 >= 0 {
            if let value = states[currentGeneration[row - 1][col]]{
                states[currentGeneration[row - 1][col]] = value + 1
            }
            else {
                states[currentGeneration[row - 1][col]] = 1
            }
        }
        
        if currentState == 0 {
            //A dead cell with exactly 2 live neighbours comes alive
            let livedCount = states[1] ?? 0
            return livedCount == 2 ? 1 : 0
        }
        else {
            let livedCount = states[1] ?? 0
            //A live cell with fewer than 2 live neighbours dies of loneliness
            //A live cell with greater than 2 live neighbours dies due to overcrowding
            if livedCount < 2 || livedCount >= 2 {
                return 0
            }
            return currentState
        }
    }
    screenshot 2024 04 24 at 2.09.45e280afpm

    Example 4. Break A document into chunks

    A document is stored in the cloud and you need to send the text of the document to the client which renders it on the screen (Think Google docs)

    You do not want to send the entire document to the client at one go, you want to send it chunk by chunk. A chunk can be created subject to certain constraints

    Rule

    • A chunk can be 5000 or fewer characters in length (This rule is relaxed only under one condition see below)
    • A chunk should contain only complete paragraphs – This is a hard and fast rule
    • A paragraph is represented by the ‘:’ Character in the document
    • List of chunks should be in the order in which they appear in the document (Do not set them up out of order)
    • If you encounter a paragraph > 5000 characters that should be in a separate chunk by itself
    • Get all chunks as close to 5000 characters as possible, subject to the constraints above

    Given a string document return a list of chunks which some other system can use to send to the client

    Hint: public func chunkify(doc: String) -> [String]

    screenshot 2024 04 24 at 2.40.57e280afpm
    public func chunkify(doc: String, chunkSize: Int) -> [String] {
        guard !doc.isEmpty else {
            return []
        }
        var chunks = [String]()
        let paragraphs = doc.split(separator: ":")
        var start = 0
        let total = paragraphs.count
        
        while start < total {
            var chunk = paragraphs[start] + ":"
            if chunk.count <= chunkSize {
                var next = start + 1
                while next < total, paragraphs[next].count + 1 <= chunkSize - chunk.count {
                    chunk += (paragraphs[next] + ":")
                    next += 1
                }
                chunks.append(String(chunk))
                start = next
            }
            else {
                chunks.append(String(chunk))
                start += 1
            }
        }
        return chunks
    }
    screenshot 2024 04 24 at 3.06.53e280afpm

    Example 5. Run Length Encoding and Decoding

    Write code which encodes a string using Run-Length encoding and decodes a string encoded using Run-Length encoding

    Example

    • ABBCCC -> 1A2B3C
    • AABBBCCCC -> 2A3B4C
    • 1D2E1F -> DEEF

    Constraints

    Assume only letters are present in the string to be encode, no numbers

    Remember to handle the case where we can have > 9 of the same characters in a row

    Run-Length Encoding Code

    public func runLengthEncoding(_ input: String) -> String {
        var result = ""
        var start = 0
        let arrayInput = Array(input)
        while start < arrayInput.count {
            var count = 1
            let currentCharacter = arrayInput[start]
            var next = start + 1
            while next < arrayInput.count, arrayInput[next] == currentCharacter {
                count += 1
                next += 1
            }
            result += "\(count)\(currentCharacter)"
            start = next
        }
        return result
    }
    screenshot 2024 04 24 at 3.42.54e280afpm

    Run-Length Decoding Code

    public func runLengthDecoding(_ input: String) -> String {
        var result = ""
        var start = 0
        let arrayInput = Array(input)
        
        //Assumed that first character start with number
        while start < arrayInput.count {
            //Step 1. Get number
            var numberStr = String(arrayInput[start])
            //Edge case. Larger than 9
            var next = start + 1
            while next < arrayInput.count, Int(String(arrayInput[next])) != nil {
                numberStr += String(arrayInput[next])
                print(numberStr)
                next += 1
            }
            let repeatedCount = Int(numberStr) ?? 1
            
            //Step 2. Generate repeated characters
            result += String(repeating: arrayInput[next], count: repeatedCount)
            
            //Step 3. Important! Pointing to next number
            start = next + 1
        }
        return result
    }
    screenshot 2024 04 24 at 4.01.42e280afpm

    Example 6. Add two numbers represented by their digits

    Given two numbers where the individual digits in the numbers are in an array or a list add them to get the final result in the same list or array form

    Example using Arrays: [1, 2] represents the number 12. Note that the most significant digit in the 0th index of the array, with the least significant digit at the last position

    Adding [1, 2] and [2, 3] should give the result [3, 5]

    Requirements

    • Don’t convert the number format to a real number to add them. Add them digit by digit
    • Remember to consider the carry over per digit if there is one!
    screenshot 2024 04 24 at 4.25.26e280afpm
    public func add(a: [Int], b: [Int]) -> [Int] {
        var result = [Int]()
        let maxLength = max(a.count, b.count)
        var carry = 0
        var currentIndexA = a.count == maxLength ? maxLength - 1 : a.count - 1
        var currentIndexB = b.count == maxLength ? maxLength - 1 : b.count - 1
        
        while currentIndexA >= 0, currentIndexB >= 0 {
            var sum = a[currentIndexA] + b[currentIndexB] + carry
            if sum > 9 {
                carry = sum / 10
                sum = sum % 10
            }
            else {
                carry = 0
            }
            result.insert(sum, at: 0)
            currentIndexA -= 1
            currentIndexB -= 1
        }
        if currentIndexA >= 0 {
            while currentIndexA >= 0 {
                var sum = a[currentIndexA] + carry
                if sum > 9 {
                    carry = sum / 10
                    sum = sum % 10
                }
                else {
                    carry = 0
                }
                result.insert(sum, at: 0)
                currentIndexA -= 1
            }
        }
        
        if currentIndexB >= 0 {
            while currentIndexB >= 0 {
                var sum = a[currentIndexB] + carry
                if sum > 9 {
                    carry = sum / 10
                    sum = sum % 10
                }
                else {
                    carry = 0
                }
                result.insert(sum, at: 0)
                currentIndexB -= 1
            }
        }
        
        
        if carry != 0 {
            result.insert(carry, at: 0)
        }
        
        return result
    }
    
    screenshot 2024 04 24 at 8.36.44e280afpm

    Example 7. Increment number by 1

    Suppose that you invent your own numeral system (which is neither decimal, binary nor any of the common ones). You specify the digits and the order of the digits in that numeral system.

    Given the digits and the order of digits used in that system and a number, write a function to increment that number by 1 and return the result

    screenshot 2024 04 24 at 9.06.32e280afpm

    Solution 1

    public func incrementByOne(input: String) -> String? {
        var result = [String]()
        let numeralSystem = ["A": 0, "B": 1, "C": 2, "D": 3]
        var input = Array(input).compactMap { "\($0)"}
        
        var validCount = 0
        
        for char in input {
            if numeralSystem.keys.contains(char) {
                validCount += 1
            }
        }
        if validCount != input.count {
            return nil
        }
        
        var currentIndex = input.count - 1
        
        var carry = 0
        while currentIndex >= 0 {
            var sum = numeralSystem[input[currentIndex]]! + carry
            if currentIndex == input.count - 1 {
                sum += 1
            }
            if sum > 3 {
                carry = sum / 4
                sum = sum % 4
            }
            else {
                carry = 0
            }
            if let key = numeralSystem.first (where: { $0.value == sum })?.key {
                result.insert(key, at: 0)
            }
            currentIndex -= 1
        }
        
        if carry != 0 {
            if let key = numeralSystem.first (where: { $0.value == carry })?.key {
                result.insert(key, at: 0)
            }
        }
        return result.joined()
    }
    
    screenshot 2024 04 24 at 9.24.19e280afpm

    Solution 2

    public func incrementByOne2(input: String) -> String? {
        let numeralSystem = ["A", "B", "C", "D"]
        var input = Array(input).compactMap { "\($0)" }
        
        var validCount = 0
        for char in input {
            if numeralSystem.contains(char) {
                validCount += 1
            }
        }
        if validCount != input.count {
            return nil
        }
        
        var currentIndex = input.count - 1
        var isCompleted = false
        while !isCompleted, currentIndex >= 0 {
            let currentDigit = input[currentIndex]
            let indexOfCurrentDigit = numeralSystem.firstIndex(of: currentDigit)
            
            //Get the next digit on increment, this will wrap around to the first digit which is why we use the modulo operator
            let indexOfNextDigit = (indexOfCurrentDigit! + 1) % numeralSystem.count
            
            //Update digit
            input[currentIndex] = numeralSystem[indexOfNextDigit]
            
            if indexOfNextDigit != 0 {
                isCompleted = true
            }
            
            //If we're at the most significant digit and that wrapped around we add a new digit to the incremented number like going from 9 to 10
            if currentIndex == 0, indexOfNextDigit == 0 {
                input.insert(numeralSystem[0], at: 0)
            }
            currentIndex -= 1
        }
        return input.joined()
    }
    
    screenshot 2024 04 24 at 9.36.42e280afpm

    Example 8. Sudoku

    screenshot 2024 03 23 at 10.12.33e280afpm
    screenshot 2024 03 23 at 11.58.23e280afpm

    Basic rules

    • Each rows and columns should have one value (1-9) -> Should not have duplicated value
    • Board size is 9×9
    • Block size is 3×3, Total 9 blocks
    • In a Block, Should have one value (1-9) -> Should not have duplicated value

    Exercise 1. isValidRowsAndCols

    This function is returns boolean. If all the row and column’s values are valid then It returns true. otherwise returns false.

    screenshot 2024 03 24 at 12.13.14e280afam
    var sudoku: [[Int]] = [
        [8, 0, 0,   4, 0, 6,   0, 0, 7],
        [0, 0, 0,   0, 0, 0,   4, 0, 0],
        [0, 1, 0,   0, 0, 0,   6, 5, 0],
        
        [5, 0, 9,   0, 3, 0,   7, 8, 0],
        [0, 0, 0,   0, 7, 0,   0, 0, 0],
        [0, 4, 8,   0, 2, 0,   1, 0, 3],
        
        [0, 5, 2,   0, 0, 0,   0, 0, 0],
        [0, 0, 1,   0, 0, 0,   0, 0, 0],
        [3, 0, 0,   9, 0, 2,   0, 0, 5]
    ]
    //Helper
    func printBoard(_ input: [[Int]]) {
        for row in 0..<9 {
            print("|", terminator: "")
            for col in 0..<9 {
                print("\(input[row][col])", terminator: "|")
            }
            print("\n")
        }
    }
    
    func isValidRowsAndCols(_ board: [[Int]]) -> Bool {
        var rowSet: [Int: Set<Int>] = [:]
        var colSet: [Int: Set<Int>] = [:]
        
        //Index starts from 0 to 8
        for i in 0...8 {
            rowSet[i] = Set()
            colSet[i] = Set()
        }
        
        for row in 0...8 {
            for col in 0...8 {
                var cellValue = board[row][col]
                //No value has assigned
                if cellValue == 0 {
                    continue
                }
                
                if cellValue < 0 || cellValue > 9 {
                    return false
                }
                //cellValue already seen
                if rowSet[row]?.contains(cellValue) == true {
                    print("💥 Invalid: \(row):\(col) -> value: \(cellValue)")
                    return false
                }
                if colSet[col]?.contains(cellValue) == true {
                    print("💥 Invalid: \(row):\(col) -> value: \(cellValue)")
                    return false
                }
                
                //Add current cell value to the row or column set
                rowSet[row]?.insert(cellValue)
                colSet[col]?.insert(cellValue)
            }
        }
        return true
    }
    
    isValidRowsAndCols(sudoku) //It returns true
    

    Exercise 2. IsValidBlock -> Bool

    screenshot 2024 03 23 at 11.58.23e280afpm

    Each block should have values between 1-9. And each cell should not have a duplicate value in a block.

    func isValidBlock(_ board: [[Int]]) -> Bool {
        //Set associated with every block
        var blockSet: [Int: Set<Int>] = [:]
        for i in 0..<9 {
            blockSet[i] = Set()
        }
        
        //Row block is 3
        for rowBlock in 0...2 {
            for colBlock in 0...2 {
                //Check 3x3 Block Cell
                for miniRow in 0...2 {
                    for miniCol in 0...2 {
                        let row = rowBlock * 3 + miniRow
                        let col = colBlock * 3 + miniCol
                        let cellValue = sudoku[row][col]
                        
                        if cellValue == 0 {
                            continue
                        }
                        if cellValue < 0 || cellValue > 9 {
                            return false
                        }
                        /* Total 9 block
                         row0: 0  1  2
                         row1: 3  4  5
                         row2: 6  7  8
                         */
                        let blockNumber = rowBlock * 3 + colBlock
                        
                        if blockSet[blockNumber]?.contains(cellValue) == true {
                            return false
                        }
                        blockSet[blockNumber]?.insert(cellValue)
                    }
                }
            }
        }
        return true
    }
    isValidBlock(sudoku) // It returns true
    
    screenshot 2024 03 24 at 12.16.41e280afam

    Exercise 3. isValidSudoku -> Bool

    This function checks all the cells in board and checks the sudoku rule.

    func isValid(_ board: [[Int]]) -> Bool {
        if !isValidRowsAndCols(board) {
            return false
        }
        if !isValidBlock(board) {
            return false
        }
        return true
    }

    It is very simple. It calls two functions we made. If all the cell’s value are valid then return true.

    Exercise 4. solveSudoku

    This function fill the cell’s value and returns solution.

    sudo

    https://www.sudokuonline.io

    As you can see when you select a cell (yellow), You need to check blue areas.

    Step 1. Create a Sudoku Board

    Generate Sudoku Board

    func generateSudoku(board: inout [[Int]]) {
        //Has a 9 mini blocks. diagonal direction
        //3 means jumping to next mini block, It loops 3 blocks and fill the numbers
        for i in stride(from: 0, through: 8, by: 3) {
            var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffled()
            for miniRow in 0...2 {
                for miniCol in 0...2 {
                    board[miniRow][miniCol] = numbers.removeFirst()
                }
            }
        }
        
        
        //Step 2. solve function to fill the all numbers
        _ = solve(board: &board)
        
        var numberOfEmptyCell = 50
        
        while numberOfEmptyCell > 0 {
            var random = [0, 1, 2, 3, 4, 5, 6, 7, 8].shuffled()
        
            board[random.first!][random.last!] = 0
            numberOfEmptyCell -= 1
        }
        //TODO: Step 3. set empty cells It depends on difficulty
    }
    screenshot 2024 04 21 at 8.55.59e280afpm

    Helper functions

    import Foundation
    //https://github.com/ShawnBaek/Table
    import Table
    
    //Check If Sudoku board has empty cell or not
    func isFilledAllNumbers(board: [[Int]]) -> Bool {
        var numberOfEmptyCell = 0
        for row in 0...8 {
            for col in 0...8 {
                if board[row][col] == 0 {
                    numberOfEmptyCell += 1
                }
            }
        }
        return numberOfEmptyCell == 0
    }
    
    //Get emptyCell's position
    func emptyCell(board: [[Int]]) -> (row: Int, col: Int)? {
        for row in 0...8 {
            for col in 0...8 {
                //Empty cell found
                if board[row][col] == 0 {
                    return (row: row, col: col)
                }
            }
        }
        return nil
    }
    
    func canPlace(_ number: Int, row: Int, col: Int, board: [[Int]]) -> Bool {
        //Sudoku rule
        
        //Rule 1. Check unique number in a row
        for r in 0...8 {
            if board[r][col] == number {
                return false
            }
        }
        
        //Rule 2. Check unique number in a col
        for c in 0...8 {
            if board[row][c] == number {
                return false
            }
        }
        
        //Rule 3. Check unique number in a mini block -> total 9
        //each block's start position will be 0,0  3,3, 6,6, and so on
        
        //If row is between 3-5, 3...5 / 3 = 1, 1 * 3 = 3
        let miniBlockStartRow = (row / 3) * 3
        let miniBlockStartCol = (col / 3) * 3
        
        for miniRow in 0...2 {
            for miniCol in 0...2 {
                let boardRow = miniBlockStartRow + miniRow
                let boardCol = miniBlockStartCol + miniCol
                if board[boardRow][boardCol] == number {
                    return false
                }
            }
        }
        return true
    }

    Sudoku Board solve function

    • Step 1. Check isFilledAllNumber or not.
      • It assumed that generated board is a valid sudoku board. All we need to do is fill the correct number on empty cell
    • Step 2. Recursively calling a function – Get empty cell and fill the correct number
    //It is a recursive function
    func solve(board: inout [[Int]]) -> Bool {
        //Base case
        //If this board filled all numbers? -> yes then return true
        //func isFilledAllNumbers -> Bool
        //Game done
        if isFilledAllNumbers(board: board) {
            return true
        }
        
        //Step 1. Get the emptyCell and finding a number to place
        //one more function to get emptyCell
        if let emptyCellPosition = emptyCell(board: board) {
            //Can place 1-9 numbers
            for number in 1...9 {
                //Check can place a number at this position
                if canPlace(number, row: emptyCellPosition.row, col: emptyCellPosition.col, board: board) {
                    //Place a number
                    board[emptyCellPosition.row][emptyCellPosition.col] = number
                    //Recursive Case
                    if solve(board: &board) {
                        return true
                    }
                    //Reset to emptyCell
                    board[emptyCellPosition.row][emptyCellPosition.col] = 0
                }
            }
        }
        return false
    }

    Check Validation

    func isValidRowsAndCols(_ board: [[Int]]) -> Bool {
        var rowSet: [Int: Set<Int>] = [:]
        var colSet: [Int: Set<Int>] = [:]
        
        //Index starts from 0 to 8
        for i in 0...8 {
            rowSet[i] = Set()
            colSet[i] = Set()
        }
        
        for row in 0...8 {
            for col in 0...8 {
                let cellValue = board[row][col]
                //No value has assigned
                if cellValue == 0 {
                    continue
                }
                
                if cellValue < 0 || cellValue > 9 {
                    return false
                }
                //cellValue already seen
                if rowSet[row]?.contains(cellValue) == true {
                    return false
                }
                if colSet[col]?.contains(cellValue) == true {
                    return false
                }
                
                //Add current cell value to the row or column set
                rowSet[row]?.insert(cellValue)
                colSet[col]?.insert(cellValue)
            }
        }
        return true
    }
    
    
    
    func isValidBlock(_ board: [[Int]]) -> Bool {
        //Set associated with every block
        var blockSet: [Int: Set<Int>] = [:]
        for i in 0..<9 {
            blockSet[i] = Set()
        }
        
        var test = 0
        //Row block is 3
        for rowBlock in 0...2 {
            for colBlock in 0...2 {
                print("Start Block: \(test)")
                //Check 3x3 Block Cell
                for miniRow in 0...2 {
                    for miniCol in 0...2 {
                        let row = rowBlock * 3 + miniRow
                        let col = colBlock * 3 + miniCol
                        let cellValue = board[row][col]
                        
                        if cellValue == 0 {
                            continue
                        }
                        if cellValue < 0 || cellValue > 9 {
                            return false
                        }
                        /* Total 9 block
                         row0: 0  1  2
                         row1: 3  4  5
                         row2: 6  7  8
                         */
                        let blockNumber = rowBlock * 3 + colBlock
                        
                        if blockSet[blockNumber]?.contains(cellValue) == true {
                            return false
                        }
                        blockSet[blockNumber]?.insert(cellValue)
                    }
                }
                
                test += 1
                
            }
            
        }
        return true
    }
    
    func isValid(_ board: [[Int]]) -> Bool {
        if !isValidRowsAndCols(board) {
            return false
        }
        if !isValidBlock(board) {
            return false
        }
        return true
    }
    screenshot 2024 04 21 at 8.57.18e280afpm 5
  • iOS, Combine Framework

    iOS, Combine Framework

    ✍️ Note

    Some codes and contents are sourced from Apple, WWDC and BigMountStudio. This post is for personal notes where I summarize the original contents to grasp the key concepts (🎨 some images I draw it)

    Overview

    screenshot 2024 04 17 at 9.28.49e280afpm

    The Combine framework provides a declarative approach for how your app processes events. Rather than potentially implementing multiple delegate callbacks or completion handler closures, you can create a single processing chain for a given event source. Each part of the chain is a Combine operator that performs a distinct action on the elements received from the previous step.

    Apple

    screenshot 2024 04 17 at 2.12.51e280afpm

    Publisher is struct

    associatedtype failure error
    screenshot 2024 04 17 at 12.20.47e280afpm
    screenshot 2024 04 17 at 10.07.54e280afpm

    Array can publish values.

    I used delay, between publisher and subscriber. It controlling the timing like debounce and throttle. Above the example, It prints 1, 2, 3, 4 after 3 seconds.

    Subscriber

    It has two main feature.

    • receive
    • assign
    associatedtype failure erron
    screenshot 2024 04 17 at 12.21.58e280afpm

    Operators

    It adopts publisher and send results to subscriber

    • Upstream: Publisher – Subscribes to Publisher
    • Downstream: Subscriber – Sends results to a Subscriber
    extension publishers
    screenshot 2024 04 17 at 12.32.16e280afpm

    Publisher’s map function. It’s upstream is self

    notification center
    screenshot 2024 04 17 at 12.31.04e280afpm
    screenshot 2024 04 17 at 12.32.49e280afpm

    Subjects

    A publisher that exposes a method for outside callers to publish elements.

    A subject is a publisher that you can use to ”inject” values into a stream, by calling its send(_:) method. This can be useful for adapting existing imperative code to the Combine model.

    • CurrentValueSubject
    • PassthroughSubject
    behave like both publisher and subscriber

    PassthroughSubject

    A subject that broadcasts elements to downstream subscribers.

    Unlike CurrentValueSubject, a PassthroughSubject doesn’t have an initial value or a buffer of the most recently-published element. A PassthroughSubject drops values if there are no subscribers, or its current demand is zero.

    screenshot 2024 04 17 at 11.50.03e280afpm

    CurrentValueSubject

    A subject that wraps a single value and publishes a new element whenever the value changes.

    Unlike PassthroughSubjectCurrentValueSubject maintains a buffer of the most recently published element.

    Calling send(_:) on a CurrentValueSubject also updates the current value, making it equivalent to updating the value directly.

    screenshot 2024 04 17 at 11.50.42e280afpm

    Map

    Transforms all elements from the upstream publisher with a provided closure.

    func map<T>(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map<Self, T>

    Parameters

    transform

    A closure that takes one element as its parameter and returns a new element.

    Return Value

    A publisher that uses the provided closure to map elements from the upstream publisher to new elements that it then publishes.

    FlatMap

    Transforms all elements from an upstream publisher into a new publisher up to a maximum number of publishers you specify.

    func flatMap<T, P>( maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -> P ) -> Publishers.FlatMap<P, Self> where T == P.Output, P : Publisher, Self.Failure == P.Failure

    Parameters

    maxPublishers

    Specifies the maximum number of concurrent publisher subscriptions, or unlimited if unspecified.transform

    A closure that takes an element as a parameter and returns a publisher that produces elements of that type.

    Return Value

    A publisher that transforms elements from an upstream publisher into a publisher of that element’s type.

    Combine‘s flatMap(maxPublishers:_:) operator performs a similar function to the flatMap(_:) operator in the Swift standard library, but turns the elements from one kind of publisher into a new publisher that is sent to subscribers. Use flatMap(maxPublishers:_:) when you want to create a new series of events for downstream subscribers based on the received value. The closure creates the new Publisher based on the received value. The new Publisher can emit more than one event, and successful completion of the new Publisher does not complete the overall stream. Failure of the new Publisher causes the overall stream to fail.

    In the example below, a PassthroughSubject publishes WeatherStation elements. The flatMap(maxPublishers:_:) receives each element, creates a URL from it, and produces a new URLSession.DataTaskPublisher, which will publish the data loaded from that URL.

    public struct WeatherStation {
        public let stationID: String
    }
    
    
    var weatherPublisher = PassthroughSubject<WeatherStation, URLError>()
    
    
    cancellable = weatherPublisher.flatMap { station -> URLSession.DataTaskPublisher in
        let url = URL(string:"https://weatherapi.example.com/stations/\(station.stationID)/observations/latest")!
        return URLSession.shared.dataTaskPublisher(for: url)
    }
    .sink(
        receiveCompletion: { completion in
            // Handle publisher completion (normal or error).
        },
        receiveValue: {
            // Process the received data.
        }
     )
    
    
    weatherPublisher.send(WeatherStation(stationID: "KSFO")) // San Francisco, CA
    weatherPublisher.send(WeatherStation(stationID: "EGLC")) // London, UK
    weatherPublisher.send(WeatherStation(stationID: "ZBBB")) // Beijing, CN
    screenshot 2024 04 17 at 2.01.05e280afpm
    flat map
    screenshot 2024 04 17 at 2.03.09e280afpm
    screenshot 2024 04 17 at 2.03.14e280afpm
    screenshot 2024 04 17 at 2.04.05e280afpm 1
    screenshot 2024 04 17 at 2.05.27e280afpm 1

    Future – Publisher

    A publisher that eventually produces a single value and then finishes or fails.

    Use a future to perform some work and then asynchronously publish a single element. You initialize the future with a closure that takes a Future.Promise; the closure calls the promise with a Result that indicates either success or failure. In the success case, the future’s downstream subscriber receives the element prior to the publishing stream finishing normally. If the result is an error, publishing terminates with that error.

    screenshot 2024 04 18 at 12.04.24e280afam

    Above the example, when you call a function It create a Future publisher. As you can see Future has finished after send a value (200)

    Apple’s example

    func generateAsyncRandomNumberFromFuture() -> Future <Int, Never> {
        return Future() { promise in
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                let number = Int.random(in: 1...10)
                promise(Result.success(number))
            }
        }
    }
    
    cancellable = generateAsyncRandomNumberFromFuture()
        .sink { number in print("Got random number \(number).") }
    
    //async-await syntax
    let number = await generateAsyncRandomNumberFromFuture().value
    print("Got random number \(number).")
    
    //Alternative to Futures
    func generateAsyncRandomNumberFromContinuation() async -> Int {
        return await withCheckedContinuation { continuation in
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                let number = Int.random(in: 1...10)
                continuation.resume(returning: number)
            }
        }
    }
    
    let asyncRandom = await generateAsyncRandomNumberFromContinuation()
    

    Demand – Publisher

    A publisher that awaits subscription before running the supplied closure to create a publisher for the new subscriber.

    screenshot 2024 04 18 at 12.19.08e280afam

    Unlike Future Publisher, It deferred publisher will not execute immediately when It is created.
    It will execute every time a subscriber is attached.

    You can use it for many of Apple’s Kits where you need to get information from a device, or ask the user for permissions to access something, like photos, or other private or sensitive information

    https://www.bigmountainstudio.com

    screenshot 2024 04 18 at 12.27.12e280afam

    Throttle – Publisher method

    Publishes either the most-recent or first element published by the upstream publisher in the specified time interval.

    interval

    The interval at which to find and emit either the most recent or the first element, expressed in the time system of the scheduler.

    When you set latest true, It returns the latest value. Otherwise It returns a first value.

    Share

    Shares the output of an upstream publisher with multiple subscribers.

    Publishers.Share is effectively a combination of the Publishers.Multicast and PassthroughSubject publishers, with an implicit autoconnect().

    The following example uses a sequence publisher as a counter to publish three random numbers, generated by a map(_:) operator. It uses a share() operator to share the same random number to each of two subscribers. This example uses a delay(for:tolerance:scheduler:options:) operator only to prevent the first subscriber from exhausting the sequence publisher immediately; an asynchronous publisher wouldn’t need this.

    Without the share() operator, stream 1 receives three random values, followed by stream 2 receiving three different random values.

    Also note that Publishers.Share is a class rather than a structure like most other publishers. This means you can use this operator to create a publisher instance that uses reference semantics.

    Example 1. Without Share – received values are different

    screenshot 2024 04 18 at 12.41.02e280afam

    Example 2. With share() – received values are the same

    screenshot 2024 04 18 at 12.41.38e280afam

    Connect / AutoConnect – Use for ConnectablePublisher

    Sometimes, you want to configure a publisher before it starts producing elements, such as when a publisher has properties that affect its behavior. But commonly used subscribers like sink(receiveValue:) demand unlimited elements immediately, which might prevent you from setting up the publisher the way you like. A publisher that produces values before you’re ready for them can also be a problem when the publisher has two or more subscribers. This multi-subscriber scenario creates a race condition: the publisher can send elements to the first subscriber before the second even exists.

    Consider the scenario in the following figure. You create a URLSession.DataTaskPublisher and attach a sink subscriber to it (Subscriber 1) which causes the data task to start fetching the URL’s data. At some later point, you attach a second subscriber (Subscriber 2). If the data task completes its download before the second subscriber attaches, the second subscriber misses the data and only sees the completion.

    screenshot 2024 04 18 at 12.35.50e280afam
    Hold Publishing by Using a Connectable Publisher

    To prevent a publisher from sending elements before you’re ready, Combine provides the ConnectablePublisher protocol. A connectable publisher produces no elements until you call its connect() method. Even if it’s ready to produce elements and has unsatisfied demand, a connectable publisher doesn’t deliver any elements to subscribers until you explicitly call connect().

    The following figure shows the URLSession.DataTaskPublisher scenario from above, but with a ConnectablePublisher ahead of the subscribers. By waiting to call connect() until both subscribers attach, the data task doesn’t start downloading until then. This eliminates the race condition and guarantees both subscribers can receive the data.

    screenshot 2024 04 18 at 12.36.01e280afam

    To use a ConnectablePublisher in your own Combine code, use the makeConnectable() operator to wrap an existing publisher with a Publishers.MakeConnectable instance. The following code shows how makeConnectable() fixes the data task publisher race condition described above. Typically, attaching a sink — identified here by the AnyCancellable it returns, cancellable1 — would cause the data task to start immediately. In this scenario, the second sink, identified as cancellable2, doesn’t attach until one second later, and the data task publisher might complete before the second sink attaches. Instead, explicitly using a ConnectablePublisher causes the data task to start only after the app calls connect(), which it does after a two-second delay.

    let url = URL(string: "https://example.com/")!
    let connectable = URLSession.shared
        .dataTaskPublisher(for: url)
        .map() { $0.data }
        .catch() { _ in Just(Data() )}
        .share()
        .makeConnectable()
    
    
    cancellable1 = connectable
        .sink(receiveCompletion: { print("Received completion 1: \($0).") },
              receiveValue: { print("Received data 1: \($0.count) bytes.") })
    
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.cancellable2 = connectable
            .sink(receiveCompletion: { print("Received completion 2: \($0).") },
                  receiveValue: { print("Received data 2: \($0.count) bytes.") })
    }
    
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        self.connection = connectable.connect()
    }

    Use the Autoconnect Operator If You Don’t Need to Explicitly Connect

    Some Combine publishers already implement ConnectablePublisher, such as Publishers.Multicast and Timer.TimerPublisher. Using these publishers can cause the opposite problem: having to explicitly connect() could be burdensome if you don’t need to configure the publisher or attach multiple subscribers.

    For cases like these, ConnectablePublisher provides the autoconnect() operator. This operator immediately calls connect() when a Subscriber attaches to the publisher with the subscribe(_:) method.

    The following example uses autoconnect(), so a subscriber immediately receives elements from a once-a-second Timer.TimerPublisher. Without autoconnect(), the example would need to explicitly start the timer publisher by calling connect() at some point.

    let cancellable = Timer.publish(every: 1, on: .main, in: .default)
        .autoconnect()
        .sink() { date in
            print ("Date now: \(date)")
         }