Сортировать объекты в массиве с помощью динамических вложенных ключей свойств

Предположим, что у вас есть один файл CMakeLists.txt в каталоге Source, вы создадите две переменные, используя разные команды file()

file(GLOB Dir1_Sources RELATIVE "Dir1" "*.cpp")
file(GLOB Dir2_Sources RELATIVE "Dir2" "*.cpp")

и добавьте оба набора, сгенерированные командами file() в исходный список вашей цели:

add_executable(MyProgram ${Dir1_Sources} ${Dir2_Sources})

В качестве альтернативы вы можете поместить файл CMakeLists.txt в Dir1 и Dir2 (Main) в качестве следует

Source
    |
    |_ CMakeLists.txt   
    |    > project(MyProgram)
    |    > cmake_minimum_required(VERSION 3.8)
    |    > add_subdirectory("Dir1")
    |    > add_subdirectory("Dir2")
    |
    |_ Dir1   
    |     |_ CMakeLists.txt   
    |         > file(GLOB Sources "*.cpp")
    |         > add_library(Dir1 STATIC ${Sources})
    |_ Dir2   
          |_ CMakeLists.txt   
              > file(GLOB Sources "*.cpp")
              > add_executable(MyProgram ${Sources})
              > target_link_libraries(MyProgram Dir1)

, чтобы добавить подкаталоги в качестве дополнительных (статических) библиотек, связанных с вашей главной целью.

9
задан Thore 17 February 2019 в 17:53
поделиться

4 ответа

Сравните элементы в функции сортировки следующим образом:

let v= c => keys.reduce((o,k) => o[k]||'',c)
return (isReverse ? -1 : 1) * v(a).localeCompare(v(b));

likte this:

sortBy = (keys, isReverse=false) => {
    this.setState(prevState => ({
        files: prevState.files.sort((a, b) => {
            let v=c=>keys.reduce((o,k) => o[k]||'',c)
            return (isReverse ? -1 : 1)*v(a).localeCompare(v(b));
        })
    }));
}

[1110 ] Вот пример того, как работает эта идея:

let files = [
 { general: { fileID: "3"}},
 { general: { fileID: "1"}},
 { general: { fileID: "2"}},
 { general: { }}
];


function sortBy(keys, arr, isReverse=false) {
    arr.sort((a,b,v=c=>keys.reduce((o,k) => o[k]||'',c)) =>             
      (isReverse ? -1 : 1)*v(a).localeCompare(v(b)) )        
}


sortBy(['general', 'fileID'],files,true);
console.log(files);

0
ответ дан Kamil Kiełczewski 17 February 2019 в 17:53
поделиться

Это также обрабатывает случай, когда путь преобразуется в не строковое значение путем преобразования его в строку. В противном случае .localeCompare может потерпеть неудачу.

sortBy = (keys, isReverse=false) => {
    this.setState(prevState => ({
        files: prevState.files.sort((a, b) => {
            const valueA = getValueAtPath(a, keys);
            const valueB = getValueAtPath(b, keys);

            if(isReverse) return valueB.localeCompare(valueA);

            return valueA.localeCompare(valueB);
        })
    }));
}

function getValueAtPath(file, path) {
    let value = file;
    let keys = [...path]; // preserve the original path array

    while(value && keys.length) {
      let key = keys.shift();
      value = value[key];
    }

    return (value || '').toString();
}
0
ответ дан abadalyan 17 February 2019 в 17:53
поделиться

Вы можете перебрать клавиши, чтобы получить значения, а затем сравнить их, как

sortBy = (keys, isReverse=false) => {

    this.setState(prevState => ({
        files: prevState.files.sort((a, b) => {
            const clonedKey = [...keys];
            let valueA = a;
            let valueB = b
            while(clonedKey.length > 0) {
                const key = clonedKey.shift();
                valueA = (valueA || {})[key];
                valueB = (valueB || {})[key];
            }
            valueA = valueA || '';
            valueB = valueB || '';
            if(isReverse) return valueB.localeCompare(valueA);

            return valueA.localeCompare(valueB);
        })
    }));
}
0
ответ дан Shubham Khatri 17 February 2019 в 17:53
поделиться

