Swift lazy subscript ignores filter - swift

How does subscripting a lazy filter work?
let ary = [0,1,2,3]
let empty = ary.lazy.filter { $0 > 4 }.map { $0 + 1 }
print(Array(empty)) // []
print(empty[2]) // 3
It looks like it just ignores the filter and does the map anyway. Is this documented somewhere? What other lazy collections have exceptional behavior like this?

It comes down to subscripting a LazyFilterCollection with an integer which in this case ignores the predicate and forwards the subscript operation to the base.
For example, if we're looking for the strictly positive integers in an array :
let array = [-10, 10, 20, 30]
let lazyFilter = array.lazy.filter { $0 > 0 }
print(lazyFilter[3]) // 30
Or, if we're looking for the lowercase characters in a string :
let str = "Hello"
let lazyFilter = str.lazy.filter { $0 > "Z" }
print(lazyFilter[str.startIndex]) //H
In both cases, the subscript is forwarded to the base collection.
The proper way of subscripting a LazyFilterCollection is using a LazyFilterCollection<Base>.Index as described in the documentation :
let start = lazyFilter.startIndex
let index = lazyFilter.index(start, offsetBy: 1)
print(lazyFilter[index])
Which yields 20 for the array example, or l for the string example.
In your case, trying to access the index 3:
let start = empty.startIndex
let index = empty.index(start, offsetBy: 3)
print(empty)
would raise the expected runtime error :
Fatal error: Index out of range

To add to Carpsen90's answer, you run into one of Collection's particularities: it's not recommended, nor safe to access collections by an absolute index, even if the type system allows this. Because the collection you receive might be a subset of another one.
Let's take a simpler example, array slicing:
let array = [0, 1, 2, 3, 4]
let slice = array[2..<3]
print(slice) // [2]
print(slice.first) // Optional(2)
print(slice[0]) // crashes with array index out of bounds
Even if slice is a collection indexable by an integer, it's still unsafe to use absolute integers to access elements of that collection, as the collection might have a different set of indices.

Related

Find nearest smaller number in array

I would like to be able to find the nearest smaller value in an array of numbers. For instance, if I have:
[1, 4, 6, 9, 14, 39]
And I'm looking for the nearest value smaller than:
8
The function would return:
6
Additionally, if I pass a number greater than the maximum value in the array, it should return the maximum. If I pass a number smaller than the minimum, it should return nil.
I tried doing this using the first function on arrays, however this on its own doesn't produce the result I'm looking for as I would need something like this:
numbers.first(where: { $0 <= target && $1 < target })
but unfortunately, this isn't valid. Any suggestions? I know this could be done fairly trivially with a while loop, however I was hoping there would be a cleaner, functional way.
Given that the array is sorted , You need
if let value = numbers.last(where: { $0 <= target }) {
print(value)
}
This is a generic solution using binary search. The array must be sorted
extension RandomAccessCollection where Element : Comparable {
func lowerElement(of value: Element) -> Element? {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex == self.startIndex ? nil : self[self.index(before: slice.startIndex)]
}
}
let array = [1, 4, 6, 9, 14, 39]
let result = array.lowerElement(of: 8)
print(result)

Get value from array that is closest to but small than another value [duplicate]

I would like to be able to find the nearest smaller value in an array of numbers. For instance, if I have:
[1, 4, 6, 9, 14, 39]
And I'm looking for the nearest value smaller than:
8
The function would return:
6
Additionally, if I pass a number greater than the maximum value in the array, it should return the maximum. If I pass a number smaller than the minimum, it should return nil.
I tried doing this using the first function on arrays, however this on its own doesn't produce the result I'm looking for as I would need something like this:
numbers.first(where: { $0 <= target && $1 < target })
but unfortunately, this isn't valid. Any suggestions? I know this could be done fairly trivially with a while loop, however I was hoping there would be a cleaner, functional way.
Given that the array is sorted , You need
if let value = numbers.last(where: { $0 <= target }) {
print(value)
}
This is a generic solution using binary search. The array must be sorted
extension RandomAccessCollection where Element : Comparable {
func lowerElement(of value: Element) -> Element? {
var slice : SubSequence = self[...]
while !slice.isEmpty {
let middle = slice.index(slice.startIndex, offsetBy: slice.count / 2)
if value < slice[middle] {
slice = slice[..<middle]
} else {
slice = slice[index(after: middle)...]
}
}
return slice.startIndex == self.startIndex ? nil : self[self.index(before: slice.startIndex)]
}
}
let array = [1, 4, 6, 9, 14, 39]
let result = array.lowerElement(of: 8)
print(result)

Data ranged subscribe strange behavior

