Go: Какой самый быстрый / чистый способ удалить несколько записей из фрагмента? - PullRequest
16 голосов
/ 16 февраля 2011

Как бы вы реализовали функцию deleteRecords в приведенном ниже коде:

Example:

type Record struct {
  id int
  name string
}

type RecordList []*Record

func deleteRecords( l *RecordList, ids []int ) {
   // Assume the RecordList can contain several 100 entries.
   // and the number of the of the records to be removed is about 10.
   // What is the fastest and cleanest ways to remove the records that match
   // the id specified in the records list.
}

Ответы [ 7 ]

17 голосов
/ 17 февраля 2011

Я выполнил некоторые микро-бенчмаркинг на своей машине, опробовав большинство подходов, приведенных здесь в ответах, и этот код работает быстрее всего, когда у вас есть около 40 элементов в списке идентификаторов:

func deleteRecords(data []*Record, ids []int) []*Record {
    w := 0 // write index

loop:
    for _, x := range data {
        for _, id := range ids {
            if id == x.id {
                continue loop
            }
        }
        data[w] = x
        w++
    }
    return data[:w]
}

Вы не сказали, важно ли сохранять порядок записей в списке.Если вы этого не сделаете, то эта функция быстрее, чем описанная выше, и все еще довольно чистая.

func reorder(data []*Record, ids []int) []*Record {
    n := len(data)
    i := 0
loop:
    for i < n {
        r := data[i]
        for _, id := range ids {
            if id == r.id {
                data[i] = data[n-1]
                n--
                continue loop
            }
        }
        i++
    }
    return data[0:n]
}

По мере увеличения числа идентификаторов возрастает и стоимость линейного поиска.Приблизительно в 50 элементах использование карты или выполнение бинарного поиска для поиска идентификатора становится более эффективным, если вы можете избежать перестроения карты (или пересмотра списка) каждый раз.При нескольких сотнях идентификаторов становится эффективнее использовать карту или бинарный поиск, даже если вам приходится каждый раз перестраивать ее.

Если вы хотите сохранить исходное содержимое среза, что-то вроде этого более подходит:

func deletePreserve(data []*Record, ids []int) []*Record {
    wdata := make([]*Record, len(data))
    w := 0
loop:
    for _, x := range data {
        for _, id := range ids {
            if id == x.id {
                continue loop
            }
        }
        wdata[w] = x
        w++
    }
    return wdata[0:w]
}
3 голосов
/ 17 февраля 2011

Для личного проекта я сделал что-то вроде этого:

func filter(sl []int, fn func(int) bool) []int {
    result := make([]int, 0, len(sl))
    last := 0
    for i, v := range sl {
        if fn(v) {
            result = append(result, sl[last:i]...)
            last = i + 1 
        }   
    }   
    return append(result, sl[last:]...)
}

Он не изменяет оригинал, но должен быть относительно эффективным.Наверное, лучше просто сделать:

func filter(sl []int, fn func(int) bool) (result []int) {
    for _, v := range sl {
       if !fn(v) {
         result = append(result, v)
       }
    }
    return
}

Проще и чище.Если вы хотите сделать это на месте, вы, вероятно, хотите что-то вроде:

func filter(sl []int, fn func(int) bool) []int {
    outi := 0
    res := sl
    for _, v := range sl {
        if !fn(v) {
            res[outi] = v 
            outi++
        }   
    }   
    return res[0:outi]
}

Вы можете оптимизировать это, чтобы использовать copy для копирования диапазонов элементов, но это вдвое больше кода и, вероятно, не стоит того.

Итак, в этом конкретном случае я бы, вероятно, сделал что-то вроде:

func deleteRecords(l []*Record, ids []int) []*Record {
    outi := 0
L:
    for _, v := range l { 
        for _, id := range ids {
            if v.id == id {
                continue L
            }   
        }   
        l[outi] = v 
        outi++
    }   
    return l[0:outi]
}

(Примечание: не проверено.)

Нет выделений, ничего сложного, иИсходя из приблизительного размера списка записей и списка идентификаторов, которые вы представили, простой линейный поиск, скорее всего, подойдет, как и более интересные вещи, но без каких-либо накладных расходов.Я понимаю, что моя версия изменяет слайс , а возвращает новый слайс, но это не лишает смысла в Go, и это позволяет избежать принудительного выделения слайса в месте вызова для выделения кучи.

2 голосов
/ 17 февраля 2011

Вместо многократного поиска идентификаторов, вы можете использовать карту. Этот код предварительно выделяет полный размер карты, а затем просто перемещает элементы массива на место. Других распределений нет.

func deleteRecords(l *RecordList, ids []int) {
    m := make(map[int]bool, len(ids))
    for _, id := range ids {
        m[id] = true
    }
    s, x := *l, 0
    for _, r := range s {
        if !m[r.id] {
            s[x] = r
            x++
        }
    }
    *l = s[0:x]
}
2 голосов
/ 17 февраля 2011

Для описанного вами случая, когда len (ids) составляет приблизительно 10, а len (* l) составляет несколько сотен, это должно быть относительно быстро, поскольку оно минимизирует распределение памяти путем обновления на месте.

package main

import (
    "fmt"
    "strconv"
)

type Record struct {
    id   int
    name string
}

type RecordList []*Record

func deleteRecords(l *RecordList, ids []int) {
    rl := *l
    for i := 0; i < len(rl); i++ {
        rid := rl[i].id
        for j := 0; j < len(ids); j++ {
            if rid == ids[j] {
                copy(rl[i:len(*l)-1], rl[i+1:])
                rl[len(rl)-1] = nil
                rl = rl[:len(rl)-1]
                break
            }
        }
    }
    *l = rl
}

func main() {
    l := make(RecordList, 777)
    for i := range l {
        l[i] = &Record{int(i), "name #" + strconv.Itoa(i)}
    }
    ids := []int{0, 1, 2, 4, 8, len(l) - 1, len(l)}
    fmt.Println(ids, len(l), cap(l), *l[0], *l[1], *l[len(l)-1])
    deleteRecords(&l, ids)
    fmt.Println(ids, len(l), cap(l), *l[0], *l[1], *l[len(l)-1])
}

Выход:

[0 1 2 4 8 776 777] 777 777 {0 name #0} {1 name #1} {776 name #776}
[0 1 2 4 8 776 777] 772 777 {1 name #1} {3 name #3} {775 name #775}
1 голос
/ 17 февраля 2011

Используйте метод удаления векторных пакетов в качестве руководства или просто используйте Вектор вместо среза.

0 голосов
/ 21 февраля 2011

При достаточно большом l и идентификаторах будет более эффективно сначала отсортировать () оба списка, а затем выполнить над ними один цикл вместо двух вложенных циклов

0 голосов
/ 16 февраля 2011

Вот один из вариантов, но я надеюсь, что есть более чистые / более быстрые и функционально выглядящие:

func deleteRecords( l *RecordList, ids []int ) *RecordList {
    var newList RecordList
    for _, rec := range l {
        toRemove := false
        for _, id := range ids {
        if rec.id == id {
            toRemove = true
        }
        if !toRemove {
            newList = append(newList, rec)
        }
    }
    return newList
}
...