В настоящее время принятый ответ, за исключением внесения ошибок в ваш код , мало чем поможет вам. Использование простой функции deepProp уменьшит болезненное повторение -

const deepProp = (o = {}, props = []) =>
  props.reduce((acc = {}, p) => acc[p], o)

Теперь без такого большого шума -

sortBy = (keys, isReverse = false) =>
  this.setState ({
    files: // without mutating the previous state!
      [...this.state.files].sort((a,b) => {
        const valueA = deepProp(a, keys) || ''
        const valueB = deepProp(b, keys) || ''
        return isReverse
          ? valueA.localeCompare(valueB)
          : valueB.localeCompare(valueA)
      })
  })

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

sortBy = (comparator = asc) =>
  this.setState
    ( { files:
          isort
            ( contramap
                ( comparator
                , generalFileId
                )
            , this.state.files
            )
      }
    )

Ваш вопрос ставит нас перед изучением двух мощных функциональных концепций; мы будем использовать их, чтобы ответить на вопрос -

  1. Монады
  2. Контравариантные функторы

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

Ваш вопрос, в частности, говорит, что вы не используете внешний пакет прямо сейчас, но сейчас самое подходящее время для его получения. Давайте кратко рассмотрим пакет data.maybe -

Структура для значений, которые могут отсутствовать, или для вычислений, которые могут не работать. Maybe(a) явно моделирует эффекты, неявные в типах Nullable, таким образом, не имеет никаких проблем, связанных с использованием null или undefined - как NullPointerException или TypeError.

Звучит как хорошая подгонка. Мы начнем с написания функции safeProp, которая принимает объект и строку свойства в качестве входных данных. Интуитивно понятно, что safeProp безопасно возвращает свойство p объекта o -

const { Nothing, fromNullable } =
  require ('data.maybe')

const safeProp = (o = {}, p = '') =>

  // if o is an object
  Object (o) === o

    // access property p on object o, wrapping the result in a Maybe
    ? fromNullable (o[p])

    // otherwise o is not an object, return Nothing
    : Nothing ()

Вместо простого возврата o[p], который мог бы быть нулевое или неопределенное значение, мы вернемся Может быть , которое поможет нам в обработке результата -

const generalFileId = (o = {}) =>

  // access the general property
  safeProp (o, 'general')

    // if it exists, access the fileId property on the child
    .chain (child => safeProp (child, 'fileId'))

    // get the result if valid, otherwise return empty string
    .getOrElse ('') 

Теперь у нас есть функция, которая может принимать объекты различной сложности и [1171 ] гарантирует результат, который нас интересует -

console .log
  ( generalFileId ({ general: { fileId: 'a' } })  // 'a'
  , generalFileId ({ general: { fileId: 'b' } })  // 'b'
  , generalFileId ({ general: 'x' })              // ''
  , generalFileId ({ a: 'x '})                    // ''
  , generalFileId ({ general: { err: 'x' } })     // ''
  , generalFileId ({})                            // ''
  )

Это уже половина дела. Теперь мы можем перейти от нашего сложного объекта к точному строковому значению, которое мы хотим использовать для сравнения.

Я намеренно избегаю показывать здесь реализацию Maybe, потому что это само по себе является ценным уроком. Когда модуль обещает возможность X , мы предполагаем, что у нас есть возможность X , и игнорируем то, что происходит в черном ящике модуля. Суть абстракции данных заключается в том, чтобы скрыть проблемы, чтобы программист мог думать о вещах на более высоком уровне.

Может возникнуть вопрос, как работает Array? Как он вычисляет или корректирует свойство length, когда элемент добавляется или удаляется из массива? Как функция map или filter создает новый новый массив ? Если вы никогда не задумывались об этом раньше, это нормально! Массив является удобным модулем, потому что он устраняет эти проблемы из головы программиста; это просто работает как рекламируется .