I was playing with swift's Data in the following a small code:
var d = Data(count: 10)
d[5] = 3
let d2 = d[5..<8]
print("\(d2[0])")
To my surprise, this code throws exception on print() while the following code does not:
var d = Data(count: 10)
d[5] = 3
let d2 = d.subdata(in: 5..<8)
print("\(d2[0])")
I somehow understand why this happens, but I don't get why this is designed like this. When I use subdata() I get a whole copy of range, so indexing is valid from 0. But when I use range subscribe [], I get access to the requested range while indexing is the same as before. So in my first example d2[5] is 3.
But I wonder why it is designed like this? I don't want to make a copy of my data by using subdata() method. I just wanted to access a portion of my data with better indexing.
This is especially creates unexpected behaviors if you pass it to a function. For example, following code creates unexpected results and exceptions and you may not find out easily why:
func testit(idata: Data) {
if idata.count > 0 {
print("\(idata.count)")
print("\(idata[0])")
}
}
//...
var d = Data(count: 10)
d[5] = 3
let d2 = d[5..<8]
testit(idata: d2)
This code is really strange. Because if you debug your code, you see that print("\(idata.count)") prints 3 as size of idata which is correct, but accessing it with idata[0] creates exception.
Is there any reason for this design? I was expecting that I could access resulting Data from subscribe starting index 0 while it is not true. Can I do this without using subdata() which creates copy of data or using additional arguments to pass base of data slice?
d[5..<8] returns Data.Slice – which happens to be Data. Generally, slices share the indices with their base collection, as documented in Slice.
One possible reason for this design decision is that it guarantees that subscripting a slice is a O(1) operation (adding an offset for accessing the base collection is not necessarily O(1), e.g. not for strings.)
It is also convenient, as in this example to locate the text after the second occurrence of a character in a string:
let string = "abcdefgabcdefg"
// Find first occurrence of "d":
if let r1 = string.range(of: "d") {
// Find second occurrence of "d":
if let r2 = string[r1.upperBound...].range(of: "d") {
print(string[r2.upperBound...]) // efg
}
}
As a consequence, you must never assume that the indices of a collection are zero-based (unless documented, as for Array.startIndex). Use startIndex to get the first index, or first to get the first element.

Compatibility of SubSequence indices

For most Swift Collections, indices of a Collection's SubSequence are compatible for use with the base Collection.
func foo<T: Collection>(_ buffer: T) -> T.Iterator.Element
where T.Index == T.SubSequence.Index
{
let start = buffer.index(buffer.startIndex, offsetBy: 2)
let end = buffer.index(buffer.startIndex, offsetBy: 3)
let sub = buffer[start ... end]
return buffer[sub.startIndex]
}
This works fine for most collections:
print(foo([0, 1, 2, 3, 4])) // 2
And even for String.UTF8View:
print(foo("01234".utf8) - 0x30 /* ASCII 0 */) // 2
But when using String.CharacterView, things start breaking:
print(foo("01234".characters)) // "0"
For the CharacterView, SubSequences create completely independent instances, i.e. Index starts again at 0. To convert back to a main String index, one has to use the distance function and add that to the startIndex of the SubSequence in the main String.
func foo<T: Collection>(_ buffer: T) -> T.Iterator.Element
where T.Index == T.SubSequence.Index, T.SubSequence: Collection, T.SubSequence.IndexDistance == T.IndexDistance
{
let start = buffer.index(buffer.startIndex, offsetBy: 2)
let end = buffer.index(buffer.startIndex, offsetBy: 3)
let sub = buffer[start ... end]
let subIndex = sub.startIndex
let distance = sub.distance(from: sub.startIndex, to: subIndex)
let bufferIndex = buffer.index(start, offsetBy: distance)
return buffer[bufferIndex]
}
With this, all three examples now correctly print 2.
Why are String SubSequence indices not compatible with their base String? As long as everything is immutable, it doesn't make sense to me why Strings are a special case, even with all the Unicode stuff. I've also noticed that substring functions return Strings and not Slices as most other collections do. However, substrings are still documented to be return in O(1). Strange magic.
Is there a way to constraint a generic function to restrict to collections where the SubSequence indices are compatible with the base Sequence?
Can one even assume that SubSequence indices are compatible for non-String collections, or is this just a coincidence, and one should always use distance(from:to:) to convert indices?
That has been discussed on swift-evolution, filed as bug report
SR-1927 – Subsequences of String Views don’t behave correctly and recently been fixed
in StringCharacterView.swift
with
commit.
With that fix String.CharacterView behaves
like other collections in that its slices should use the same indices for the same elements as the original collection.

Immutable Dictionary value change

Can we change any pair value in let type Dictionary in Swift Langauage.
like :
let arr2 : AnyObject[] = [1, "23", "hello"]
arr2[1] = 23
arr2 // output: [1,23,"hello"]
let arr1 :Dictionary<Int,AnyObject> = [1: "One" , 2 : 2]
arr1[2] = 4 // not posible error
arr1
In Case of Immutable Array we can change its value like above but not in case of Immutable
Dictionary. Why?
This is taken from The Swift Programming Language book:
For dictionaries, immutability also means that you cannot replace the
value for an existing key in the dictionary. An immutable dictionary’s
contents cannot be changed once they are set.
Immutability has a slightly different meaning for arrays, however. You
are still not allowed to perform any action that has the potential to
change the size of an immutable array, but you are allowed to set a
new value for an existing index in the array.
Array declared with let has only immutable length. Contents can still be changed.
Dictionary declared with let is completely immutable, you can't change contents of it. If you want, you must use var instead of let.
Swift has changed a lot since then.
Array and Dictionary are value types. When declared with let, they cannot change any more. Especially, one cannot re-assign them, or the elements in them.
But if the type of the elements is reference type, you can change the properties of the elements in Array or Dictionary.
Here is a sample.(run in Xcode6 beta-6)
class Point {
var x = 0
var y = 0
}
let valueArr: [Int] = [1, 2, 3, 4]
let refArr: [Point] = [Point(), Point()]
valueArr[0] = -1 // error
refArr[0] = Point() // error
refArr[0].x = 1
let valueDict: [Int : Int] = [1: 1, 2: 2]
let refDict: [Int: Point] = [1: Point(), 2: Point()]
valueDict[1] = -1 //error
refDict[1] = Point() //error
refDict[1]!.x = -1