8

Is there a way to limit array size in swift so that when you append elements to it when it's full, it doesn't append?

I know this can be done programmatically. Wondering if swift has inbuilt handling for this.

eg:

array -> Array with size 10

array.append(1)

.

.

.

array.append(10)

array.append(11) // Doesn't do anything?

array.insert(0, pos: 0) 

Use Case: Pushes the last element out of the array to make space for the new element?

Edit - last line is the main use case.

2
  • 1
    just write an extension function. don't think that there's an inbuilt function for that Commented Mar 7, 2018 at 5:49
  • 4
    No, this kind of behaviour is not part if Swift's standard library. You need to write your own array wrapper for this. Commented Mar 7, 2018 at 5:53

5 Answers 5

7

Nope, Swift doesn't come with this kind of array - which is similar to a View from a database - allowing you to peek the first N elements. Though for this both the view and it target should be reference types, which is not the case in Swift for arrays.

But enough blabbing around, you could quickly write a wrapper over an Array for fulfilling your needs:

/// an array-like struct that has a fixed maximum capacity
/// any element over the maximum allowed size gets discarded
struct LimitedArray<T> {
    private(set) var storage: [T] = []
    public let maxSize: Int

    /// creates an empty array
    public init(maxSize: Int) {
        self.maxSize = maxSize
    }

    /// takes the max N elements from the given collection
    public init<S: Sequence>(from other: S, maxSize: Int) where S.Element == T {
        self.maxSize = maxSize
        storage = Array(other.prefix(maxSize))
    }

    /// adds a new item to the array, does nothing if the array has reached its maximum capacity
    /// returns a bool indicated the operation success
    @discardableResult public mutating func append(_ item: T) -> Bool {
        if storage.count < maxSize {
            storage.append(item)
            return true
        } else {
            return false
        }
    }

    /// inserts an item at the specified position. if this would result in
    /// the array exceeding its maxSize, the extra element are dropped
    public mutating func insert(_ item: T, at index: Int) {
        storage.insert(item, at: index)
        if storage.count > maxSize {
            storage.remove(at: maxSize)
        }
    }

    // add here other methods you might need
}

// let's benefit all the awesome operations like map, flatMap, reduce, filter, etc
extension LimitedArray: MutableCollection {
    public var startIndex: Int { return storage.startIndex }
    public var endIndex: Int { return storage.endIndex }

    public subscript(_ index: Int) -> T {
        get { return storage[index] }
        set { storage[index] = newValue }
    }

    public func index(after i: Int) -> Int {
        return storage.index(after: i)
    }
}

Since the struct conforms to Collection, you can easily pass it to code that knows only to work with arrays by transforming its contents into an array: Array(myLimitedArray).

Sign up to request clarification or add additional context in comments.

5 Comments

Alternatively: public init<S: Sequence>(from other: S, maxSize: Int) where S.Element == T and then storage = Array(other.prefix(maxSize)) – it might require Swift 4.1.
And the Collection indices do not necessarily start at zero. Try let la = LimitedArray(from: [0, 1, 2][1...], maxSize: 1)
Thanks @MartinR for the suggestions, I incorporated them in the answer. Using prefix also should address the non-zeroed indexed collections.
'SubSequence' is not a member type of 'S' I am getting this error
@VarunNaharia seems SubSequence was moved to some of the Collection protocol family, updated the code in the answer to compile with the latest Swift.
3

simply count the elements of array before appending new element.

var array :[Int] = [1,2,3,4,5,6,7,8,9,10]

func insertIntoArray(_ value: Int, array: [Int]) -> [Int] {
    var arr = array
    if arr.count == 10 {
        arr.removeLast()
    }
    arr.append(value)
    return arr
}

array = insertIntoArray(11, array: array)

4 Comments

Yea avoiding that is the point of the question. Main use case is the last line.
@RakeshaShastri you would need to implement your own append and insert methods with an extra argument for limiting the number of elements
this does not allow to insert the element at the beginning of the array as OP asked. Besides that it would be much better to extend array and create a mutating method
Swift is a type inferred language. There is no need to explicitly set the type of the array in your code
1

I recently wanted this exact type of collection. I went ahead and created this. It should do exactly what you want with some additional helper functions.

https://gist.github.com/djk12587/ea6d8dea837f8274b911deb0c819f74f

struct CappedCollection<T> {

    private var elements: [T]
    var maxCount: Int

    init(elements: [T], maxCount: Int) {
        self.elements = elements
        self.maxCount = maxCount
    }
}

extension CappedCollection: Collection, ExpressibleByArrayLiteral {

    typealias Index = Int
    typealias Element = T

    init(arrayLiteral elements: Element...) {
        self.elements = elements
        maxCount = elements.count
    }

    var startIndex: Index { return elements.startIndex }
    var endIndex: Index { return elements.endIndex }

    subscript(index: Index) -> Iterator.Element {
        get { return elements[index] }
    }

    func index(after i: Index) -> Index {
        return elements.index(after: i)
    }

    @discardableResult
    mutating func append(_ newElement: Element) -> Element? {
        elements.append(newElement)
        return removeExtraElements().first
    }

    @discardableResult
    mutating func append<C>(contentsOf newElements: C) -> [Element] where C : Collection, CappedCollection.Element == C.Element {
        elements.append(contentsOf: newElements)
        return removeExtraElements()
    }

    @discardableResult
    mutating func insert(_ newElement: Element, at i: Int) -> Element? {
        elements.insert(newElement, at: i)
        return removeExtraElements().first
    }

    @discardableResult
    mutating func insert<C>(contentsOf newElements: C, at i: Int) -> [Element] where C : Collection, CappedCollection.Element == C.Element {
        elements.insert(contentsOf: newElements, at: i)
        return removeExtraElements()
    }

    private mutating func removeExtraElements() -> [Element] {
        guard elements.count > maxCount else { return [] }

        var poppedElements: [Element] = []
        poppedElements.append(contentsOf: elements[maxCount..<elements.count])
        elements.removeLast(elements.count - maxCount)
        return poppedElements
    }
}

1 Comment

how to use it with my array?
1

From swift 5.1 you can use something like this

@propertyWrapper 
public struct MaxSizeCollection<Value: Collection> {
private let _maxSize: UInt?
public var _value: Value

public init(wrappedValue value: Value) {
    _value = value
    _maxSize = nil
}

public init(wrappedValue value: Value, maxSize: UInt) {
    _value = value
    _maxSize = maxSize
}

public var wrappedValue: Value {
    get { _value }
    set {
        guard let _maxSize = _maxSize, _value.count <= _maxSize else { return }

        _value = newValue
    }
}
}

and then use it like this

@MaxSizeCollection(maxSize: 128)
var limitSizeArray: [Any] = []

Comments

0

The answer to your question is no. Swift does not have a fixed-size array data structure, nor does it really need to.

If you do want to create a fixed-size array for yourself, Ray Wenderlich has published an interesting tutorial/article on how to do just that in Swift!

1 Comment

Thanks! I'll check it out. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.