Przekazywanie tablicy do funkcji ze zmienną liczbą argumentów w języku Swift

151

W języku szybkiego programowania mówi:

Funkcje mogą również pobierać zmienną liczbę argumentów, zbierając je w tablicę.

  func sumOf(numbers: Int...) -> Int {
      ...
  }

Kiedy wywołuję taką funkcję z listą liczb oddzielonych przecinkami (sumOf (1, 2, 3, 4), są one udostępniane jako tablica wewnątrz funkcji.

Pytanie: co jeśli mam już tablicę liczb, które chcę przekazać do tej funkcji?

let numbers = [1, 2, 3, 4]
sumOf(numbers)

To kończy się niepowodzeniem z powodu błędu kompilatora: „Nie można znaleźć przeciążenia dla '__conversion', który akceptuje podane argumenty”. Czy istnieje sposób na przekształcenie istniejącej tablicy w listę elementów, które mogę przekazać do funkcji wariadycznej?

Ole Begemann
źródło
1
Dlaczego po prostu nie skonfigurować funkcji, aby zamiast tego pobierała tablicę liczb całkowitych?
Mick MacCallum
27
Jasne, ale mogę nie być autorem funkcji i mogę nie być w stanie (lub chcieć) jej zmienić.
Ole Begemann

Odpowiedzi:

97

Splatting nie jest jeszcze w języku , co potwierdzają twórcy. Na razie obejściem jest użycie przeciążenia lub czekanie, jeśli nie możesz dodać przeciążeń.

manojlds
źródło
3
Czy Splatting jest już w języku? Próbuję zadzwonićsumOf(...numbers)
Noitidart
Tak rozczarowujące! Udało mi się to nawet w czymś tak prostym, jak próba delegowania moich własnych wywołań dziennika print!
Mark A. Donohoe
65

Oto obejście, które znalazłem. Wiem, że to nie jest dokładnie to, czego chcesz, ale wydaje się, że działa.

Krok 1: Zadeklaruj żądaną funkcję za pomocą tablicy zamiast argumentów wariadycznych:

func sumOf(numbers: [Int]) -> Int {
    var total = 0
    for i in numbers {
        total += i
    }
    return total
}

Krok 2: Wywołaj to z poziomu funkcji wariadycznej:

func sumOf(numbers: Int...) -> Int {
    return sumOf(numbers)
}

Krok 3: Zadzwoń w dowolny sposób:

var variadicSum = sumOf(1, 2, 3, 4, 5)
var arraySum = sumOf([1, 2, 3, 4, 5])

Wydaje się dziwne, ale działa w moich testach. Daj mi znać, jeśli powoduje to nieprzewidziane problemy dla kogoś. Wydaje się, że Swift jest w stanie rozdzielić różnicę między dwoma wywołaniami o tej samej nazwie funkcji.

Ponadto, dzięki tej metodzie, jeśli Apple aktualizuje język, jak sugeruje odpowiedź @ manojid, wystarczy zaktualizować te funkcje. W przeciwnym razie będziesz musiał przejść i wiele zmienić nazwy.

Logan
źródło
Dzięki, podoba mi się obejście. Nadal udzielę „prawidłowej” odpowiedzi manojldom za znalezienie linku do oficjalnego potwierdzenia, że ​​funkcja nie jest jeszcze dostępna. Mam nadzieję że rozumiesz.
Ole Begemann
Prawdopodobnie postępujesz zgodnie z Przewodnikiem i swoją func sumOf (liczby: [Int]) -> Int faktycznie oblicza średnią
gfelisberto
18

Możesz przesłać funkcję:

typealias Function = [Int] -> Int
let sumOfArray = unsafeBitCast(sumOf, Function.self)
sumOfArray([1, 2, 3])
Guoye Zhang
źródło
Wspaniały! Działa to również z wieloma (nazwanymi) parametrami; np .: func sumOf(foo numbers: Int..., bar: Bool) -> Int {};wymagatypealias Function = (foo: [Int], bar: Bool) -> Int;
ThomasR
Wspaniały! Ocal moje życie.
JerryZhou
Jak mogę zrobić podobne rzeczy z AnyObject ...? stackoverflow.com/questions/42016358/ ... Dzięki.
JerryZhou
To bardzo zły pomysł. Nie ma absolutnie żadnej gwarancji, że to zadziała.
idmean
1
@MattMc Jasne. O ile wiem, nie ma nic, co pozwoliłoby ci po prostu przerzucić jeden typ wywoływalny na inny za pomocą unsafeBitCast. To może działać dzisiaj, ale jeśli nie mówi tak, następna wersja kompilatora może dosłownie zrobić wszystko tutaj (błąd kompilatora / awaria / losowe wykonanie kodu ...). Spójrz na poważnie wyglądające ostrzeżenie na stronie unsafeBitCast .
idmean
15

Możesz użyć funkcji pomocniczej jako takiej:

func sumOf (numbers : [Int])  -> Int { return numbers.reduce(0, combine: +) }
func sumOf (numbers : Int...) -> Int { return sumOf (numbers) }
GoZoner
źródło
12
Pod warunkiem, że istnieje wersja dla tablic. Pomyślałem, że podstawą pytania jest to, że pierwotna funkcja przyjmuje Int...i nie można jej (łatwo) zmienić?
2
@delnan: Dobrze. Wydaje mi się, że powinien istnieć sposób na przekazanie tablicy do funkcji wariadycznej, biorąc pod uwagę, że argumenty wariadyczne i tak są przekształcane w tablicę.
Ole Begemann
1
Języki, które akceptują zmienną liczbę argumentów w funkcjach, takich jak Schemat, mają applyprocedurę. Niektórzy nazywają to „splataniem”.
GoZoner
1
O czym sumArraytu mowa?
Rob
2

Wiem, że ta odpowiedź nie odpowiada dokładnie na twoje pytanie, ale czuję, że warto ją odnotować. Ja też zacząłem bawić się Swiftem i od razu wpadłem na podobne pytanie. Odpowiedź Manojlda jest lepsza na twoje pytanie, zgadzam się, ale znowu mam inne obejście, które wymyśliłem. Tak się składa, że ​​bardziej lubię Logana.

W moim przypadku chciałem tylko przekazać tablicę:

func sumOf(numbers: Array<Int>) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}

var someNums = [8,7,2,9,12]
sumOf(someNums)
sumOf([10, 15, 20])

Chciałem się tylko podzielić, na wypadek gdyby ktoś inny pomyślał tak jak ja. Przez większość czasu wolałbym przekazywać tablicę w ten sposób, ale nie sądzę jeszcze, że opcja „Szybko”. :)

gregthegeek
źródło
1

Zrobiłem to (Wrapper + Identity Mapping):

func addBarButtonItems(types: REWEBarButtonItemType...) {
    addBarButtonItems(types: types.map { $0 })
}

func addBarButtonItems(types: [REWEBarButtonItemType]) {
    // actual implementation
}
schirrmacher
źródło
0

Szybki 5

Jest to podejście z @dynamicCallablefunkcją, która pozwala uniknąć przeciążenia, unsafeBitCastale powinieneś zrobić konkretny structsposób wywołania:

@dynamicCallable
struct SumOf {
    func dynamicallyCall(withArguments args: [Int]) -> Int {
        return args.reduce(0, +)
    }
}

let sum = SumOf()

// Use a dynamic method call.
sum(1, 2, 3) // 6

// Call the underlying method directly.
sum.dynamicallyCall(withArguments: [1, 2, 3]) // 6
iUrii
źródło