Это применимо независимо от того, предоставляется ли модуль JavaScript, третьей стороной, например, из npm, или если вы написали модуль самостоятельно. Если Array не существует, мы могли бы реализовать его как нашу собственную структуру данных с эквивалентными удобствами. Пользователи нашего модуля получают полезные функциональные возможности без , вносящие дополнительную сложность. А-ха наступает момент, когда вы понимаете, что программист является его / ее собственным пользователем: когда вы сталкиваетесь с трудной проблемой, напишите модуль, чтобы избавиться от оков сложности. Придумайте ваше собственное удобство!

Мы покажем базовую реализацию Maybe позже в ответе, но сейчас нам просто нужно закончить сортировку ...


Начнем с двух основных компараторов, asc для сортировки по возрастанию, и desc для сортировки по убыванию -

const asc = (a, b) =>
  a .localeCompare (b)

const desc = (a, b) =>
  asc (a, b) * -1

В React мы не можем изменить предыдущее состояние, вместо этого мы должны создать новое состояние . Таким образом, чтобы отсортировать неизменным , мы должны реализовать isort, который не будет мутировать входной объект -

const isort = (compare = asc, xs = []) =>
  xs
    .slice (0)      // clone
    .sort (compare) // then sort

И, конечно, a и b иногда являются сложными объектами, поэтому случай, когда мы не можем напрямую позвонить asc или desc. Ниже contramap преобразует наши данные с помощью одной функции g, перед тем, как передаст данные другой функции, f -

const contramap = (f, g) =>
  (a, b) => f (g (a), g (b))

const files =
  [ { general: { fileId: 'e' } }
  , { general: { fileId: 'b' } }
  , { general: { fileId: 'd' } }
  , { general: { fileId: 'c' } }
  , { general: { fileId: 'a' } }
  ]

isort
  ( contramap (asc, generalFileId) // ascending comparator
  , files
  )

// [ { general: { fileId: 'a' } }
// , { general: { fileId: 'b' } }
// , { general: { fileId: 'c' } }
// , { general: { fileId: 'd' } }
// , { general: { fileId: 'e' } }
// ]

с помощью другого компаратора desc. ], мы можем видеть работу сортировки в другом направлении -

isort
  ( contramap (desc, generalFileId) // descending comparator
  , files 
  )

// [ { general: { fileId: 'e' } }
// , { general: { fileId: 'd' } }
// , { general: { fileId: 'c' } }
// , { general: { fileId: 'b' } }
// , { general: { fileId: 'a' } }
// ]

Теперь, чтобы написать метод для вашего компонента React, sortBy. Метод существенно сокращен до this.setState({ files: t (this.state.files) }), где t - это неизменное преобразование состояния вашей программы. Это хорошо, потому что сложность исключается из ваших компонентов, где тестирование затруднено, и вместо этого находится в универсальных модулях, которые легко тестировать -

sortBy = (reverse = true) =>
  this.setState
    ( { files:
          isort
            ( contramap
                ( reverse ? desc : asc
                , generalFileId
                )
            , this.state.files
            )
      }
    )

При этом используется логический переключатель как в вашем первоначальном вопросе, но поскольку React включает функциональный шаблон, я думаю, что он будет еще лучше в качестве функции более высокого порядка -

sortBy = (comparator = asc) =>
  this.setState
    ( { files:
          isort
            ( contramap
                ( comparator
                , generalFileId
                )
            , this.state.files
            )
      }
    )

Если не требуется гарантированное доступ к вложенному свойству чтобы быть general и fileId, мы можем сделать универсальную функцию, которая принимает список свойств и может искать вложенное свойство любой глубины -

const deepProp = (o = {}, props = []) =>
  props .reduce
    ( (acc, p) => // for each p, safely lookup p on child
        acc .chain (child => safeProp (child, p))
    , fromNullable (o) // init with Maybe o
    )

const generalFileId = (o = {}) =>
  deepProp (o, [ 'general', 'fileId' ]) // using deepProp
    .getOrElse ('')

const fooBarQux = (o = {}) =>
  deepProp (o, [ 'foo', 'bar', 'qux' ]) // any number of nested props
    .getOrElse (0)                      // customizable default

console.log
  ( generalFileId ({ general: { fileId: 'a' } } ) // 'a'
  , generalFileId ({})                            // ''
  , fooBarQux ({ foo: { bar: { qux: 1 } } } )     // 1
  , fooBarQux ({ foo: { bar: 2 } })               // 0
  , fooBarQux ({})                                // 0
  )

Выше мы используем пакет data.maybe, который дает нам возможность работать с потенциальными значениями . Модуль экспортирует функции для преобразования обычных значений в Maybe и наоборот, а также множество полезных операций, применимых к потенциальным значениям. Однако ничто не заставляет вас использовать именно эту реализацию. Концепция достаточно проста, чтобы вы могли реализовать fromNullable, Just и Nothing в виде пары дюжин строк, которые мы увидим позже в этом ответе -

Запустите полную демонстрацию ниже на ] repl.it

const { Just, Nothing, fromNullable } =
  require ('data.maybe')

const safeProp = (o = {}, p = '') =>
  Object (o) === o
    ? fromNullable (o[p])
    : Nothing ()

const generalFileId = (o = {}) =>
  safeProp (o, 'general')
    .chain (child => safeProp (child, 'fileId'))
    .getOrElse ('')

// ----------------------------------------------
const asc = (a, b) =>
  a .localeCompare (b)

const desc = (a, b) =>
  asc (a, b) * -1

const contramap = (f, g) =>
  (a, b) => f (g (a), g (b))

const isort = (compare = asc, xs = []) =>
  xs
    .slice (0)
    .sort (compare)

// ----------------------------------------------
const files =
  [ { general: { fileId: 'e' } }
  , { general: { fileId: 'b' } }
  , { general: { fileId: 'd' } }
  , { general: { fileId: 'c' } }
  , { general: { fileId: 'a' } }
  ]

isort
  ( contramap (asc, generalFileId)
  , files
  )

// [ { general: { fileId: 'a' } }
// , { general: { fileId: 'b' } }
// , { general: { fileId: 'c' } }
// , { general: { fileId: 'd' } }
// , { general: { fileId: 'e' } }
// ]

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

Наконец, sortBy реализован как функция высшего порядка, что означает, что мы не ограничены только восходящими и нисходящими сортировками, переключаемыми логическим значением reverse; любой действительный компаратор может быть использован. Это означает, что мы могли бы даже написать специализированный компаратор, который обрабатывает разрывы связей, используя пользовательскую логику, или сравнивает сначала year, затем month, затем day и т. Д .; функции высшего порядка расширяют ваши возможности.


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

// Maybe.js
const None =
  Symbol ()

class Maybe
{ constructor (v)
  { this.value = v }

  chain (f)
  { return this.value == None ? this : f (this.value) }

  getOrElse (v)
  { return this.value === None ? v : this.value }
}

const Nothing = () =>
  new Maybe (None)

const Just = v =>
  new Maybe (v)

const fromNullable = v =>
  v == null
    ? Nothing ()
    : Just (v)

module.exports =
  { Just, Nothing, fromNullable } // note the class is hidden from the user

Тогда мы будем использовать его в нашем модуле. Нам нужно только изменить импорт (require), но все остальное просто работает как есть из-за общедоступного API-интерфейса совпадений нашего модуля -

const { Just, Nothing, fromNullable } =
  require ('./Maybe') // this time, use our own Maybe

const safeProp = (o = {}, p = '') => // nothing changes here
  Object (o) === o
    ? fromNullable (o[p])
    : Nothing ()

const deepProp = (o, props) => // nothing changes here
  props .reduce
    ( (acc, p) =>
        acc .chain (child => safeProp (child, p))
    , fromNullable (o)
    )

// ...

Для получения дополнительной информации о том, как использовать контркарту и, возможно, некоторые неожиданные сюрпризы, пожалуйста, изучите следующие связанные ответы -

  1. мультисортировка с использованием контркарты
  2. рекурсивный поиск с использованием контркарты
0
ответ дан user633183 17 February 2019 в 17:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: